rivals-api 2.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.
- rivals_api/__init__.py +165 -0
- rivals_api/client.py +437 -0
- rivals_api/exceptions.py +17 -0
- rivals_api/extensions.py +161 -0
- rivals_api/hero_catalog.json +10091 -0
- rivals_api/hero_ids.py +107 -0
- rivals_api/history.py +138 -0
- rivals_api/mcp_server.py +742 -0
- rivals_api/models.py +1034 -0
- rivals_api/normalize.py +342 -0
- rivals_api/providers.py +118 -0
- rivals_api/resources.py +641 -0
- rivals_api/selection.py +129 -0
- rivals_api-2.0.0.dist-info/METADATA +325 -0
- rivals_api-2.0.0.dist-info/RECORD +29 -0
- rivals_api-2.0.0.dist-info/WHEEL +4 -0
- rivals_api-2.0.0.dist-info/licenses/LICENSE +21 -0
- rivalsdata/__init__.py +2 -0
- rivalsdata/client.py +8 -0
- rivalsdata/exceptions.py +8 -0
- rivalsdata/extensions.py +8 -0
- rivalsdata/hero_ids.py +8 -0
- rivalsdata/history.py +8 -0
- rivalsdata/mcp_server.py +8 -0
- rivalsdata/models.py +8 -0
- rivalsdata/normalize.py +8 -0
- rivalsdata/providers.py +8 -0
- rivalsdata/resources.py +8 -0
- rivalsdata/selection.py +8 -0
rivals_api/extensions.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""New public analytics resources, separate from the legacy response contracts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
from .models import DataModel
|
|
9
|
+
from .normalize import tracker_stats
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def response(data: Any, source: str, **scope: Any) -> DataModel:
|
|
13
|
+
return DataModel({"data": data, "source": source, "scope": scope})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PlayerAnalytics:
|
|
17
|
+
def __init__(self, client: Any, uid: int) -> None:
|
|
18
|
+
self.client, self.uid = client, uid
|
|
19
|
+
|
|
20
|
+
def rank_history(self, *, season: int | None = None) -> DataModel:
|
|
21
|
+
return response(self.client.providers.rt.request(
|
|
22
|
+
f"/player/{self.uid}/rank-history", params={"season": season}),
|
|
23
|
+
"rivalstracker", season=season)
|
|
24
|
+
|
|
25
|
+
def cosmetics(self) -> DataModel:
|
|
26
|
+
return response(self.client.providers.rt.request(
|
|
27
|
+
f"/player/{self.uid}/identity-cosmetics"), "rivalstracker",
|
|
28
|
+
inventory_type="observed_usage", ownership_verified=False)
|
|
29
|
+
|
|
30
|
+
def punishment_history(self) -> DataModel:
|
|
31
|
+
return response(self.client.providers.rt.request(
|
|
32
|
+
f"/player/{self.uid}/punishments"), "rivalstracker")
|
|
33
|
+
|
|
34
|
+
def matchups(self, *, season: int | None = None) -> DataModel:
|
|
35
|
+
body = self.client.providers.rt.request(f"/player/{self.uid}",
|
|
36
|
+
params={"season": season})
|
|
37
|
+
return response(body.get("matchups", []), "rivalstracker", season=season,
|
|
38
|
+
opponents="enemy_heroes")
|
|
39
|
+
|
|
40
|
+
def encounters(self, *, season: int | None = None, mode: str = "all",
|
|
41
|
+
local_offset: int = 0) -> DataModel:
|
|
42
|
+
if not -840 <= local_offset <= 840:
|
|
43
|
+
raise ValueError("local_offset must be timezone offset minutes (-840..840)")
|
|
44
|
+
path = self.client._tracker_path(self.uid) + "/aggregated"
|
|
45
|
+
body = self.client.providers.tracker.request(path, params={
|
|
46
|
+
"filter": "encounters", "mode": mode, "season": season,
|
|
47
|
+
"localOffset": local_offset})
|
|
48
|
+
return response(body, "tracker", season=season, mode=mode,
|
|
49
|
+
local_offset=local_offset, default_window_days=75 if season is None else None)
|
|
50
|
+
|
|
51
|
+
def career(self, *, season: int | str | None = None, mode: str = "all") -> DataModel:
|
|
52
|
+
if mode == "quickplay":
|
|
53
|
+
mode = "quick-match"
|
|
54
|
+
if season in ("all", -1):
|
|
55
|
+
# Never treat an omitted season (provider default) as lifetime.
|
|
56
|
+
catalog = self.seasons().data.get("seasons", [])
|
|
57
|
+
rows = []
|
|
58
|
+
for item in catalog:
|
|
59
|
+
rows.extend({**row, "season": item["id"]}
|
|
60
|
+
for row in self.career(season=item["id"], mode=mode).data)
|
|
61
|
+
return response(rows, "tracker", season="all", mode=mode,
|
|
62
|
+
aggregation="separate_season_segments", unique_matches_verified=False)
|
|
63
|
+
params = {"mode": mode}
|
|
64
|
+
if season not in (None, "all", -1):
|
|
65
|
+
params["season"] = season
|
|
66
|
+
segments = self.client.providers.tracker.request(
|
|
67
|
+
self.client._tracker_path(self.uid) + "/segments/career", params=params)
|
|
68
|
+
rows = [{"type": s.get("type"), "attributes": s.get("attributes", {}),
|
|
69
|
+
"metadata": s.get("metadata", {}), **tracker_stats(s)} for s in segments]
|
|
70
|
+
return response(rows, "tracker", season=season, mode=mode,
|
|
71
|
+
counts_basis="provider_hero_participation", unique_matches_verified=False)
|
|
72
|
+
|
|
73
|
+
def rank_stats(self, *, season: int | None = None) -> DataModel:
|
|
74
|
+
return response(self.client.providers.tracker.request(
|
|
75
|
+
self.client._tracker_path(self.uid) + "/stats/overview/ranked",
|
|
76
|
+
params={"season": season}), "tracker", season=season)
|
|
77
|
+
|
|
78
|
+
def seasons(self) -> DataModel:
|
|
79
|
+
body = self.client.providers.tracker.request(self.client._tracker_path(self.uid))
|
|
80
|
+
meta = body.get("metadata", {})
|
|
81
|
+
return response({k: meta.get(k) for k in ("seasons", "currentSeason", "defaultSeason")},
|
|
82
|
+
"tracker")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class GameAnalytics:
|
|
86
|
+
def __init__(self, client: Any) -> None:
|
|
87
|
+
self.client = client
|
|
88
|
+
|
|
89
|
+
def hero_stats(self, *, season: int | None = None) -> DataModel:
|
|
90
|
+
return response(self.client.providers.rt.request("/heroes/stats", params={"season": season}),
|
|
91
|
+
"rivalstracker", season=season)
|
|
92
|
+
|
|
93
|
+
def hero_matchups(self, hero_id: int | str | None = None) -> DataModel:
|
|
94
|
+
body = self.client.providers.rt.request("/hero-matchups")
|
|
95
|
+
if hero_id is not None:
|
|
96
|
+
body = body.get(str(hero_id), {})
|
|
97
|
+
return response(body, "rivalstracker", platforms="all", ranks="all",
|
|
98
|
+
season_filter_supported=False)
|
|
99
|
+
|
|
100
|
+
def team_compositions(self) -> DataModel:
|
|
101
|
+
return response(self.client.providers.rt.request("/roles/data"), "rivalstracker")
|
|
102
|
+
|
|
103
|
+
def rank_distribution(self) -> DataModel:
|
|
104
|
+
rows = self.client.providers.rt.request("/rank-distribution/data")
|
|
105
|
+
total = sum(row.get("total", 0) for row in rows)
|
|
106
|
+
return response({"total": total, "ranks": [
|
|
107
|
+
{**row, "percentage": row.get("total", 0) * 100 / total if total else None}
|
|
108
|
+
for row in rows]}, "rivalstracker", population="provider_tracked_players")
|
|
109
|
+
|
|
110
|
+
def skin_popularity(self) -> DataModel:
|
|
111
|
+
return response(self.client.providers.rt.request("/hero-skins"), "rivalstracker",
|
|
112
|
+
ownership_verified=False)
|
|
113
|
+
|
|
114
|
+
def hero_history(self, hero_id: int | str, *, days: int = 30,
|
|
115
|
+
platform: str = "pc") -> DataModel:
|
|
116
|
+
if days not in (30, 90, 180):
|
|
117
|
+
raise ValueError("days must be 30, 90, or 180")
|
|
118
|
+
if platform not in ("pc", "console"):
|
|
119
|
+
raise ValueError("platform must be pc or console")
|
|
120
|
+
return response(self.client.providers.rt.request(
|
|
121
|
+
f"/hero-stats-history/{quote(str(hero_id), safe='')}",
|
|
122
|
+
params={"days": days, "platform": platform}), "rivalstracker",
|
|
123
|
+
days=days, platform=platform, rank="celestial+")
|
|
124
|
+
|
|
125
|
+
def hero_players(self, hero_id: int | str, *, season: int | None = None,
|
|
126
|
+
device: int = 1) -> DataModel:
|
|
127
|
+
return response(self.client.providers.rt.request(
|
|
128
|
+
f"/hero-players/{quote(str(hero_id), safe='')}",
|
|
129
|
+
params={"season": season, "device": device}), "rivalstracker",
|
|
130
|
+
season=season, device=device)
|
|
131
|
+
|
|
132
|
+
def hero_reference(self, hero_id: int | str | None = None) -> DataModel:
|
|
133
|
+
# A dated packaged public catalog supports abilities without a browser.
|
|
134
|
+
import json
|
|
135
|
+
from importlib.resources import files
|
|
136
|
+
|
|
137
|
+
catalog = json.loads(files("rivals_api").joinpath("hero_catalog.json").read_text(encoding="utf-8"))
|
|
138
|
+
rows = catalog["heroes"]
|
|
139
|
+
if hero_id is not None:
|
|
140
|
+
rows = [row for row in rows if str(row.get("hero_id")) == str(hero_id)]
|
|
141
|
+
return response(rows, "rivalstracker_static", snapshot_date=catalog["snapshot_date"])
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Community:
|
|
145
|
+
def __init__(self, client: Any) -> None:
|
|
146
|
+
self.client = client
|
|
147
|
+
|
|
148
|
+
def crosshairs(self, *, page: int = 1, sort: str = "upvotes", players: str = "all",
|
|
149
|
+
search: str | None = None, name: str | None = None) -> DataModel:
|
|
150
|
+
if page < 1:
|
|
151
|
+
raise ValueError("page must be positive")
|
|
152
|
+
params = {"platformUserIdentifier": name} if name else {
|
|
153
|
+
"page": page, "sort": sort, "players": players, "search": search}
|
|
154
|
+
rows = self.client.providers.tracker.request("/api/v1/marvel-rivals/crosshairs", params=params)
|
|
155
|
+
return response(rows, "tracker", page=page, page_size=20)
|
|
156
|
+
|
|
157
|
+
def looking_for_group(self, **filters: Any) -> DataModel:
|
|
158
|
+
return response(self.client.providers.tracker.request(
|
|
159
|
+
"/api/v1/marvel-rivals/lfg/search", params={
|
|
160
|
+
key: ",".join(value) if isinstance(value, list) else value
|
|
161
|
+
for key, value in filters.items() if value != "all"}), "tracker")
|