rivalsdata-api 1.0.0__py3-none-any.whl
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/__init__.py +22 -0
- rivalsdata/client.py +282 -0
- rivalsdata/exceptions.py +13 -0
- rivalsdata/mcp_server.py +444 -0
- rivalsdata/models.py +172 -0
- rivalsdata/resources.py +248 -0
- rivalsdata_api-1.0.0.dist-info/METADATA +189 -0
- rivalsdata_api-1.0.0.dist-info/RECORD +11 -0
- rivalsdata_api-1.0.0.dist-info/WHEEL +4 -0
- rivalsdata_api-1.0.0.dist-info/entry_points.txt +2 -0
- rivalsdata_api-1.0.0.dist-info/licenses/LICENSE +21 -0
rivalsdata/resources.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Resource managers for public non-player and player-section endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
from .models import DataModel, StatRecord
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _one(value: Any) -> Any:
|
|
12
|
+
return DataModel(value) if isinstance(value, dict) else value
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _many(value: Any) -> Any:
|
|
16
|
+
if isinstance(value, list):
|
|
17
|
+
return [StatRecord(row) if isinstance(row, dict) else row for row in value]
|
|
18
|
+
return _one(value)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Leaderboards:
|
|
22
|
+
"""Global player leaderboard resource (``GET /leaderboards``)."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, client: Any) -> None:
|
|
25
|
+
self._client = client
|
|
26
|
+
|
|
27
|
+
def fetch(
|
|
28
|
+
self, *, limit: int = 100, skip: int = 0, season: int | None = None,
|
|
29
|
+
platform: int | str | None = None,
|
|
30
|
+
) -> DataModel | list[Any]:
|
|
31
|
+
params: dict[str, Any] = {"limit": limit}
|
|
32
|
+
if skip:
|
|
33
|
+
params["skip"] = skip
|
|
34
|
+
if season is not None:
|
|
35
|
+
params["season"] = season
|
|
36
|
+
if platform is not None:
|
|
37
|
+
params["os"] = platform
|
|
38
|
+
return _one(self._client._get_json("/leaderboards", params=params))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class HeroStats:
|
|
42
|
+
"""Aggregate hero tier-list, detail, trend, and leaderboard data."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, client: Any) -> None:
|
|
45
|
+
self._client = client
|
|
46
|
+
|
|
47
|
+
def tier_list(
|
|
48
|
+
self, *, platform: int | str = 1, rank: str = "grandmaster_plus"
|
|
49
|
+
) -> Any:
|
|
50
|
+
return _many(self._client._get_json(
|
|
51
|
+
"/stats/tierlist", params={"platform": platform, "rank": rank}
|
|
52
|
+
))
|
|
53
|
+
|
|
54
|
+
def get(self, hero_id: str | int) -> Any:
|
|
55
|
+
return _one(self._client._get_json(f"/stats/heroes/{quote(str(hero_id))}"))
|
|
56
|
+
|
|
57
|
+
def meta(self, hero_id: str | int, *, range: int = 90) -> Any:
|
|
58
|
+
if range not in (30, 90, 180):
|
|
59
|
+
raise ValueError("range must be 30, 90, or 180 days")
|
|
60
|
+
return _one(self._client._get_json(
|
|
61
|
+
f"/stats/meta/{quote(str(hero_id))}", params={"range": range}
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
def leaderboard(self, hero_id: str | int, **filters: Any) -> Any:
|
|
65
|
+
"""Fetch a hero-specific leaderboard; filters pass through as query args."""
|
|
66
|
+
params = {"hero": hero_id, **filters}
|
|
67
|
+
return _many(self._client._get_json("/stats/leaderboards", params=params))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TeamUps:
|
|
71
|
+
"""Global team-up analytics (``GET /stats/teamups``)."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, client: Any) -> None:
|
|
74
|
+
self._client = client
|
|
75
|
+
|
|
76
|
+
def fetch(self, *, platform: int | str = 1, rank: str = "grandmaster_plus") -> Any:
|
|
77
|
+
return _many(self._client._get_json(
|
|
78
|
+
"/stats/teamups", params={"platform": platform, "rank": rank}
|
|
79
|
+
))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Insights:
|
|
83
|
+
"""Public site-wide insights pages and their pagination metadata."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, client: Any) -> None:
|
|
86
|
+
self._client = client
|
|
87
|
+
|
|
88
|
+
def punishments(self, *, kind: str = "login", cursor: str | None = None) -> Any:
|
|
89
|
+
params: dict[str, Any] = {"kind": kind}
|
|
90
|
+
if cursor:
|
|
91
|
+
params["cursor"] = cursor
|
|
92
|
+
return _one(self._client._get_json("/stats/punishments", params=params))
|
|
93
|
+
|
|
94
|
+
def xp(self, *, cursor: str | None = None) -> Any:
|
|
95
|
+
params = {"cursor": cursor} if cursor else None
|
|
96
|
+
return _one(self._client._get_json("/stats/xp", params=params))
|
|
97
|
+
|
|
98
|
+
def top_500(self, *, platform: int | str = 1) -> Any:
|
|
99
|
+
return _one(self._client._get_json("/stats/oaa", params={"os": platform}))
|
|
100
|
+
|
|
101
|
+
def commbans(self, *, mode: str = "all") -> Any:
|
|
102
|
+
return _one(self._client._get_json("/stats/commbans", params={"mode": mode}))
|
|
103
|
+
|
|
104
|
+
def leavers(self, *, mode: str = "all") -> Any:
|
|
105
|
+
return _one(self._client._get_json("/stats/leavers", params={"mode": mode}))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Factions:
|
|
109
|
+
"""Faction pages (``GET /faction/{faction_id}``)."""
|
|
110
|
+
|
|
111
|
+
def __init__(self, client: Any) -> None:
|
|
112
|
+
self._client = client
|
|
113
|
+
|
|
114
|
+
def get(self, faction_id: str | int) -> Any:
|
|
115
|
+
return _one(self._client._get_json(f"/faction/{quote(str(faction_id))}"))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Profiles:
|
|
119
|
+
"""Public profile-card pages (response schema is not fully mapped)."""
|
|
120
|
+
|
|
121
|
+
def __init__(self, client: Any) -> None:
|
|
122
|
+
self._client = client
|
|
123
|
+
|
|
124
|
+
def get(self, username: str) -> Any:
|
|
125
|
+
if not username.strip():
|
|
126
|
+
raise ValueError("username must not be empty")
|
|
127
|
+
return _one(self._client._get_json(
|
|
128
|
+
f"/profiles/{quote(username.strip(), safe='')}"
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Favorites:
|
|
133
|
+
"""Fetch profile summaries for a supplied list of favorite UIDs."""
|
|
134
|
+
|
|
135
|
+
def __init__(self, client: Any) -> None:
|
|
136
|
+
self._client = client
|
|
137
|
+
|
|
138
|
+
def fetch(self, uids: list[str | int]) -> Any:
|
|
139
|
+
return _many(self._client._post_json("/favorites", {"uids": [str(uid) for uid in uids]}))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class PlayerResource:
|
|
143
|
+
def __init__(self, client: Any, uid: int) -> None:
|
|
144
|
+
self._client = client
|
|
145
|
+
self.uid = uid
|
|
146
|
+
|
|
147
|
+
def _post(self, path: str, **payload: Any) -> Any:
|
|
148
|
+
return self._client._post_json(path, {"uid": self.uid, **payload})
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class PlayerHeroes(PlayerResource):
|
|
152
|
+
"""Per-player hero summary rows from ``POST /player/heroes``."""
|
|
153
|
+
|
|
154
|
+
def fetch(self, *, season: int | None = None) -> Any:
|
|
155
|
+
payload = {"season": season} if season is not None else {}
|
|
156
|
+
return _many(self._post("/player/heroes", **payload))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class PlayerStats(PlayerResource):
|
|
160
|
+
"""Detailed per-player statistics tabs."""
|
|
161
|
+
|
|
162
|
+
def heroes(self, *, season: int | None = None) -> Any:
|
|
163
|
+
payload = {"season": season} if season is not None else {}
|
|
164
|
+
return _many(self._post("/player/stats/heroes", **payload))
|
|
165
|
+
|
|
166
|
+
def maps(self, *, season: int | None = None) -> Any:
|
|
167
|
+
payload = {"season": season} if season is not None else {}
|
|
168
|
+
return _many(self._post("/player/stats/maps", **payload))
|
|
169
|
+
|
|
170
|
+
def bans(self, *, season: int | None = None) -> Any:
|
|
171
|
+
payload = {"season": season} if season is not None else {}
|
|
172
|
+
return _many(self._post("/player/stats/bans", **payload))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class PlayerMatches(PlayerResource):
|
|
176
|
+
"""Player match-history pages."""
|
|
177
|
+
|
|
178
|
+
def fetch(
|
|
179
|
+
self, *, cursor: str | None = None, season: int | None = None,
|
|
180
|
+
mode: str | None = None, hero: str | int | None = None,
|
|
181
|
+
teammate: str | int | None = None, cached: bool = True,
|
|
182
|
+
) -> Any:
|
|
183
|
+
payload: dict[str, Any] = {"cursor": cursor}
|
|
184
|
+
for key, value in (("season", season), ("mode", mode), ("hero", hero), ("teammate", teammate)):
|
|
185
|
+
if value is not None:
|
|
186
|
+
payload[key] = value
|
|
187
|
+
path = "/player/matches/cached" if cached else "/player/matches"
|
|
188
|
+
return _one(self._post(path, **payload))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class PlayerLiveGame(PlayerResource):
|
|
192
|
+
"""Current live match data for a player whose profile status is in-game.
|
|
193
|
+
|
|
194
|
+
The site supplies the opaque match ID in the player's ``status.battle_id``
|
|
195
|
+
field. A fresh player profile is needed to discover the current match.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(self, client: Any, player_data: dict[str, Any]) -> None:
|
|
199
|
+
super().__init__(client, int(player_data["uid"]))
|
|
200
|
+
self._player_data = player_data
|
|
201
|
+
|
|
202
|
+
def fetch(self) -> DataModel | None:
|
|
203
|
+
"""Fetch the current live match, or return ``None`` if out of game."""
|
|
204
|
+
status = self._player_data.get("status")
|
|
205
|
+
if not isinstance(status, dict):
|
|
206
|
+
return None
|
|
207
|
+
match_id = status.get("battle_id")
|
|
208
|
+
if not match_id:
|
|
209
|
+
return None
|
|
210
|
+
return _one(self._client._post_json(
|
|
211
|
+
"/live", {"match_id": str(match_id), "uid": self.uid}
|
|
212
|
+
))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class PlayerTeammates(PlayerResource):
|
|
216
|
+
def fetch(self, *, season: int | None = None, mode: str | None = None) -> Any:
|
|
217
|
+
payload = {key: value for key, value in (("season", season), ("mode", mode)) if value is not None}
|
|
218
|
+
return _many(self._post("/player/teammates", **payload))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class PlayerCrosshairs(PlayerResource):
|
|
222
|
+
def fetch(self) -> Any:
|
|
223
|
+
return _many(self._post("/player/crosshairs"))
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class PlayerProficiency(PlayerResource):
|
|
227
|
+
def fetch(self) -> Any:
|
|
228
|
+
return _one(self._post("/player/proficiency"))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class PlayerPunishments(PlayerResource):
|
|
232
|
+
def fetch(self) -> Any:
|
|
233
|
+
return _one(self._post("/player/punishments"))
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class PlayerNameHistory(PlayerResource):
|
|
237
|
+
def fetch(self) -> Any:
|
|
238
|
+
return _many(self._post("/player/name-history"))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class Matches:
|
|
242
|
+
"""Match detail endpoint. The upstream match identifier format is opaque."""
|
|
243
|
+
|
|
244
|
+
def __init__(self, client: Any) -> None:
|
|
245
|
+
self._client = client
|
|
246
|
+
|
|
247
|
+
def get(self, match_id: str | int) -> Any:
|
|
248
|
+
return _one(self._client._post_json("/match", {"match_id": str(match_id)}))
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rivalsdata-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client and MCP server for public Marvel Rivals stats from RivalsData
|
|
5
|
+
Project-URL: Homepage, https://github.com/GS-Rionnag/rivalsdata-api
|
|
6
|
+
Project-URL: Repository, https://github.com/GS-Rionnag/rivalsdata-api
|
|
7
|
+
Project-URL: Issues, https://github.com/GS-Rionnag/rivalsdata-api/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/GS-Rionnag/rivalsdata-api/releases
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api-client,game-stats,marvel-rivals,mcp,rivalsdata
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Games/Entertainment
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: curl-cffi>=0.7
|
|
25
|
+
Provides-Extra: browser
|
|
26
|
+
Requires-Dist: camoufox>=0.5.6; extra == 'browser'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.11; extra == 'dev'
|
|
31
|
+
Provides-Extra: mcp
|
|
32
|
+
Requires-Dist: mcp-ui-server>=1.0.0; extra == 'mcp'
|
|
33
|
+
Requires-Dist: mcp<2,>=1.12; extra == 'mcp'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# rivalsdata-api
|
|
37
|
+
|
|
38
|
+
An unofficial Python client for RivalsData's public Marvel Rivals data. It
|
|
39
|
+
uses the site's undocumented API, so routes and fields can change. The client
|
|
40
|
+
keeps unknown response fields accessible instead of discarding them.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
Python 3.10 or newer:
|
|
45
|
+
|
|
46
|
+
```console
|
|
47
|
+
python -m pip install rivalsdata-api
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For editable development, clone the repository and run
|
|
51
|
+
`python -m pip install -e '.[dev]'`. The optional Camoufox Cloudflare fallback
|
|
52
|
+
is installed with `python -m pip install 'rivalsdata-api[browser]'`, followed
|
|
53
|
+
by `python -m camoufox fetch`.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from rivalsdata import RivalsDataClient
|
|
59
|
+
|
|
60
|
+
with RivalsDataClient() as rd:
|
|
61
|
+
player = rd.get_player("GS-") # numeric UID works too
|
|
62
|
+
print(player.name, player.level, player.rank_game_season)
|
|
63
|
+
|
|
64
|
+
# Player profile sections are lazy resource managers.
|
|
65
|
+
hero_season = player.heroes.fetch(season=20)
|
|
66
|
+
map_stats = player.stats.maps(season=20)
|
|
67
|
+
match_page = player.matches.fetch(season=20)
|
|
68
|
+
|
|
69
|
+
# Current match (None if the profile is not currently in a game).
|
|
70
|
+
live_game = player.live_game.fetch()
|
|
71
|
+
if live_game is not None:
|
|
72
|
+
print(live_game.players, live_game.team_avg_rank)
|
|
73
|
+
|
|
74
|
+
# Site-wide resources are available from the client.
|
|
75
|
+
leaderboard = rd.leaderboards.fetch(limit=100, season=20, platform=1)
|
|
76
|
+
tier_list = rd.heroes.tier_list(platform=1, rank="grandmaster_plus")
|
|
77
|
+
team_ups = rd.team_ups.fetch(platform=1, rank="grandmaster_plus")
|
|
78
|
+
xp_page = rd.insights.xp()
|
|
79
|
+
|
|
80
|
+
print(hero_season[0].win_rate) # integer percent when wins/losses are present
|
|
81
|
+
```
|
|
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 also returns an MCP-UI player report with rank and
|
|
95
|
+
competitive record, current match roster split by side, a hero win-rate chart,
|
|
96
|
+
and recent match form with K/D/A. It supports local stdio for Claude Desktop
|
|
97
|
+
and Streamable HTTP for remote MCP clients such as ChatGPT. Data comes from
|
|
98
|
+
RivalsData's undocumented API and may change; profile match history can be
|
|
99
|
+
private.
|
|
100
|
+
|
|
101
|
+
### How the MCP UI works
|
|
102
|
+
|
|
103
|
+
`show_player_dashboard` fetches current data, then returns an HTML UI resource
|
|
104
|
+
alongside the tool result. MCP-UI labels it with a `ui://` resource URI and
|
|
105
|
+
preferred size. A compatible host can render that resource in a sandboxed
|
|
106
|
+
panel; a host without UI support can still use the regular MCP tools and their
|
|
107
|
+
text/data responses. ChatGPT uses MCP-UI's Apps SDK adapter, while Claude is
|
|
108
|
+
listed as supporting MCP Apps directly. The dashboard is a snapshot from the
|
|
109
|
+
time the tool runs; ask for it again to refresh.
|
|
110
|
+
|
|
111
|
+
### Claude Desktop (local)
|
|
112
|
+
|
|
113
|
+
Add a server entry to Claude Desktop's `claude_desktop_config.json`, replacing
|
|
114
|
+
the path with the Python executable in the environment where the extra is
|
|
115
|
+
installed:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"mcpServers": {
|
|
120
|
+
"rivalsdata": {
|
|
121
|
+
"command": "C:\\path\\to\\venv\\Scripts\\python.exe",
|
|
122
|
+
"args": ["-m", "rivalsdata.mcp_server"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
On macOS/Linux, use the environment's `bin/python` path. Restart Claude Desktop
|
|
129
|
+
after saving the configuration.
|
|
130
|
+
|
|
131
|
+
### ChatGPT or remote Claude connector
|
|
132
|
+
|
|
133
|
+
Run the server on a host reachable over HTTPS:
|
|
134
|
+
|
|
135
|
+
```console
|
|
136
|
+
rivalsdata-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The MCP endpoint is `/mcp` (for example, `https://your-host.example/mcp`). Add
|
|
140
|
+
that endpoint through the client's custom/remote MCP connector settings. The
|
|
141
|
+
server does not implement authentication; put it behind an authenticated
|
|
142
|
+
HTTPS gateway before exposing it publicly. For local development, bind to
|
|
143
|
+
`127.0.0.1` instead. Use `python -m rivalsdata.mcp_server --help` to see options.
|
|
144
|
+
|
|
145
|
+
`Player` and returned `DataModel` objects support both mapping access and
|
|
146
|
+
attribute access (`player["level"]` or `player.level`). Nested dictionaries
|
|
147
|
+
and arrays are wrapped recursively; `.raw` returns a shallow copy of a model's
|
|
148
|
+
original JSON. For endpoints whose fields evolve, these generic typed wrappers
|
|
149
|
+
preserve the complete payload.
|
|
150
|
+
|
|
151
|
+
## Public resources
|
|
152
|
+
|
|
153
|
+
- `rd.leaderboards.fetch(...)` — global player ranking.
|
|
154
|
+
- `rd.heroes.tier_list(...)`, `.get(hero_id)`, `.meta(hero_id, range=90)`,
|
|
155
|
+
`.leaderboard(hero_id, **filters)` — hero metrics and ranking.
|
|
156
|
+
- `rd.team_ups.fetch(...)` — team-up stats.
|
|
157
|
+
- `rd.insights.punishments(...)`, `.xp(...)`, `.top_500(...)`, `.commbans(...)`,
|
|
158
|
+
`.leavers(...)` — public insights and cursor metadata.
|
|
159
|
+
- `rd.factions.get(faction_id)`, `rd.matches.get(match_id)`,
|
|
160
|
+
`rd.profiles.get(username)`, and `rd.favorites.fetch(uids)` — detail/profile
|
|
161
|
+
lookups.
|
|
162
|
+
- `player.heroes.fetch(...)`, `.matches.fetch(...)`, `.live_game.fetch()`, `.teammates.fetch(...)`,
|
|
163
|
+
`.crosshairs.fetch()`, `.proficiency.fetch()`, `.punishments.fetch()`,
|
|
164
|
+
`.name_history.fetch()` — profile sections.
|
|
165
|
+
- `player.stats.heroes(...)`, `.maps(...)`, `.bans(...)` — detailed profile stats.
|
|
166
|
+
|
|
167
|
+
See [the observed API inventory](docs/API.md) for methods, parameters, observed
|
|
168
|
+
response shapes, and endpoints that require a RivalsData account. The API
|
|
169
|
+
inventory distinguishes observed behavior from inferred/unverified details.
|
|
170
|
+
|
|
171
|
+
## Cloudflare fallback
|
|
172
|
+
|
|
173
|
+
Requests use curl_cffi with a Chrome TLS profile by default. If blocked, enable
|
|
174
|
+
the optional browser fallback:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
with RivalsDataClient(use_browser_fallback=True) as rd:
|
|
178
|
+
player = rd.get_player(1970288503)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Errors and contributions
|
|
182
|
+
|
|
183
|
+
All package exceptions inherit from `RivalsDataError`. See
|
|
184
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for setup, code layout, change workflow, and
|
|
185
|
+
notes for new contributors. [docs/PROJECT_CONTEXT.md](docs/PROJECT_CONTEXT.md)
|
|
186
|
+
is the handoff document for new coding sessions.
|
|
187
|
+
|
|
188
|
+
This project is not affiliated with RivalsData, NetEase, or Marvel. Keep
|
|
189
|
+
request rates reasonable and respect the site's terms.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
rivalsdata/__init__.py,sha256=TFp3SdV5ioBVUL7RBBsy6vImKjK4DxxXLaA8r2A9Bps,477
|
|
2
|
+
rivalsdata/client.py,sha256=OQM2L1t2LLcbBKoKGr4060RSNYl1egb8bd7Ct0PRqt0,10974
|
|
3
|
+
rivalsdata/exceptions.py,sha256=rL7sfeFnb5vb1xOG5P_Tt-NGf9U1Y0YkpLE3EZ0mK0k,455
|
|
4
|
+
rivalsdata/mcp_server.py,sha256=t0M2rErOGLfraTk2frdSjS6QO0mLkWGYEAiiLlTSTTk,21922
|
|
5
|
+
rivalsdata/models.py,sha256=PUffcUJuIEgZe1x3m8UseqXAmOiRsQ5ObLswtPxHnQ4,5550
|
|
6
|
+
rivalsdata/resources.py,sha256=NlwiOUYxYIW96KR5flA2RQ24MuKo_itUT4pGTdu5l3U,8571
|
|
7
|
+
rivalsdata_api-1.0.0.dist-info/METADATA,sha256=RSPO8g2NfqBufF26gwxV7IE17rFd-e6x7Fq7YqKJLD4,7540
|
|
8
|
+
rivalsdata_api-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
9
|
+
rivalsdata_api-1.0.0.dist-info/entry_points.txt,sha256=wgwiN90usgAAOWS2__aZfvB0zBvBiXIC0ZKUn7p6FSc,62
|
|
10
|
+
rivalsdata_api-1.0.0.dist-info/licenses/LICENSE,sha256=B19xsF0tqLiaasaK8QyX4GbA2dZNWlf7PcF7GbDZ2Y0,1084
|
|
11
|
+
rivalsdata_api-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rivalsdata-api contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|