rivalsdata-api 1.1.0__tar.gz → 1.2.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rivalsdata-api
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Python client and MCP server for public Marvel Rivals stats from RivalsData
5
5
  Project-URL: Homepage, https://github.com/GS-Rionnag/rivalsdata-api
6
6
  Project-URL: Repository, https://github.com/GS-Rionnag/rivalsdata-api
@@ -67,6 +67,7 @@ with RivalsDataClient() as rd:
67
67
 
68
68
  # Player profile sections are lazy resource managers.
69
69
  hero_season = player.heroes.fetch(season=20)
70
+ all_hero_seasons = player.heroes.fetch(season="all")
70
71
  map_stats = player.stats.maps(season=20)
71
72
  match_page = player.matches.fetch(season=20)
72
73
 
@@ -84,6 +85,37 @@ with RivalsDataClient() as rd:
84
85
  print(hero_season[0].win_rate) # integer percent when wins/losses are present
85
86
  ```
86
87
 
88
+ Calculated player class statistics are available through
89
+ `player.stats.classes(season=20)` and the MCP `get_player_stats` tool with
90
+ `category="classes"`. Each row contains `player_class` (`tank`, `support`,
91
+ `dps`), the official role, hero IDs, and separate `competitive` and `quickplay`
92
+ totals for games, wins, losses, and available MVP/SVP counts.
93
+
94
+ ```python
95
+ with RivalsDataClient() as rd:
96
+ stats = rd.get_player("GS-").stats.classes(season=20)
97
+ for row in stats.classes:
98
+ print(row.player_class, row.competitive.win_rate)
99
+ print(stats.excluded) # Unknown roles or incomplete win/loss records
100
+ ```
101
+
102
+ Win rates are `total wins / (total wins + total losses)`, rounded to an integer
103
+ percent. They are weighted by hero records, rather than averaging hero win
104
+ rates. Switching heroes can make one match contribute to multiple records;
105
+ these totals describe hero participation, not distinct matches. Empty modes
106
+ have a `None` win rate. Role mappings were observed on RivalsData on
107
+ 2026-09-30, including Deadpool's separate role IDs; generic Deadpool and unknown
108
+ IDs are excluded rather than assigned a guessed class.
109
+
110
+ Character playtime was checked with Camoufox on 2026-09-30. Player hero stats
111
+ did not expose cumulative hours, including in All Seasons. Match details do
112
+ provide seconds in `match.teams[].players[].heroes[].play_time`; the site shows
113
+ these as minutes and seconds when hovering a hero portrait. Sum the relevant
114
+ player's entries across distinct retrieved matches and divide by 3600 to get
115
+ character hours for those matches. Incomplete history prevents treating this as
116
+ a lifetime total. See [the playtime investigation](docs/API.md#character-playtime-investigation-2026-09-30)
117
+ for the observed fields and example.
118
+
87
119
  ## MCP server (ChatGPT and Claude)
88
120
 
89
121
  Install the MCP extra and the package:
@@ -1,70 +1,102 @@
1
- # rivalsdata-api
2
-
3
- An unofficial Python client for RivalsData's public Marvel Rivals data. It
4
- uses the site's undocumented API, so routes and fields can change. The client
5
- keeps unknown response fields accessible instead of discarding them.
6
-
7
- ## Install
8
-
9
- Python 3.10 or newer:
10
-
11
- ```console
12
- python -m pip install rivalsdata-api
13
- ```
14
-
15
- For editable development, clone the repository and run
16
- `python -m pip install -e '.[dev]'`. The optional Camoufox Cloudflare fallback
17
- is installed with `python -m pip install 'rivalsdata-api[browser]'`, followed
18
- by `python -m camoufox fetch`.
19
-
20
- ## Quick start
21
-
22
- ```python
1
+ # rivalsdata-api
2
+
3
+ An unofficial Python client for RivalsData's public Marvel Rivals data. It
4
+ uses the site's undocumented API, so routes and fields can change. The client
5
+ keeps unknown response fields accessible instead of discarding them.
6
+
7
+ ## Install
8
+
9
+ Python 3.10 or newer:
10
+
11
+ ```console
12
+ python -m pip install rivalsdata-api
13
+ ```
14
+
15
+ For editable development, clone the repository and run
16
+ `python -m pip install -e '.[dev]'`. The optional Camoufox Cloudflare fallback
17
+ is installed with `python -m pip install 'rivalsdata-api[browser]'`, followed
18
+ by `python -m camoufox fetch`.
19
+
20
+ ## Quick start
21
+
22
+ ```python
23
23
  from rivalsdata import RivalsDataClient, hero_id, hero_name
24
24
 
25
25
  print(hero_name(1016)) # Loki
26
26
  print(hero_id("Loki")) # 1016
27
-
28
- with RivalsDataClient() as rd:
29
- player = rd.get_player("GS-") # numeric UID works too
30
- print(player.name, player.level, player.rank_game_season)
31
-
32
- # Player profile sections are lazy resource managers.
33
- hero_season = player.heroes.fetch(season=20)
34
- map_stats = player.stats.maps(season=20)
35
- match_page = player.matches.fetch(season=20)
36
-
37
- # Current match (None if the profile is not currently in a game).
38
- live_game = player.live_game.fetch()
39
- if live_game is not None:
40
- print(live_game.players, live_game.team_avg_rank)
41
-
42
- # Site-wide resources are available from the client.
43
- leaderboard = rd.leaderboards.fetch(limit=100, season=20, platform=1)
44
- tier_list = rd.heroes.tier_list(platform=1, rank="grandmaster_plus")
45
- team_ups = rd.team_ups.fetch(platform=1, rank="grandmaster_plus")
46
- xp_page = rd.insights.xp()
47
-
48
- print(hero_season[0].win_rate) # integer percent when wins/losses are present
49
- ```
50
-
51
- ## MCP server (ChatGPT and Claude)
52
-
53
- Install the MCP extra and the package:
54
-
55
- ```console
56
- python -m pip install 'rivalsdata-api[mcp]'
57
- ```
58
-
59
- The server exposes read-only tools for player search and profiles, a player's
60
- current live match (when they are in one), match history, player stats,
61
- leaderboards, heroes, team-ups, public insights, matches, and factions. The
62
- `show_player_dashboard` tool returns an MCP-UI player card with rank and
63
- competitive record plus one optional data section per call: current match
64
- roster, hero win-rate chart, or recent match form with K/D/A. This keeps each
65
- dashboard pull to the profile plus at most one additional endpoint. It supports
66
- local stdio for Claude Desktop
67
- and Streamable HTTP for remote MCP clients such as ChatGPT. Data comes from
27
+
28
+ with RivalsDataClient() as rd:
29
+ player = rd.get_player("GS-") # numeric UID works too
30
+ print(player.name, player.level, player.rank_game_season)
31
+
32
+ # Player profile sections are lazy resource managers.
33
+ hero_season = player.heroes.fetch(season=20)
34
+ all_hero_seasons = player.heroes.fetch(season="all")
35
+ map_stats = player.stats.maps(season=20)
36
+ match_page = player.matches.fetch(season=20)
37
+
38
+ # Current match (None if the profile is not currently in a game).
39
+ live_game = player.live_game.fetch()
40
+ if live_game is not None:
41
+ print(live_game.players, live_game.team_avg_rank)
42
+
43
+ # Site-wide resources are available from the client.
44
+ leaderboard = rd.leaderboards.fetch(limit=100, season=20, platform=1)
45
+ tier_list = rd.heroes.tier_list(platform=1, rank="grandmaster_plus")
46
+ team_ups = rd.team_ups.fetch(platform=1, rank="grandmaster_plus")
47
+ xp_page = rd.insights.xp()
48
+
49
+ print(hero_season[0].win_rate) # integer percent when wins/losses are present
50
+ ```
51
+
52
+ Calculated player class statistics are available through
53
+ `player.stats.classes(season=20)` and the MCP `get_player_stats` tool with
54
+ `category="classes"`. Each row contains `player_class` (`tank`, `support`,
55
+ `dps`), the official role, hero IDs, and separate `competitive` and `quickplay`
56
+ totals for games, wins, losses, and available MVP/SVP counts.
57
+
58
+ ```python
59
+ with RivalsDataClient() as rd:
60
+ stats = rd.get_player("GS-").stats.classes(season=20)
61
+ for row in stats.classes:
62
+ print(row.player_class, row.competitive.win_rate)
63
+ print(stats.excluded) # Unknown roles or incomplete win/loss records
64
+ ```
65
+
66
+ Win rates are `total wins / (total wins + total losses)`, rounded to an integer
67
+ percent. They are weighted by hero records, rather than averaging hero win
68
+ rates. Switching heroes can make one match contribute to multiple records;
69
+ these totals describe hero participation, not distinct matches. Empty modes
70
+ have a `None` win rate. Role mappings were observed on RivalsData on
71
+ 2026-09-30, including Deadpool's separate role IDs; generic Deadpool and unknown
72
+ IDs are excluded rather than assigned a guessed class.
73
+
74
+ Character playtime was checked with Camoufox on 2026-09-30. Player hero stats
75
+ did not expose cumulative hours, including in All Seasons. Match details do
76
+ provide seconds in `match.teams[].players[].heroes[].play_time`; the site shows
77
+ these as minutes and seconds when hovering a hero portrait. Sum the relevant
78
+ player's entries across distinct retrieved matches and divide by 3600 to get
79
+ character hours for those matches. Incomplete history prevents treating this as
80
+ a lifetime total. See [the playtime investigation](docs/API.md#character-playtime-investigation-2026-09-30)
81
+ for the observed fields and example.
82
+
83
+ ## MCP server (ChatGPT and Claude)
84
+
85
+ Install the MCP extra and the package:
86
+
87
+ ```console
88
+ python -m pip install 'rivalsdata-api[mcp]'
89
+ ```
90
+
91
+ The server exposes read-only tools for player search and profiles, a player's
92
+ current live match (when they are in one), match history, player stats,
93
+ leaderboards, heroes, team-ups, public insights, matches, and factions. The
94
+ `show_player_dashboard` tool returns an MCP-UI player card with rank and
95
+ competitive record plus one optional data section per call: current match
96
+ roster, hero win-rate chart, or recent match form with K/D/A. This keeps each
97
+ dashboard pull to the profile plus at most one additional endpoint. It supports
98
+ local stdio for Claude Desktop
99
+ and Streamable HTTP for remote MCP clients such as ChatGPT. Data comes from
68
100
  RivalsData's undocumented API and may change; profile match history can be
69
101
  private.
70
102
 
@@ -75,9 +107,9 @@ a hero name or numeric ID. The pip package also exports `hero_name(id)` and
75
107
  `.top_hero_name` conveniences. Those resolved fields are included in mapping
76
108
  iteration and `.to_dict()` output to simplify serialization; `.raw` remains the
77
109
  untouched source payload.
78
-
79
- ### How the MCP UI works
80
-
110
+
111
+ ### How the MCP UI works
112
+
81
113
  `show_player_dashboard` fetches current data, then returns an HTML UI resource
82
114
  alongside the tool result. It advertises the dashboard through
83
115
  `_meta.ui.resourceUri`, uses the `text/html;profile=mcp-app` resource MIME type,
@@ -88,44 +120,44 @@ also includes `openai/outputTemplate` as a ChatGPT compatibility alias. Hosts
88
120
  without UI support still receive a text result and can use the regular MCP
89
121
  tools. The dashboard is a snapshot from the time the tool runs; ask for it
90
122
  again to refresh.
91
- The `section` argument defaults to `live_match`; use `hero_form` or
92
- `recent_matches` in separate calls when you need those views.
93
-
94
- ### Claude Desktop (local)
95
-
96
- Add a server entry to Claude Desktop's `claude_desktop_config.json`, replacing
97
- the path with the Python executable in the environment where the extra is
98
- installed:
99
-
100
- ```json
101
- {
102
- "mcpServers": {
103
- "rivalsdata": {
104
- "command": "C:\\path\\to\\venv\\Scripts\\python.exe",
105
- "args": ["-m", "rivalsdata.mcp_server"]
106
- }
107
- }
108
- }
109
- ```
110
-
111
- On macOS/Linux, use the environment's `bin/python` path. Restart Claude Desktop
112
- after saving the configuration.
113
-
114
- ### ChatGPT or remote Claude connector
115
-
116
- Run the server on a host reachable over HTTPS:
117
-
118
- ```console
119
- uvicorn rivalsdata.mcp_server:app --host 0.0.0.0 --port 8000
120
- ```
121
-
122
- The MCP endpoint is `/mcp` (for example, `https://your-host.example/mcp`). Add
123
- that endpoint through the client's custom/remote MCP connector settings. The
124
- server does not implement authentication; put it behind an authenticated
125
- HTTPS gateway before exposing it publicly. For local development, bind to
126
- `127.0.0.1` instead. The `app` is the MCP SDK's Streamable HTTP ASGI
127
- application; Uvicorn manages its lifespan and session manager.
128
-
123
+ The `section` argument defaults to `live_match`; use `hero_form` or
124
+ `recent_matches` in separate calls when you need those views.
125
+
126
+ ### Claude Desktop (local)
127
+
128
+ Add a server entry to Claude Desktop's `claude_desktop_config.json`, replacing
129
+ the path with the Python executable in the environment where the extra is
130
+ installed:
131
+
132
+ ```json
133
+ {
134
+ "mcpServers": {
135
+ "rivalsdata": {
136
+ "command": "C:\\path\\to\\venv\\Scripts\\python.exe",
137
+ "args": ["-m", "rivalsdata.mcp_server"]
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ On macOS/Linux, use the environment's `bin/python` path. Restart Claude Desktop
144
+ after saving the configuration.
145
+
146
+ ### ChatGPT or remote Claude connector
147
+
148
+ Run the server on a host reachable over HTTPS:
149
+
150
+ ```console
151
+ uvicorn rivalsdata.mcp_server:app --host 0.0.0.0 --port 8000
152
+ ```
153
+
154
+ The MCP endpoint is `/mcp` (for example, `https://your-host.example/mcp`). Add
155
+ that endpoint through the client's custom/remote MCP connector settings. The
156
+ server does not implement authentication; put it behind an authenticated
157
+ HTTPS gateway before exposing it publicly. For local development, bind to
158
+ `127.0.0.1` instead. The `app` is the MCP SDK's Streamable HTTP ASGI
159
+ application; Uvicorn manages its lifespan and session manager.
160
+
129
161
  Every implemented response route now has named endpoint models and row models
130
162
  with annotations for fields observed in the API inventory. This includes
131
163
  `Player`, `Match`, `MatchHistory`, `MatchTeam`, `MatchPlayer`, `Character`,
@@ -151,43 +183,43 @@ with RivalsDataClient() as rd:
151
183
  tier_list = rd.heroes.tier_list() # TierListResponse
152
184
  print(tier_list.heroes[0].hero_id) # Character
153
185
  ```
154
-
155
- ## Public resources
156
-
157
- - `rd.leaderboards.fetch(...)` — global player ranking.
158
- - `rd.heroes.tier_list(...)`, `.get(hero_id)`, `.meta(hero_id, range=90)`,
159
- `.leaderboard(hero_id, **filters)` — hero metrics and ranking.
160
- - `rd.team_ups.fetch(...)` — team-up stats.
161
- - `rd.insights.punishments(...)`, `.xp(...)`, `.top_500(...)`, `.commbans(...)`,
162
- `.leavers(...)` — public insights and cursor metadata.
163
- - `rd.factions.get(faction_id)`, `rd.matches.get(match_id)`,
164
- `rd.profiles.get(username)`, and `rd.favorites.fetch(uids)` — detail/profile
165
- lookups.
166
- - `player.heroes.fetch(...)`, `.matches.fetch(...)`, `.live_game.fetch()`, `.teammates.fetch(...)`,
167
- `.crosshairs.fetch()`, `.proficiency.fetch()`, `.punishments.fetch()`,
168
- `.name_history.fetch()` — profile sections.
169
- - `player.stats.heroes(...)`, `.maps(...)`, `.bans(...)` — detailed profile stats.
170
-
171
- See [the observed API inventory](docs/API.md) for methods, parameters, observed
172
- response shapes, and endpoints that require a RivalsData account. The API
173
- inventory distinguishes observed behavior from inferred/unverified details.
174
-
175
- ## Cloudflare fallback
176
-
177
- Requests use curl_cffi with a Chrome TLS profile by default. If blocked, enable
178
- the optional browser fallback:
179
-
180
- ```python
181
- with RivalsDataClient(use_browser_fallback=True) as rd:
182
- player = rd.get_player(1970288503)
183
- ```
184
-
185
- ## Errors and contributions
186
-
187
- All package exceptions inherit from `RivalsDataError`. See
188
- [CONTRIBUTING.md](CONTRIBUTING.md) for setup, code layout, change workflow, and
189
- notes for new contributors. [docs/PROJECT_CONTEXT.md](docs/PROJECT_CONTEXT.md)
190
- is the handoff document for new coding sessions.
191
-
192
- This project is not affiliated with RivalsData, NetEase, or Marvel. Keep
193
- request rates reasonable and respect the site's terms.
186
+
187
+ ## Public resources
188
+
189
+ - `rd.leaderboards.fetch(...)` — global player ranking.
190
+ - `rd.heroes.tier_list(...)`, `.get(hero_id)`, `.meta(hero_id, range=90)`,
191
+ `.leaderboard(hero_id, **filters)` — hero metrics and ranking.
192
+ - `rd.team_ups.fetch(...)` — team-up stats.
193
+ - `rd.insights.punishments(...)`, `.xp(...)`, `.top_500(...)`, `.commbans(...)`,
194
+ `.leavers(...)` — public insights and cursor metadata.
195
+ - `rd.factions.get(faction_id)`, `rd.matches.get(match_id)`,
196
+ `rd.profiles.get(username)`, and `rd.favorites.fetch(uids)` — detail/profile
197
+ lookups.
198
+ - `player.heroes.fetch(...)`, `.matches.fetch(...)`, `.live_game.fetch()`, `.teammates.fetch(...)`,
199
+ `.crosshairs.fetch()`, `.proficiency.fetch()`, `.punishments.fetch()`,
200
+ `.name_history.fetch()` — profile sections.
201
+ - `player.stats.heroes(...)`, `.maps(...)`, `.bans(...)` — detailed profile stats.
202
+
203
+ See [the observed API inventory](docs/API.md) for methods, parameters, observed
204
+ response shapes, and endpoints that require a RivalsData account. The API
205
+ inventory distinguishes observed behavior from inferred/unverified details.
206
+
207
+ ## Cloudflare fallback
208
+
209
+ Requests use curl_cffi with a Chrome TLS profile by default. If blocked, enable
210
+ the optional browser fallback:
211
+
212
+ ```python
213
+ with RivalsDataClient(use_browser_fallback=True) as rd:
214
+ player = rd.get_player(1970288503)
215
+ ```
216
+
217
+ ## Errors and contributions
218
+
219
+ All package exceptions inherit from `RivalsDataError`. See
220
+ [CONTRIBUTING.md](CONTRIBUTING.md) for setup, code layout, change workflow, and
221
+ notes for new contributors. [docs/PROJECT_CONTEXT.md](docs/PROJECT_CONTEXT.md)
222
+ is the handoff document for new coding sessions.
223
+
224
+ This project is not affiliated with RivalsData, NetEase, or Marvel. Keep
225
+ request rates reasonable and respect the site's terms.
@@ -16,7 +16,7 @@ and `.raw` so upstream additions are not discarded.
16
16
  | Search | `POST /players/search` | `{"name": name}` | Search rows; `aid` may contain the numeric UID after the final `_`. |
17
17
  | Player overview | `POST /player` | `{"uid": number}` | Object keys observed: `cached_at`, `claimed`, `faction`, `icon`, `last_seen`, `leaderboard`, `level`, `login_os`, `match_history_is_visible`, `mood`, `name`, `rank_game_season`, `status`, `uid`, `xp`. `rank_game_season` is keyed by game/season ids; competitive rows include `battle_count`, `rank_game_id`, `rank_score`, and `win_count`. |
18
18
  | Player live game | `POST /live` | `{"match_id": status.battle_id, "uid": number}` | Object keys observed: `players` (12 player entries keyed by team slot) and `team_avg_rank` (rank averages keyed by side). Player rows include `ai`, `games`, `icon`, `losses`, `name`, `proficiency`, `rank`, `side`, `team_id`, `top_heroes`, `uid`, and `wins`. The match ID comes from the player's current `/player` response; the endpoint was observed on a profile marked `In game (Competitive)`. |
19
- | Player hero summary | `POST /player/heroes` | `uid`, optional `season` | Array rows: `assists`, `deaths`, `games`, `hero_id`, `kda`, `kills`, `losses`, `rank`, `wins`. |
19
+ | Player hero summary | `POST /player/heroes` | `uid`, optional `season`; wrapper `season="all"` sends season ID `-1` | Array rows: `assists`, `deaths`, `games`, `hero_id`, `kda`, `kills`, `losses`, `rank`, `wins`. The all-seasons selector was verified against the public `GS-` profile; `season=-1` returned 40 hero rows, while `season=0` returned none. |
20
20
  | Player crosshairs | `POST /player/crosshairs` | `uid` | Array rows: `crosshair`, `uses`. |
21
21
  | Player match history | `POST /player/matches/cached` (or `/player/matches`) | `uid`, `cursor`, optional `season`; cached endpoint also accepts `mode`, `hero`, `teammate` | Cached response keys observed: `matches`, `next_cursor`, `source`; match rows include `assists`, `deaths`, `game_mode_id`, `game_play_mode_id`, `hero_id`, `is_mvp`, `is_svp`, `is_win`, `kills`, `map_id`, `match_uid`, `os`, `placement`, `platform`, `rank_level`, `rank_score`, `score_change`, `season`, `team_score`, `timestamp`, `winner_camp`. A sample profile marked its history private; a private profile can return no visible match rows. |
22
22
  | Player teammates | `POST /player/teammates` | `uid`, optional `season`, `mode` | Array rows: `games`, `icon`, `losses`, `name`, `teammate_uid`, `wins`. |
@@ -50,6 +50,41 @@ none of these, it returns `None`.
50
50
 
51
51
  ## Observed UI and routes
52
52
 
53
+ `player.stats.classes(season=...)` derives tank (Vanguard), support (Strategist),
54
+ and DPS (Duelist) totals from `/player/stats/heroes`; it does not call a class
55
+ endpoint. It sums games/wins/losses separately for competitive and quickplay,
56
+ and MVP/SVP counts when every included row supplies them. Win rate is calculated
57
+ from summed wins and losses. Unknown classes and incomplete win/loss rows appear
58
+ in `excluded`. Hero switching can count a single match in multiple hero records,
59
+ so class totals are participation counts rather than distinct matches.
60
+
61
+ The roster at `/stats` on 2026-09-30 identifies Deadpool variants as 10571
62
+ (tank), 10572 (DPS), and 10573 (support), Daredevil as 1055, and Angela as 1056.
63
+
64
+ ### Character playtime investigation (2026-09-30)
65
+
66
+ Using Camoufox (the package's optional browser dependency), the public GS-
67
+ profile (`1970288503`) was inspected in Stats > Heroes for the current season
68
+ and All Seasons. Expanded hero cards showed games, wins/losses, win rate, KDA,
69
+ accuracy, MVP/SVP counts, combat averages, and team-ups. Neither the UI nor the
70
+ captured `/player/heroes` and `/player/stats/heroes` responses supplied cumulative
71
+ hero playtime. The all-seasons stats request returned 49 hero rows, including
72
+ Deadpool's distinct role variants.
73
+
74
+ Match details do expose per-character time in seconds at
75
+ `teams[].players[].heroes[].play_time`. For match
76
+ `5518155_1790655030_1272083_11001_11`, the match duration was 584 seconds and GS-'s
77
+ Loki entry contained `play_time=583.8668914120644`. Hovering the hero portrait
78
+ displayed `Loki` and `9:43`. The observed frontend formatter takes
79
+ `floor(play_time / 60)` minutes and `floor(play_time % 60)` seconds.
80
+
81
+ Hours for a character over a supplied set of matches can be calculated by
82
+ summing that player's matching hero entries and dividing by 3600. Include all
83
+ matching entries (a hero can have multiple usage segments), and deduplicate
84
+ match IDs. This produces time for the retrieved matches, not a guaranteed
85
+ lifetime or season total: history and detail availability may be incomplete.
86
+ No automatic bulk match fetch was added as part of class statistics.
87
+
53
88
  | UI route | What the UI exposes |
54
89
  | --- | --- |
55
90
  | `/` | Search, top leaderboard, favorites, top heroes, and top team-ups. |
@@ -15,7 +15,7 @@ as public, stable methods. Keep requests respectful and conservative.
15
15
  ## Current package
16
16
 
17
17
  - Distribution: `rivalsdata-api`; import: `rivalsdata`.
18
- - Version: `1.1.0`.
18
+ - Version: `1.2.0`.
19
19
  - Python `>=3.10`, Hatchling build, `src/` layout.
20
20
  - Runtime HTTP dependency: `curl-cffi`; optional browser fallback: Camoufox.
21
21
  - Public entry point: `RivalsDataClient`.
@@ -45,7 +45,7 @@ copy of the full response.
45
45
  Lazy subresources include `player.heroes.fetch(...)`, `player.matches.fetch(...)`,
46
46
  `player.teammates.fetch(...)`, `player.crosshairs.fetch()`,
47
47
  `player.proficiency.fetch()`, `player.punishments.fetch()`,
48
- `player.name_history.fetch()`, and `player.stats.heroes/maps/bans(...)`.
48
+ `player.name_history.fetch()`, and `player.stats.heroes/maps/bans/classes(...)`.
49
49
  Client-wide resources include `client.leaderboards`, `client.heroes`,
50
50
  `client.team_ups`, `client.insights`, `client.factions`, and `client.matches`.
51
51
  `client.profiles` and `client.favorites` have typed read methods. The profile
@@ -54,6 +54,21 @@ Favorites requires numeric UIDs and returns player summary rows.
54
54
  Rows offer `.win_rate` and `.winrate` integer-percent access when data supports
55
55
  it; all original data remains in mapping access.
56
56
 
57
+ `player.stats.classes(season=...)` groups observed hero IDs into tank, support,
58
+ and DPS. It sums games, wins, losses, and available MVP/SVP counts separately
59
+ for competitive and quickplay, with win rates calculated from summed wins and
60
+ losses. The response has typed `classes` rows and an `excluded` list for unknown
61
+ roles or incomplete win/loss data. MCP exposes it through `get_player_stats`
62
+ with `category="classes"`. Hero switching means totals count participation,
63
+ not unique matches. Role IDs and the corrected Angela/Daredevil IDs were
64
+ verified against RivalsData's roster on 2026-09-30; Deadpool has separate role
65
+ variants (10571/10572/10573), while generic 1057 remains unclassified.
66
+
67
+ Camoufox inspection found no cumulative hero hours in current-season or
68
+ all-seasons player stats. Match hero usage does include `play_time` in seconds.
69
+ See `docs/API.md` for the recorded investigation; no playtime aggregation
70
+ method was added.
71
+
57
72
  ## Observed API details
58
73
 
59
74
  The major UI areas inspected are home, player profile/tabs, global leaderboard,
@@ -103,7 +118,7 @@ Use a virtual environment. Install editable dependencies with
103
118
  fallback work. The global host Python has unrelated package conflicts; don't
104
119
  change global dependencies to resolve those.
105
120
 
106
- No automated test suite is currently tracked. Don't add network-dependent
107
- checks to routine development; use mocked tests when the project owner asks for
108
- tests. Ruff and wheel builds are available for code checks. Commit coherent
121
+ Mocked tests in `tests/` cover hero lookups, typed responses, and calculated
122
+ class stats. Keep network-dependent checks opt-in. Run pytest, Ruff, and package
123
+ builds for code checks. Commit coherent
109
124
  milestones and push to `origin` when explicitly requested by the project owner.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rivalsdata-api"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "Python client and MCP server for public Marvel Rivals stats from RivalsData"
9
9
  keywords = ["marvel-rivals", "rivalsdata", "game-stats", "api-client", "mcp"]
10
10
  readme = "README.md"
@@ -7,10 +7,13 @@ from .exceptions import (
7
7
  RivalsDataError,
8
8
  RivalsDataHTTPError,
9
9
  )
10
- from .hero_ids import HERO_NAMES, hero_id, hero_name
10
+ from .hero_ids import HERO_CLASSES, HERO_NAMES, hero_class, hero_id, hero_name
11
11
  from .models import (
12
12
  BanRecord,
13
13
  Character,
14
+ ClassModeStats,
15
+ ClassStatsRecord,
16
+ ClassStatsResponse,
14
17
  CombatAverages,
15
18
  CommBanHero,
16
19
  CommBanInsights,
@@ -74,9 +77,13 @@ from .models import (
74
77
  )
75
78
 
76
79
  __all__ = [
80
+ "HERO_CLASSES",
77
81
  "HERO_NAMES",
78
82
  "BanRecord",
79
83
  "Character",
84
+ "ClassModeStats",
85
+ "ClassStatsRecord",
86
+ "ClassStatsResponse",
80
87
  "CloudflareError",
81
88
  "CombatAverages",
82
89
  "CommBanHero",
@@ -142,7 +149,8 @@ __all__ = [
142
149
  "Top500Season",
143
150
  "XPPage",
144
151
  "XPRecord",
152
+ "hero_class",
145
153
  "hero_id",
146
154
  "hero_name",
147
155
  ]
148
- __version__ = "1.1.0"
156
+ __version__ = "1.2.0"
@@ -46,9 +46,12 @@ HERO_NAMES: dict[str, str] = {
46
46
  "1052": "Iron Fist",
47
47
  "1053": "Emma Frost",
48
48
  "1054": "Phoenix",
49
- "1055": "Angela",
50
- "1056": "Daredevil",
49
+ "1055": "Daredevil",
50
+ "1056": "Angela",
51
51
  "1057": "Deadpool",
52
+ "10571": "Tankpool",
53
+ "10572": "DPSpool",
54
+ "10573": "Stratpool",
52
55
  "1058": "Gambit",
53
56
  "1059": "Elsa Bloodstone",
54
57
  "1060": "White Fox",
@@ -61,6 +64,30 @@ HERO_NAMES: dict[str, str] = {
61
64
  "1067": "Gorr the God Butcher",
62
65
  }
63
66
 
67
+ # Observed on https://rivalsdata.com/stats on 2026-09-30. The unspecialized
68
+ # Deadpool ID (1057) has no single role and is deliberately left unresolved.
69
+ HERO_CLASSES: dict[str, str] = {
70
+ **dict.fromkeys((
71
+ "1011", "1018", "1022", "1027", "1035", "1037", "1039", "1042",
72
+ "1051", "1053", "1056", "1062", "1065", "1066", "10571",
73
+ ), "tank"),
74
+ **dict.fromkeys((
75
+ "1016", "1020", "1023", "1025", "1028", "1031", "1046", "1047",
76
+ "1050", "1058", "1060", "1064", "10573",
77
+ ), "support"),
78
+ **dict.fromkeys((
79
+ "1014", "1015", "1017", "1021", "1024", "1026", "1029", "1030",
80
+ "1032", "1033", "1034", "1036", "1038", "1040", "1041", "1043",
81
+ "1044", "1045", "1048", "1049", "1052", "1054", "1055", "1059",
82
+ "1061", "1063", "1067", "10572",
83
+ ), "dps"),
84
+ }
85
+
86
+
87
+ def hero_class(hero_id: object) -> str | None:
88
+ """Return tank, support, or dps for a known role-specific hero ID."""
89
+ return HERO_CLASSES.get(str(hero_id))
90
+
64
91
 
65
92
  def hero_name(hero_id: object) -> str | None:
66
93
  """Resolve a known playable hero ID, preserving unknown IDs for callers."""