rivalsdata-api 1.0.0__tar.gz → 1.1.1__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.
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/PKG-INFO +59 -18
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/README.md +57 -17
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/docs/API.md +19 -17
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/docs/PROJECT_CONTEXT.md +8 -6
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/pyproject.toml +2 -5
- rivalsdata_api-1.1.1/src/rivalsdata/__init__.py +148 -0
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/src/rivalsdata/client.py +29 -7
- rivalsdata_api-1.1.1/src/rivalsdata/hero_ids.py +80 -0
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/src/rivalsdata/mcp_server.py +116 -37
- rivalsdata_api-1.1.1/src/rivalsdata/models.py +966 -0
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/src/rivalsdata/resources.py +116 -70
- rivalsdata_api-1.1.1/tests/test_hero_names.py +31 -0
- rivalsdata_api-1.1.1/tests/test_typed_responses.py +90 -0
- rivalsdata_api-1.0.0/src/rivalsdata/__init__.py +0 -22
- rivalsdata_api-1.0.0/src/rivalsdata/models.py +0 -172
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/.github/workflows/publish-pypi.yml +0 -0
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/.gitignore +0 -0
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/CONTRIBUTING.md +0 -0
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/LICENSE +0 -0
- {rivalsdata_api-1.0.0 → rivalsdata_api-1.1.1}/src/rivalsdata/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rivalsdata-api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
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
|
|
@@ -31,6 +31,7 @@ Requires-Dist: ruff>=0.11; extra == 'dev'
|
|
|
31
31
|
Provides-Extra: mcp
|
|
32
32
|
Requires-Dist: mcp-ui-server>=1.0.0; extra == 'mcp'
|
|
33
33
|
Requires-Dist: mcp<2,>=1.12; extra == 'mcp'
|
|
34
|
+
Requires-Dist: uvicorn>=0.30; extra == 'mcp'
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
|
|
36
37
|
# rivalsdata-api
|
|
@@ -55,7 +56,10 @@ by `python -m camoufox fetch`.
|
|
|
55
56
|
## Quick start
|
|
56
57
|
|
|
57
58
|
```python
|
|
58
|
-
from rivalsdata import RivalsDataClient
|
|
59
|
+
from rivalsdata import RivalsDataClient, hero_id, hero_name
|
|
60
|
+
|
|
61
|
+
print(hero_name(1016)) # Loki
|
|
62
|
+
print(hero_id("Loki")) # 1016
|
|
59
63
|
|
|
60
64
|
with RivalsDataClient() as rd:
|
|
61
65
|
player = rd.get_player("GS-") # numeric UID works too
|
|
@@ -63,6 +67,7 @@ with RivalsDataClient() as rd:
|
|
|
63
67
|
|
|
64
68
|
# Player profile sections are lazy resource managers.
|
|
65
69
|
hero_season = player.heroes.fetch(season=20)
|
|
70
|
+
all_hero_seasons = player.heroes.fetch(season="all")
|
|
66
71
|
map_stats = player.stats.maps(season=20)
|
|
67
72
|
match_page = player.matches.fetch(season=20)
|
|
68
73
|
|
|
@@ -91,22 +96,37 @@ python -m pip install 'rivalsdata-api[mcp]'
|
|
|
91
96
|
The server exposes read-only tools for player search and profiles, a player's
|
|
92
97
|
current live match (when they are in one), match history, player stats,
|
|
93
98
|
leaderboards, heroes, team-ups, public insights, matches, and factions. The
|
|
94
|
-
`show_player_dashboard` tool
|
|
95
|
-
competitive record
|
|
96
|
-
|
|
99
|
+
`show_player_dashboard` tool returns an MCP-UI player card with rank and
|
|
100
|
+
competitive record plus one optional data section per call: current match
|
|
101
|
+
roster, hero win-rate chart, or recent match form with K/D/A. This keeps each
|
|
102
|
+
dashboard pull to the profile plus at most one additional endpoint. It supports
|
|
103
|
+
local stdio for Claude Desktop
|
|
97
104
|
and Streamable HTTP for remote MCP clients such as ChatGPT. Data comes from
|
|
98
105
|
RivalsData's undocumented API and may change; profile match history can be
|
|
99
106
|
private.
|
|
100
107
|
|
|
108
|
+
Known `hero_id` and `top_hero_id` fields in MCP results include corresponding
|
|
109
|
+
`hero_name` and `top_hero_name` fields. The `resolve_hero` tool accepts either
|
|
110
|
+
a hero name or numeric ID. The pip package also exports `hero_name(id)` and
|
|
111
|
+
`hero_id(name)`; returned `DataModel` rows provide `.hero_name` and
|
|
112
|
+
`.top_hero_name` conveniences. Those resolved fields are included in mapping
|
|
113
|
+
iteration and `.to_dict()` output to simplify serialization; `.raw` remains the
|
|
114
|
+
untouched source payload.
|
|
115
|
+
|
|
101
116
|
### How the MCP UI works
|
|
102
117
|
|
|
103
118
|
`show_player_dashboard` fetches current data, then returns an HTML UI resource
|
|
104
|
-
alongside the tool result.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
alongside the tool result. It advertises the dashboard through
|
|
120
|
+
`_meta.ui.resourceUri`, uses the `text/html;profile=mcp-app` resource MIME type,
|
|
121
|
+
and registers that URI for `resources/read` so the host can actually load the
|
|
122
|
+
app frame. The tool result carries the rendered dashboard as structured content
|
|
123
|
+
for the app frame and an embedded HTML resource for older MCP-UI clients. It
|
|
124
|
+
also includes `openai/outputTemplate` as a ChatGPT compatibility alias. Hosts
|
|
125
|
+
without UI support still receive a text result and can use the regular MCP
|
|
126
|
+
tools. The dashboard is a snapshot from the time the tool runs; ask for it
|
|
127
|
+
again to refresh.
|
|
128
|
+
The `section` argument defaults to `live_match`; use `hero_form` or
|
|
129
|
+
`recent_matches` in separate calls when you need those views.
|
|
110
130
|
|
|
111
131
|
### Claude Desktop (local)
|
|
112
132
|
|
|
@@ -133,20 +153,41 @@ after saving the configuration.
|
|
|
133
153
|
Run the server on a host reachable over HTTPS:
|
|
134
154
|
|
|
135
155
|
```console
|
|
136
|
-
rivalsdata
|
|
156
|
+
uvicorn rivalsdata.mcp_server:app --host 0.0.0.0 --port 8000
|
|
137
157
|
```
|
|
138
158
|
|
|
139
159
|
The MCP endpoint is `/mcp` (for example, `https://your-host.example/mcp`). Add
|
|
140
160
|
that endpoint through the client's custom/remote MCP connector settings. The
|
|
141
161
|
server does not implement authentication; put it behind an authenticated
|
|
142
162
|
HTTPS gateway before exposing it publicly. For local development, bind to
|
|
143
|
-
`127.0.0.1` instead.
|
|
163
|
+
`127.0.0.1` instead. The `app` is the MCP SDK's Streamable HTTP ASGI
|
|
164
|
+
application; Uvicorn manages its lifespan and session manager.
|
|
165
|
+
|
|
166
|
+
Every implemented response route now has named endpoint models and row models
|
|
167
|
+
with annotations for fields observed in the API inventory. This includes
|
|
168
|
+
`Player`, `Match`, `MatchHistory`, `MatchTeam`, `MatchPlayer`, `Character`,
|
|
169
|
+
`ProficiencyResponse`, `LeaderboardResponse`, `PunishmentsPage`, `XPPage`,
|
|
170
|
+
`Top500Response`, and typed teammate, crosshair, stats, faction, and insight
|
|
171
|
+
records. For example, `rd.matches.get(match_id)` returns a `Match`,
|
|
172
|
+
`player.matches.fetch()` returns a `MatchHistory`, and
|
|
173
|
+
`player.proficiency.fetch()` returns a `ProficiencyResponse`. Nested match
|
|
174
|
+
teams and participants are converted to `MatchTeam` and `MatchPlayer`; embedded
|
|
175
|
+
character records use `Character`. Models support mapping access
|
|
176
|
+
(`player["level"]`) and attribute access (`player.level`). Unknown upstream
|
|
177
|
+
fields are still preserved and available through `.raw`; endpoint schemas
|
|
178
|
+
that have not been observed completely are annotated only for known fields.
|
|
144
179
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
180
|
+
```python
|
|
181
|
+
with RivalsDataClient() as rd:
|
|
182
|
+
player = rd.get_player(1970288503) # Player
|
|
183
|
+
proficiency = player.proficiency.fetch() # ProficiencyResponse
|
|
184
|
+
account = next(iter(proficiency.accounts.values())) # Proficiency
|
|
185
|
+
hero = account.hero_proficiency_infos["1011"] # HeroProficiency
|
|
186
|
+
print(hero.proficiency_level, hero.proficiency_point)
|
|
187
|
+
|
|
188
|
+
tier_list = rd.heroes.tier_list() # TierListResponse
|
|
189
|
+
print(tier_list.heroes[0].hero_id) # Character
|
|
190
|
+
```
|
|
150
191
|
|
|
151
192
|
## Public resources
|
|
152
193
|
|
|
@@ -20,7 +20,10 @@ by `python -m camoufox fetch`.
|
|
|
20
20
|
## Quick start
|
|
21
21
|
|
|
22
22
|
```python
|
|
23
|
-
from rivalsdata import RivalsDataClient
|
|
23
|
+
from rivalsdata import RivalsDataClient, hero_id, hero_name
|
|
24
|
+
|
|
25
|
+
print(hero_name(1016)) # Loki
|
|
26
|
+
print(hero_id("Loki")) # 1016
|
|
24
27
|
|
|
25
28
|
with RivalsDataClient() as rd:
|
|
26
29
|
player = rd.get_player("GS-") # numeric UID works too
|
|
@@ -28,6 +31,7 @@ with RivalsDataClient() as rd:
|
|
|
28
31
|
|
|
29
32
|
# Player profile sections are lazy resource managers.
|
|
30
33
|
hero_season = player.heroes.fetch(season=20)
|
|
34
|
+
all_hero_seasons = player.heroes.fetch(season="all")
|
|
31
35
|
map_stats = player.stats.maps(season=20)
|
|
32
36
|
match_page = player.matches.fetch(season=20)
|
|
33
37
|
|
|
@@ -56,22 +60,37 @@ python -m pip install 'rivalsdata-api[mcp]'
|
|
|
56
60
|
The server exposes read-only tools for player search and profiles, a player's
|
|
57
61
|
current live match (when they are in one), match history, player stats,
|
|
58
62
|
leaderboards, heroes, team-ups, public insights, matches, and factions. The
|
|
59
|
-
`show_player_dashboard` tool
|
|
60
|
-
competitive record
|
|
61
|
-
|
|
63
|
+
`show_player_dashboard` tool returns an MCP-UI player card with rank and
|
|
64
|
+
competitive record plus one optional data section per call: current match
|
|
65
|
+
roster, hero win-rate chart, or recent match form with K/D/A. This keeps each
|
|
66
|
+
dashboard pull to the profile plus at most one additional endpoint. It supports
|
|
67
|
+
local stdio for Claude Desktop
|
|
62
68
|
and Streamable HTTP for remote MCP clients such as ChatGPT. Data comes from
|
|
63
69
|
RivalsData's undocumented API and may change; profile match history can be
|
|
64
70
|
private.
|
|
65
71
|
|
|
72
|
+
Known `hero_id` and `top_hero_id` fields in MCP results include corresponding
|
|
73
|
+
`hero_name` and `top_hero_name` fields. The `resolve_hero` tool accepts either
|
|
74
|
+
a hero name or numeric ID. The pip package also exports `hero_name(id)` and
|
|
75
|
+
`hero_id(name)`; returned `DataModel` rows provide `.hero_name` and
|
|
76
|
+
`.top_hero_name` conveniences. Those resolved fields are included in mapping
|
|
77
|
+
iteration and `.to_dict()` output to simplify serialization; `.raw` remains the
|
|
78
|
+
untouched source payload.
|
|
79
|
+
|
|
66
80
|
### How the MCP UI works
|
|
67
81
|
|
|
68
82
|
`show_player_dashboard` fetches current data, then returns an HTML UI resource
|
|
69
|
-
alongside the tool result.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
alongside the tool result. It advertises the dashboard through
|
|
84
|
+
`_meta.ui.resourceUri`, uses the `text/html;profile=mcp-app` resource MIME type,
|
|
85
|
+
and registers that URI for `resources/read` so the host can actually load the
|
|
86
|
+
app frame. The tool result carries the rendered dashboard as structured content
|
|
87
|
+
for the app frame and an embedded HTML resource for older MCP-UI clients. It
|
|
88
|
+
also includes `openai/outputTemplate` as a ChatGPT compatibility alias. Hosts
|
|
89
|
+
without UI support still receive a text result and can use the regular MCP
|
|
90
|
+
tools. The dashboard is a snapshot from the time the tool runs; ask for it
|
|
91
|
+
again to refresh.
|
|
92
|
+
The `section` argument defaults to `live_match`; use `hero_form` or
|
|
93
|
+
`recent_matches` in separate calls when you need those views.
|
|
75
94
|
|
|
76
95
|
### Claude Desktop (local)
|
|
77
96
|
|
|
@@ -98,20 +117,41 @@ after saving the configuration.
|
|
|
98
117
|
Run the server on a host reachable over HTTPS:
|
|
99
118
|
|
|
100
119
|
```console
|
|
101
|
-
rivalsdata
|
|
120
|
+
uvicorn rivalsdata.mcp_server:app --host 0.0.0.0 --port 8000
|
|
102
121
|
```
|
|
103
122
|
|
|
104
123
|
The MCP endpoint is `/mcp` (for example, `https://your-host.example/mcp`). Add
|
|
105
124
|
that endpoint through the client's custom/remote MCP connector settings. The
|
|
106
125
|
server does not implement authentication; put it behind an authenticated
|
|
107
126
|
HTTPS gateway before exposing it publicly. For local development, bind to
|
|
108
|
-
`127.0.0.1` instead.
|
|
127
|
+
`127.0.0.1` instead. The `app` is the MCP SDK's Streamable HTTP ASGI
|
|
128
|
+
application; Uvicorn manages its lifespan and session manager.
|
|
129
|
+
|
|
130
|
+
Every implemented response route now has named endpoint models and row models
|
|
131
|
+
with annotations for fields observed in the API inventory. This includes
|
|
132
|
+
`Player`, `Match`, `MatchHistory`, `MatchTeam`, `MatchPlayer`, `Character`,
|
|
133
|
+
`ProficiencyResponse`, `LeaderboardResponse`, `PunishmentsPage`, `XPPage`,
|
|
134
|
+
`Top500Response`, and typed teammate, crosshair, stats, faction, and insight
|
|
135
|
+
records. For example, `rd.matches.get(match_id)` returns a `Match`,
|
|
136
|
+
`player.matches.fetch()` returns a `MatchHistory`, and
|
|
137
|
+
`player.proficiency.fetch()` returns a `ProficiencyResponse`. Nested match
|
|
138
|
+
teams and participants are converted to `MatchTeam` and `MatchPlayer`; embedded
|
|
139
|
+
character records use `Character`. Models support mapping access
|
|
140
|
+
(`player["level"]`) and attribute access (`player.level`). Unknown upstream
|
|
141
|
+
fields are still preserved and available through `.raw`; endpoint schemas
|
|
142
|
+
that have not been observed completely are annotated only for known fields.
|
|
109
143
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
144
|
+
```python
|
|
145
|
+
with RivalsDataClient() as rd:
|
|
146
|
+
player = rd.get_player(1970288503) # Player
|
|
147
|
+
proficiency = player.proficiency.fetch() # ProficiencyResponse
|
|
148
|
+
account = next(iter(proficiency.accounts.values())) # Proficiency
|
|
149
|
+
hero = account.hero_proficiency_infos["1011"] # HeroProficiency
|
|
150
|
+
print(hero.proficiency_level, hero.proficiency_point)
|
|
151
|
+
|
|
152
|
+
tier_list = rd.heroes.tier_list() # TierListResponse
|
|
153
|
+
print(tier_list.heroes[0].hero_id) # Character
|
|
154
|
+
```
|
|
115
155
|
|
|
116
156
|
## Public resources
|
|
117
157
|
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
This is an observed inventory of the public web client's API, gathered by
|
|
4
4
|
reviewing the RivalsData UI and its browser requests on 2026-09-29. The upstream
|
|
5
5
|
API is undocumented and can change. Field sets below are examples from live
|
|
6
|
-
responses, not schemas guaranteed by RivalsData.
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
responses, not schemas guaranteed by RivalsData. The client converts every
|
|
7
|
+
implemented route to endpoint-specific model and row classes (such as `Match`,
|
|
8
|
+
`Character`, `PlayerSummary`, `ProficiencyResponse`, and `PunishmentsPage`).
|
|
9
|
+
Known fields are annotated; unknown keys remain accessible through `DataModel`
|
|
10
|
+
and `.raw` so upstream additions are not discarded.
|
|
9
11
|
|
|
10
12
|
## Implemented public read endpoints
|
|
11
13
|
|
|
@@ -14,31 +16,31 @@ keys.
|
|
|
14
16
|
| Search | `POST /players/search` | `{"name": name}` | Search rows; `aid` may contain the numeric UID after the final `_`. |
|
|
15
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`. |
|
|
16
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)`. |
|
|
17
|
-
| 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. |
|
|
18
20
|
| Player crosshairs | `POST /player/crosshairs` | `uid` | Array rows: `crosshair`, `uses`. |
|
|
19
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. |
|
|
20
22
|
| Player teammates | `POST /player/teammates` | `uid`, optional `season`, `mode` | Array rows: `games`, `icon`, `losses`, `name`, `teammate_uid`, `wins`. |
|
|
21
|
-
| Player proficiency | `POST /player/proficiency` | `uid` | Object keyed by account id (sample: `11001_{uid}`);
|
|
22
|
-
| Player hero stats | `POST /player/stats/heroes` | `uid`, optional `season` | Array rows: `competitive`, `hero_id`, `quickplay`, `rank`. |
|
|
23
|
-
| Player map stats | `POST /player/stats/maps` | `uid`, optional `season` | Array
|
|
24
|
-
| Player ban stats | `POST /player/stats/bans` | `uid`, optional `season` | Array
|
|
25
|
-
| Player punishments | `POST /player/punishments` | `uid` | Object keys
|
|
23
|
+
| Player proficiency | `POST /player/proficiency` | `uid` | Object keyed by account id (sample: `11001_{uid}`); each account has `hero_proficiency_infos` keyed by hero id, with `proficiency_level` and `proficiency_point`. |
|
|
24
|
+
| Player hero stats | `POST /player/stats/heroes` | `uid`, optional `season` | Array rows: `competitive`, `hero_id`, `quickplay`, `rank`. Each mode includes games, wins/losses, KDA, MVP/SVP counts, accuracy, and `per_10`/`per_game` combat averages. |
|
|
25
|
+
| Player map stats | `POST /player/stats/maps` | `uid`, optional `season` | Array rows: `map`, `competitive`, `quickplay`; each mode has games, wins, losses, winrate. |
|
|
26
|
+
| Player ban stats | `POST /player/stats/bans` | `uid`, optional `season` | Array rows: `hero_id`, `matches`, `wins`, `losses`, `winrate`. |
|
|
27
|
+
| Player punishments | `POST /player/punishments` | `uid` | Object keys: `chat`, `login`, `rank`; non-null entries include `expire`, `name`, `reason`, `time`, `uid`. |
|
|
26
28
|
| Player name history | `POST /player/name-history` | `uid` | Array rows: `first_seen`, `name`. |
|
|
27
29
|
| Global leaderboard | `GET /leaderboards` | `limit`, optional `skip`, `season`, `os` (Python `platform`) | Object keys observed: `count`, `players`, `updated_at`; row keys: `heroes`, `icon`, `losses`, `name`, `os`, `position`, `rank_level`, `rank_score`, `season`, `status`, `uid`, `wins`. |
|
|
28
|
-
| Hero tier list | `GET /stats/tierlist` | `platform`, `rank` |
|
|
29
|
-
| Hero detail | `GET /stats/heroes/{hero_id}` | Path parameter |
|
|
30
|
-
| Hero trend/meta | `GET /stats/meta/{hero_id}` | `range`
|
|
31
|
-
| Hero leaderboard | `GET /stats/leaderboards` | `hero` plus caller-supplied query filters |
|
|
32
|
-
| Team-up stats | `GET /stats/teamups` | `platform`, `rank`, optional `hero` |
|
|
30
|
+
| Hero tier list | `GET /stats/tierlist` | `platform`, `rank` | Object: `last_update`, `heroes`; rows include hero id, picks/bans, total games, winrate, pick rate, ban rate, and score. |
|
|
31
|
+
| Hero detail | `GET /stats/heroes/{hero_id}` | Path parameter | Object: `hero`, `last_update`, `season`; `hero` contains aggregate per-10 stats and pick/ban rates. |
|
|
32
|
+
| Hero trend/meta | `GET /stats/meta/{hero_id}` | `range` as `30d`, `90d`, or `180d` | Object: `hero_id`, `last_update`, `points`, `range`, `window_days`; point rows include timestamp, games, pick/ban rates, and winrate without mirror matches. |
|
|
33
|
+
| Hero leaderboard | `GET /stats/leaderboards` | `hero` plus caller-supplied query filters | Object: `last_update`, `players`; rows include combat averages, placement, score, rank, and wins/losses. |
|
|
34
|
+
| Team-up stats | `GET /stats/teamups` | `platform`, `rank`, optional `hero` | Object: `last_update`, `heroes`, where `heroes` maps hero ids to slot ids and rows with `bond_id`, `games`, `nm_winrate`, `pickrate`, `winrate`. |
|
|
33
35
|
| Punishments log | `GET /stats/punishments` | `kind`, optional `cursor` | Object: `last_update`, `next`, `results`; sample row keys: `expires_at`, `icon`, `issued_at`, `kind`, `name`, `peak_rank_level`, `peak_rank_score`, `rank`, `reason`, `uid`. |
|
|
34
36
|
| XP leaderboard | `GET /stats/xp` | Optional `cursor` | Object: `last_update`, `next`, `results`; sample row keys: `icon`, `name`, `rank`, `uid`, `xp`. |
|
|
35
37
|
| Top 500 finishes | `GET /stats/oaa` | `os` (Python `platform`) | Object: `count`, `last_update`, `os`, `players`; sample row keys: `avg_placement`, `avg_score`, `finishes`, `icon`, `name`, `seasons`, `uid`. |
|
|
36
38
|
| Hero comm-ban insight | `GET /stats/commbans` | `mode` (`all` or `competitive`) | Object: `heroes`, `last_update`, `mode`, `overall_pct`; hero rows: `ci95`, `hero_id`, `pct`, `qualifying_players`, `vs_avg`, `weighted_banned`, `weighted_players`. |
|
|
37
39
|
| Hero AFK insight | `GET /stats/leavers` | `mode` (`all` or `competitive`) | Object: `heroes`, `last_update`, `mode`, `overall_pct`; hero rows: `ci95`, `games`, `hero_id`, `leaves`, `leaves_per_player`, `pct`, `players`, `vs_avg`. |
|
|
38
|
-
| Faction details | `GET /faction/{faction_id}` | Path parameter | Faction
|
|
40
|
+
| Faction details | `GET /faction/{faction_id}` | Path parameter | Faction `captain`, `description`, `members`, `name`, `region`, `results`, `tag`, `type`; member records contain account id, config/rank, game status, and name. |
|
|
39
41
|
| Match details | `POST /match` | `{"match_id": "..."}` | Object keys observed: `match_uid`, `replay_id`, `winner_camp`, `duration_seconds`, `map_id`, `game_mode_id`, `game_play_mode_id`, `platform`, `timestamp`, `draft`, `teams`; team player rows include combat stats and per-hero usage. |
|
|
40
|
-
| Public profile card | `GET /profiles/{
|
|
41
|
-
| Favorites lookup | `POST /favorites` | `{"uids": [
|
|
42
|
+
| Public profile card | `GET /profiles/{uid}` | Numeric UID path parameter; `Profiles.get` resolves usernames | Object: `leaderboard_social`, `socials`, `uid`, `updated_at`. |
|
|
43
|
+
| Favorites lookup | `POST /favorites` | `{"uids": [numeric_uid, ...]}` | Array of public player summaries with `aid`, `config_server`, `games`, `name`, and `status`. |
|
|
42
44
|
|
|
43
45
|
The client exposes these read resources through `RivalsDataClient` and `Player`;
|
|
44
46
|
see README examples and method docstrings. `DataModel.win_rate` returns an
|
|
@@ -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: `
|
|
18
|
+
- Version: `1.1.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`.
|
|
@@ -48,8 +48,9 @@ Lazy subresources include `player.heroes.fetch(...)`, `player.matches.fetch(...)
|
|
|
48
48
|
`player.name_history.fetch()`, and `player.stats.heroes/maps/bans(...)`.
|
|
49
49
|
Client-wide resources include `client.leaderboards`, `client.heroes`,
|
|
50
50
|
`client.team_ups`, `client.insights`, `client.factions`, and `client.matches`.
|
|
51
|
-
`client.profiles` and `client.favorites` have
|
|
52
|
-
|
|
51
|
+
`client.profiles` and `client.favorites` have typed read methods. The profile
|
|
52
|
+
endpoint takes a numeric UID; the wrapper can resolve a username first.
|
|
53
|
+
Favorites requires numeric UIDs and returns player summary rows.
|
|
53
54
|
Rows offer `.win_rate` and `.winrate` integer-percent access when data supports
|
|
54
55
|
it; all original data remains in mapping access.
|
|
55
56
|
|
|
@@ -70,8 +71,8 @@ detail pages. Main public request families are:
|
|
|
70
71
|
opened from the public GS- profile. The returned object contains replay id,
|
|
71
72
|
mode/map/time, draft picks/bans, both teams, players' combat stats, and hero
|
|
72
73
|
usage.
|
|
73
|
-
- `GET /profiles/{
|
|
74
|
-
|
|
74
|
+
- `GET /profiles/{uid}` returns profile social metadata and `POST /favorites`
|
|
75
|
+
returns public player summaries. Both were checked through the browser.
|
|
75
76
|
|
|
76
77
|
The hero detail Counters and Synergy tabs showed “Coming Soon” on inspection.
|
|
77
78
|
The Live Game tab did not expose data for the sampled player. Do not invent
|
|
@@ -84,7 +85,8 @@ endpoints can change account state and are intentionally not implemented by
|
|
|
84
85
|
this read-only package yet. `client.matches.get` uses the verified `match_id`
|
|
85
86
|
payload.
|
|
86
87
|
|
|
87
|
-
The
|
|
88
|
+
The hero meta endpoint requires `range=30d`, `90d`, or `180d`; bare integers
|
|
89
|
+
are rejected. The website currently shows Season 10 / season value 20 and OS `1` for PC on
|
|
88
90
|
the inspected UI. Treat those as site values, not permanent constants.
|
|
89
91
|
|
|
90
92
|
## Contributor workflow
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rivalsdata-api"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.1"
|
|
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"
|
|
@@ -34,12 +34,9 @@ Changelog = "https://github.com/GS-Rionnag/rivalsdata-api/releases"
|
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
|
36
36
|
browser = ["camoufox>=0.5.6"]
|
|
37
|
-
mcp = ["mcp>=1.12,<2", "mcp-ui-server>=1.0.0"]
|
|
37
|
+
mcp = ["mcp>=1.12,<2", "mcp-ui-server>=1.0.0", "uvicorn>=0.30"]
|
|
38
38
|
dev = ["build>=1.2", "pytest>=8", "ruff>=0.11"]
|
|
39
39
|
|
|
40
|
-
[project.scripts]
|
|
41
|
-
rivalsdata-mcp = "rivalsdata.mcp_server:main"
|
|
42
|
-
|
|
43
40
|
[tool.ruff]
|
|
44
41
|
line-length = 88
|
|
45
42
|
target-version = "py310"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Unofficial client for public RivalsData player data."""
|
|
2
|
+
|
|
3
|
+
from .client import RivalsDataClient
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
CloudflareError,
|
|
6
|
+
PlayerNotFoundError,
|
|
7
|
+
RivalsDataError,
|
|
8
|
+
RivalsDataHTTPError,
|
|
9
|
+
)
|
|
10
|
+
from .hero_ids import HERO_NAMES, hero_id, hero_name
|
|
11
|
+
from .models import (
|
|
12
|
+
BanRecord,
|
|
13
|
+
Character,
|
|
14
|
+
CombatAverages,
|
|
15
|
+
CommBanHero,
|
|
16
|
+
CommBanInsights,
|
|
17
|
+
CrosshairRecord,
|
|
18
|
+
DataModel,
|
|
19
|
+
DraftEntry,
|
|
20
|
+
Faction,
|
|
21
|
+
FactionConfig,
|
|
22
|
+
FactionGame,
|
|
23
|
+
FactionMember,
|
|
24
|
+
FactionRank,
|
|
25
|
+
FactionResult,
|
|
26
|
+
FactionSummary,
|
|
27
|
+
FavoritePlayer,
|
|
28
|
+
FavoritesResponse,
|
|
29
|
+
HeroDetail,
|
|
30
|
+
HeroInsights,
|
|
31
|
+
HeroLeaderboardPlayer,
|
|
32
|
+
HeroLeaderboardResponse,
|
|
33
|
+
HeroMeta,
|
|
34
|
+
HeroMetaPoint,
|
|
35
|
+
HeroModeStats,
|
|
36
|
+
HeroProficiency,
|
|
37
|
+
HeroStatsRecord,
|
|
38
|
+
LeaderboardPlayer,
|
|
39
|
+
LeaderboardResponse,
|
|
40
|
+
LeaverHero,
|
|
41
|
+
LeaverInsights,
|
|
42
|
+
LiveGame,
|
|
43
|
+
LiveGamePlayer,
|
|
44
|
+
MapModeStats,
|
|
45
|
+
MapRecord,
|
|
46
|
+
Match,
|
|
47
|
+
MatchHistory,
|
|
48
|
+
MatchPlayer,
|
|
49
|
+
MatchTeam,
|
|
50
|
+
NameHistoryRecord,
|
|
51
|
+
Player,
|
|
52
|
+
PlayerPunishments,
|
|
53
|
+
PlayerSearchResult,
|
|
54
|
+
PlayerStatus,
|
|
55
|
+
PlayerSummary,
|
|
56
|
+
Proficiency,
|
|
57
|
+
ProficiencyResponse,
|
|
58
|
+
ProfileCard,
|
|
59
|
+
PunishmentEntry,
|
|
60
|
+
PunishmentRecord,
|
|
61
|
+
PunishmentsPage,
|
|
62
|
+
RankRecord,
|
|
63
|
+
StatRecord,
|
|
64
|
+
Teammate,
|
|
65
|
+
TeamScore,
|
|
66
|
+
TeamUpRecord,
|
|
67
|
+
TeamUpsResponse,
|
|
68
|
+
TierListResponse,
|
|
69
|
+
Top500Player,
|
|
70
|
+
Top500Response,
|
|
71
|
+
Top500Season,
|
|
72
|
+
XPPage,
|
|
73
|
+
XPRecord,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"HERO_NAMES",
|
|
78
|
+
"BanRecord",
|
|
79
|
+
"Character",
|
|
80
|
+
"CloudflareError",
|
|
81
|
+
"CombatAverages",
|
|
82
|
+
"CommBanHero",
|
|
83
|
+
"CommBanInsights",
|
|
84
|
+
"CrosshairRecord",
|
|
85
|
+
"DataModel",
|
|
86
|
+
"DraftEntry",
|
|
87
|
+
"Faction",
|
|
88
|
+
"FactionConfig",
|
|
89
|
+
"FactionGame",
|
|
90
|
+
"FactionMember",
|
|
91
|
+
"FactionRank",
|
|
92
|
+
"FactionResult",
|
|
93
|
+
"FactionSummary",
|
|
94
|
+
"FavoritePlayer",
|
|
95
|
+
"FavoritesResponse",
|
|
96
|
+
"HeroDetail",
|
|
97
|
+
"HeroInsights",
|
|
98
|
+
"HeroLeaderboardPlayer",
|
|
99
|
+
"HeroLeaderboardResponse",
|
|
100
|
+
"HeroMeta",
|
|
101
|
+
"HeroMetaPoint",
|
|
102
|
+
"HeroModeStats",
|
|
103
|
+
"HeroProficiency",
|
|
104
|
+
"HeroStatsRecord",
|
|
105
|
+
"LeaderboardPlayer",
|
|
106
|
+
"LeaderboardResponse",
|
|
107
|
+
"LeaverHero",
|
|
108
|
+
"LeaverInsights",
|
|
109
|
+
"LiveGame",
|
|
110
|
+
"LiveGamePlayer",
|
|
111
|
+
"MapModeStats",
|
|
112
|
+
"MapRecord",
|
|
113
|
+
"Match",
|
|
114
|
+
"MatchHistory",
|
|
115
|
+
"MatchPlayer",
|
|
116
|
+
"MatchTeam",
|
|
117
|
+
"NameHistoryRecord",
|
|
118
|
+
"Player",
|
|
119
|
+
"PlayerNotFoundError",
|
|
120
|
+
"PlayerPunishments",
|
|
121
|
+
"PlayerSearchResult",
|
|
122
|
+
"PlayerStatus",
|
|
123
|
+
"PlayerSummary",
|
|
124
|
+
"Proficiency",
|
|
125
|
+
"ProficiencyResponse",
|
|
126
|
+
"ProfileCard",
|
|
127
|
+
"PunishmentEntry",
|
|
128
|
+
"PunishmentRecord",
|
|
129
|
+
"PunishmentsPage",
|
|
130
|
+
"RankRecord",
|
|
131
|
+
"RivalsDataClient",
|
|
132
|
+
"RivalsDataError",
|
|
133
|
+
"RivalsDataHTTPError",
|
|
134
|
+
"StatRecord",
|
|
135
|
+
"TeamScore",
|
|
136
|
+
"TeamUpRecord",
|
|
137
|
+
"TeamUpsResponse",
|
|
138
|
+
"Teammate",
|
|
139
|
+
"TierListResponse",
|
|
140
|
+
"Top500Player",
|
|
141
|
+
"Top500Response",
|
|
142
|
+
"Top500Season",
|
|
143
|
+
"XPPage",
|
|
144
|
+
"XPRecord",
|
|
145
|
+
"hero_id",
|
|
146
|
+
"hero_name",
|
|
147
|
+
]
|
|
148
|
+
__version__ = "1.1.0"
|
|
@@ -7,6 +7,7 @@ from the site's public player page and search box.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
|
+
import time
|
|
10
11
|
import unicodedata
|
|
11
12
|
from typing import Any
|
|
12
13
|
from urllib.parse import urlencode
|
|
@@ -14,7 +15,7 @@ from urllib.parse import urlencode
|
|
|
14
15
|
from curl_cffi import requests
|
|
15
16
|
|
|
16
17
|
from .exceptions import CloudflareError, PlayerNotFoundError, RivalsDataHTTPError
|
|
17
|
-
from .models import Player
|
|
18
|
+
from .models import Player, PlayerSearchResult
|
|
18
19
|
from .resources import (
|
|
19
20
|
Factions,
|
|
20
21
|
Favorites,
|
|
@@ -33,6 +34,15 @@ class RivalsDataClient:
|
|
|
33
34
|
base_url = "https://rivalsdata.com"
|
|
34
35
|
api_url = "https://api.rivalsdata.com"
|
|
35
36
|
|
|
37
|
+
leaderboards: Leaderboards
|
|
38
|
+
heroes: HeroStats
|
|
39
|
+
team_ups: TeamUps
|
|
40
|
+
insights: Insights
|
|
41
|
+
factions: Factions
|
|
42
|
+
profiles: Profiles
|
|
43
|
+
favorites: Favorites
|
|
44
|
+
matches: Matches
|
|
45
|
+
|
|
36
46
|
def __init__(
|
|
37
47
|
self,
|
|
38
48
|
*,
|
|
@@ -71,7 +81,19 @@ class RivalsDataClient:
|
|
|
71
81
|
def __exit__(self, *_: object) -> None:
|
|
72
82
|
self.close()
|
|
73
83
|
|
|
74
|
-
def
|
|
84
|
+
def _request_with_gateway_retries(
|
|
85
|
+
self, method: Any, path: str, **kwargs: Any
|
|
86
|
+
) -> Any:
|
|
87
|
+
"""Retry short-lived upstream gateway failures on read-only requests."""
|
|
88
|
+
url = f"{self.api_url}{path}"
|
|
89
|
+
for attempt in range(3):
|
|
90
|
+
response = method(url, timeout=self.timeout, **kwargs)
|
|
91
|
+
if response.status_code not in (502, 504) or attempt == 2:
|
|
92
|
+
break
|
|
93
|
+
time.sleep(0.25 * (attempt + 1))
|
|
94
|
+
return response
|
|
95
|
+
|
|
96
|
+
def resolve_player(self, username: str) -> PlayerSearchResult:
|
|
75
97
|
"""Search by username; add numeric uid to the source result."""
|
|
76
98
|
query = username.strip()
|
|
77
99
|
if not query:
|
|
@@ -95,7 +117,7 @@ class RivalsDataClient:
|
|
|
95
117
|
raise RivalsDataHTTPError(
|
|
96
118
|
"Search returned a player without a recognizable numeric UID"
|
|
97
119
|
)
|
|
98
|
-
return {**player, "uid": uid}
|
|
120
|
+
return PlayerSearchResult({**player, "uid": uid})
|
|
99
121
|
|
|
100
122
|
def get_player(self, uuid_or_username: str | int) -> Player:
|
|
101
123
|
"""Fetch a typed public profile by numeric UID or username.
|
|
@@ -120,8 +142,8 @@ class RivalsDataClient:
|
|
|
120
142
|
|
|
121
143
|
def _get_json(self, path: str, *, params: dict[str, Any] | None = None) -> Any:
|
|
122
144
|
query = {key: value for key, value in (params or {}).items() if value is not None}
|
|
123
|
-
response = self.
|
|
124
|
-
|
|
145
|
+
response = self._request_with_gateway_retries(
|
|
146
|
+
self.session.get, path, params=query
|
|
125
147
|
)
|
|
126
148
|
body = response.text
|
|
127
149
|
blocked = (
|
|
@@ -190,8 +212,8 @@ class RivalsDataClient:
|
|
|
190
212
|
) from exc
|
|
191
213
|
|
|
192
214
|
def _post_json(self, path: str, payload: dict[str, Any]) -> Any:
|
|
193
|
-
response = self.
|
|
194
|
-
|
|
215
|
+
response = self._request_with_gateway_retries(
|
|
216
|
+
self.session.post, path, json=payload
|
|
195
217
|
)
|
|
196
218
|
body = response.text
|
|
197
219
|
blocked = (
|