rivals-api 2.0.0__tar.gz → 3.0.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.
Files changed (57) hide show
  1. {rivals_api-2.0.0 → rivals_api-3.0.0}/PKG-INFO +103 -5
  2. {rivals_api-2.0.0 → rivals_api-3.0.0}/README.md +102 -4
  3. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/CHANGELOG.md +52 -0
  4. rivals_api-3.0.0/docs/GAME_REFERENCES.md +91 -0
  5. rivals_api-3.0.0/docs/PLAYER_SEARCH_AUDIT.md +131 -0
  6. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/PROJECT_CONTEXT.md +30 -1
  7. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/PROVIDER_INTEGRATION.md +80 -5
  8. {rivals_api-2.0.0 → rivals_api-3.0.0}/pyproject.toml +1 -1
  9. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/__init__.py +15 -1
  10. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/client.py +5 -0
  11. rivals_api-3.0.0/src/rivals_api/game_catalog.json +815 -0
  12. rivals_api-3.0.0/src/rivals_api/game_ids.py +47 -0
  13. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/history.py +27 -12
  14. rivals_api-3.0.0/src/rivals_api/match_details.py +96 -0
  15. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/mcp_server.py +87 -15
  16. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/models.py +291 -9
  17. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/normalize.py +139 -40
  18. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/providers.py +2 -2
  19. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/resources.py +413 -49
  20. rivals_api-3.0.0/tests/test_game_references.py +209 -0
  21. rivals_api-3.0.0/tests/test_match_details.py +253 -0
  22. rivals_api-3.0.0/tests/test_mcp_player_search.py +83 -0
  23. {rivals_api-2.0.0 → rivals_api-3.0.0}/tests/test_providers.py +15 -12
  24. {rivals_api-2.0.0 → rivals_api-3.0.0}/.github/workflows/publish-pypi.yml +0 -0
  25. {rivals_api-2.0.0 → rivals_api-3.0.0}/.gitignore +0 -0
  26. {rivals_api-2.0.0 → rivals_api-3.0.0}/CONTRIBUTING.md +0 -0
  27. {rivals_api-2.0.0 → rivals_api-3.0.0}/LICENSE +0 -0
  28. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/API.md +0 -0
  29. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/PROVIDER_FEATURE_GAPS.md +0 -0
  30. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/RIVALSTRACKER_API.md +0 -0
  31. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/RIVALSTRACKER_API_AUDIT.md +0 -0
  32. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/SITE_COMPARISON_GS4.md +0 -0
  33. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/SITE_COMPARISON_GS_MULTI_SEASON.md +0 -0
  34. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/TRACKER_NETWORK_API.md +0 -0
  35. {rivals_api-2.0.0 → rivals_api-3.0.0}/docs/provider-feature-gaps.json +0 -0
  36. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/exceptions.py +0 -0
  37. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/extensions.py +0 -0
  38. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/hero_catalog.json +0 -0
  39. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/hero_ids.py +0 -0
  40. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivals_api/selection.py +0 -0
  41. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/__init__.py +0 -0
  42. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/client.py +0 -0
  43. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/exceptions.py +0 -0
  44. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/extensions.py +0 -0
  45. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/hero_ids.py +0 -0
  46. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/history.py +0 -0
  47. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/mcp_server.py +0 -0
  48. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/models.py +0 -0
  49. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/normalize.py +0 -0
  50. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/providers.py +0 -0
  51. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/resources.py +0 -0
  52. {rivals_api-2.0.0 → rivals_api-3.0.0}/src/rivalsdata/selection.py +0 -0
  53. {rivals_api-2.0.0 → rivals_api-3.0.0}/tests/fixtures/providers.json +0 -0
  54. {rivals_api-2.0.0 → rivals_api-3.0.0}/tests/test_class_stats.py +0 -0
  55. {rivals_api-2.0.0 → rivals_api-3.0.0}/tests/test_hero_names.py +0 -0
  56. {rivals_api-2.0.0 → rivals_api-3.0.0}/tests/test_selection.py +0 -0
  57. {rivals_api-2.0.0 → rivals_api-3.0.0}/tests/test_typed_responses.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rivals-api
3
- Version: 2.0.0
3
+ Version: 3.0.0
4
4
  Summary: Multi-source Python client and MCP server for Marvel Rivals player stats
5
5
  Project-URL: Homepage, https://github.com/GS-Rionnag/rivals-api
6
6
  Project-URL: Repository, https://github.com/GS-Rionnag/rivals-api
@@ -71,7 +71,15 @@ with RivalsClient() as rd:
71
71
  hero_season = player.heroes.fetch(season=20)
72
72
  all_hero_seasons = player.heroes.fetch(season="all")
73
73
  map_stats = player.stats.maps(season=20)
74
- match_page = player.matches.fetch(season=20)
74
+ match_page = player.matches.fetch(limit=20, season=20)
75
+ all_matches = player.matches.fetch(limit="all")
76
+ combined_rate = player.matches.fetch_win_rate(season=20)
77
+ print(combined_rate.win_rate_pct, combined_rate.provider_rates)
78
+ fast_rate = player.matches.fetch_win_rate()
79
+ exact_rate = player.matches.fetch_win_rate(method="exact")
80
+ cached_rate = player.matches.fetch_win_rate(method="cached") # after fetch(limit="all")
81
+ hero_rates = player.matches.fetch_hero_win_rates(method="exact")
82
+ class_rates = player.matches.fetch_class_win_rates(method="cached")
75
83
 
76
84
  # Current match when its provider exposes it; Custom discovery is unsupported.
77
85
  live_game = player.live_game.fetch()
@@ -249,8 +257,88 @@ with annotations for fields observed in the API inventory. This includes
249
257
  `ProficiencyResponse`, `LeaderboardResponse`, `PunishmentsPage`, `XPPage`,
250
258
  `Top500Response`, and typed teammate, crosshair, stats, faction, and insight
251
259
  records. For example, `rd.matches.get(match_id)` returns a `Match`,
252
- `player.matches.fetch()` returns a `MatchHistory`, and
253
- `player.proficiency.fetch()` returns a `ProficiencyResponse`. Nested match
260
+ `player.matches.fetch(limit=...)` returns a `MatchHistory`. The required limit
261
+ is either a positive number (bounded fetch with a resumable cursor) or
262
+ `"all"` (fetch every available page); both combine and deduplicate providers.
263
+ Each history row is a client-bound `Match`. Call `get_details()` while its
264
+ client is open to lazily fetch combined RivalsData, RivalsTracker, and Tracker.gg
265
+ details, including typed teams, players, and hero segments:
266
+
267
+ ```python
268
+ with RivalsClient(use_browser_fallback=True) as client:
269
+ player = client.get_player(1970288503)
270
+ history = player.matches.fetch(limit=20)
271
+ if history.matches:
272
+ match = history.matches[0]
273
+ details = match.get_details() # Match; the history row stays unchanged
274
+ for team in details.teams:
275
+ for participant in team.players:
276
+ print(participant.name, participant.get("accuracy_percent"))
277
+ print(details.provider_metadata.errors)
278
+ # match.get_details(refresh=True) bypasses detail/provider caches.
279
+ ```
280
+
281
+ `client.matches.get(match_id, refresh=False)` uses the same fetcher. Provider
282
+ outages return available details with errors; conflicts and field selections
283
+ remain in `provider_metadata`. `accuracy_percent` uses percent units for players
284
+ and heroes. The legacy RD `accuracy` field remains a player percentage or a
285
+ hero ratio; `session_hit_rate` is separate and is never substituted for accuracy.
286
+ Unknown/NaN accuracy is unavailable. See the
287
+ [match detail guide](docs/PROVIDER_INTEGRATION.md#combined-match-details).
288
+
289
+ History and detail matches also expose readable typed references. They format
290
+ as names in bot messages; use `.name` for a plain string and `.id` for the
291
+ original identifier. For a match with map ID `1288`, mode `3`, and platform `1`:
292
+
293
+ ```python
294
+ print(match.map) # Hell's Heaven
295
+ print(match.map.name) # Hell's Heaven (str)
296
+ print(match.map.id) # 1288
297
+ print(match.map.location) # Hydra Charteris Base
298
+ print(match.game_mode) # Custom
299
+ print(match.gameplay_mode) # Domination
300
+ print(match.platform) # PC
301
+ print(match.hero) # Named Character, when history includes a hero
302
+ print(match.rank) # Named Rank, when a rank level is available
303
+ print(match.season_info) # Named Season, when a season is available
304
+ ```
305
+
306
+ These are objects with `__str__`, so `f"Played on {match.map}"` prints the map
307
+ name while `match.map.id` remains available. `game_play_mode` aliases
308
+ `gameplay_mode`. Participants expose `hero`, `top_hero`, `rank_info`, and
309
+ `platform_info`; draft entries expose `hero` and `team`. Existing Character
310
+ records now have `.id`, `.name`, and name-based string formatting.
311
+
312
+ **Migration:** `match.platform` is now a `Platform` object. Use
313
+ `match.platform_id` or `match.platform.id` for its previous numeric value.
314
+ `to_dict()` serializes references as `{id, name, is_known, source, ...}` objects;
315
+ the original `map_id`, `game_mode_id`, and other numeric fields remain present.
316
+ `.raw` preserves the input payload. The packaged catalogs resolve names
317
+ without additional network requests. Unmapped codes produce an explicit
318
+ `Unknown ...` reference with `is_known=False`; absent fields return `None`.
319
+ See [readable match references](docs/GAME_REFERENCES.md) for mapping sources
320
+ and gameplay-code limits.
321
+
322
+ The overall, hero, and class win-rate methods accept `method="estimate"` (the
323
+ default), `"exact"`, or `"cached"`. Overall estimates average available
324
+ RivalsData/RivalsTracker competitive rates; hero estimates average each
325
+ provider's per-hero rates; class estimates aggregate provider hero-participation
326
+ counts within each class before averaging provider rates. These estimates are
327
+ fast and approximate, and include provider sample counts. Exact calculations
328
+ traverse all available history pages and deduplicate by match ID. Hero/class
329
+ exact calculations also inspect match details and assign each match to the
330
+ player's longest-played hero, which can require one detail lookup per match.
331
+ Cached calculations make no requests and reuse a prior
332
+ `matches.fetch(limit="all")` in the same Python process, including across client
333
+ instances. If the cached history fetch had provider errors, the calculation
334
+ does not retry missing data; inspect its coverage/errors. Cached hero/class
335
+ rates use cached playtime details when available and otherwise fall back to
336
+ the history row's hero. The returned metadata reports sources, unknown results,
337
+ provider errors, and hero-attribution fallback counts. See the
338
+ [win-rate method guide](docs/PROVIDER_INTEGRATION.md#win-rate-calculation-choices)
339
+ for the assumptions and costs of each method.
340
+ MCP tools expose the same three methods. `player.proficiency.fetch()` returns a
341
+ `ProficiencyResponse`. Nested match
254
342
  teams and participants are converted to `MatchTeam` and `MatchPlayer`; embedded
255
343
  character records use `Character`. Models support mapping access
256
344
  (`player["level"]`) and attribute access (`player.level`). Unknown upstream
@@ -271,6 +359,16 @@ with RivalsClient() as rd:
271
359
 
272
360
  ## Public resources
273
361
 
362
+ For a player selection list, use `rd.search_players("silo")`, or the MCP tool
363
+ `search_players(name="silo")`. Each candidate includes a name and numeric UID;
364
+ pass the selected UID to `get_player_profile(uid=283622404)` for their profile
365
+ overview. `search_player_candidates` remains an alias, and `get_player` still
366
+ accepts a UID or exact name. MCP `search_players` now returns a list instead
367
+ of one resolved account. Live checks on 2026-10-02 confirmed that the
368
+ candidate search includes `siloء` (UID `283622404`) when searching `silo`.
369
+ See [the player-search audit](docs/PLAYER_SEARCH_AUDIT.md) for provider comparisons
370
+ and exact-name versus suggestion behavior.
371
+
274
372
  - `rd.leaderboards.fetch(...)` — global player ranking.
275
373
  - `rd.heroes.tier_list(...)`, `.get(hero_id)`, `.meta(hero_id, range=90)`,
276
374
  `.leaderboard(hero_id, **filters)` — hero metrics and ranking.
@@ -317,7 +415,7 @@ Existing functions combine public RivalsData, RivalsTracker, and Tracker.gg data
317
415
 
318
416
  ## Documentation
319
417
 
320
- - [2.0.0 release notes and migration guide](docs/CHANGELOG.md)
418
+ - [Release notes and migration guide](docs/CHANGELOG.md)
321
419
  - [Provider integration and data-selection rules](docs/PROVIDER_INTEGRATION.md)
322
420
  - [Research findings and unsupported features](docs/PROVIDER_FEATURE_GAPS.md)
323
421
  - [RivalsTracker API audit](docs/RIVALSTRACKER_API_AUDIT.md)
@@ -35,7 +35,15 @@ with RivalsClient() as rd:
35
35
  hero_season = player.heroes.fetch(season=20)
36
36
  all_hero_seasons = player.heroes.fetch(season="all")
37
37
  map_stats = player.stats.maps(season=20)
38
- match_page = player.matches.fetch(season=20)
38
+ match_page = player.matches.fetch(limit=20, season=20)
39
+ all_matches = player.matches.fetch(limit="all")
40
+ combined_rate = player.matches.fetch_win_rate(season=20)
41
+ print(combined_rate.win_rate_pct, combined_rate.provider_rates)
42
+ fast_rate = player.matches.fetch_win_rate()
43
+ exact_rate = player.matches.fetch_win_rate(method="exact")
44
+ cached_rate = player.matches.fetch_win_rate(method="cached") # after fetch(limit="all")
45
+ hero_rates = player.matches.fetch_hero_win_rates(method="exact")
46
+ class_rates = player.matches.fetch_class_win_rates(method="cached")
39
47
 
40
48
  # Current match when its provider exposes it; Custom discovery is unsupported.
41
49
  live_game = player.live_game.fetch()
@@ -213,8 +221,88 @@ with annotations for fields observed in the API inventory. This includes
213
221
  `ProficiencyResponse`, `LeaderboardResponse`, `PunishmentsPage`, `XPPage`,
214
222
  `Top500Response`, and typed teammate, crosshair, stats, faction, and insight
215
223
  records. For example, `rd.matches.get(match_id)` returns a `Match`,
216
- `player.matches.fetch()` returns a `MatchHistory`, and
217
- `player.proficiency.fetch()` returns a `ProficiencyResponse`. Nested match
224
+ `player.matches.fetch(limit=...)` returns a `MatchHistory`. The required limit
225
+ is either a positive number (bounded fetch with a resumable cursor) or
226
+ `"all"` (fetch every available page); both combine and deduplicate providers.
227
+ Each history row is a client-bound `Match`. Call `get_details()` while its
228
+ client is open to lazily fetch combined RivalsData, RivalsTracker, and Tracker.gg
229
+ details, including typed teams, players, and hero segments:
230
+
231
+ ```python
232
+ with RivalsClient(use_browser_fallback=True) as client:
233
+ player = client.get_player(1970288503)
234
+ history = player.matches.fetch(limit=20)
235
+ if history.matches:
236
+ match = history.matches[0]
237
+ details = match.get_details() # Match; the history row stays unchanged
238
+ for team in details.teams:
239
+ for participant in team.players:
240
+ print(participant.name, participant.get("accuracy_percent"))
241
+ print(details.provider_metadata.errors)
242
+ # match.get_details(refresh=True) bypasses detail/provider caches.
243
+ ```
244
+
245
+ `client.matches.get(match_id, refresh=False)` uses the same fetcher. Provider
246
+ outages return available details with errors; conflicts and field selections
247
+ remain in `provider_metadata`. `accuracy_percent` uses percent units for players
248
+ and heroes. The legacy RD `accuracy` field remains a player percentage or a
249
+ hero ratio; `session_hit_rate` is separate and is never substituted for accuracy.
250
+ Unknown/NaN accuracy is unavailable. See the
251
+ [match detail guide](docs/PROVIDER_INTEGRATION.md#combined-match-details).
252
+
253
+ History and detail matches also expose readable typed references. They format
254
+ as names in bot messages; use `.name` for a plain string and `.id` for the
255
+ original identifier. For a match with map ID `1288`, mode `3`, and platform `1`:
256
+
257
+ ```python
258
+ print(match.map) # Hell's Heaven
259
+ print(match.map.name) # Hell's Heaven (str)
260
+ print(match.map.id) # 1288
261
+ print(match.map.location) # Hydra Charteris Base
262
+ print(match.game_mode) # Custom
263
+ print(match.gameplay_mode) # Domination
264
+ print(match.platform) # PC
265
+ print(match.hero) # Named Character, when history includes a hero
266
+ print(match.rank) # Named Rank, when a rank level is available
267
+ print(match.season_info) # Named Season, when a season is available
268
+ ```
269
+
270
+ These are objects with `__str__`, so `f"Played on {match.map}"` prints the map
271
+ name while `match.map.id` remains available. `game_play_mode` aliases
272
+ `gameplay_mode`. Participants expose `hero`, `top_hero`, `rank_info`, and
273
+ `platform_info`; draft entries expose `hero` and `team`. Existing Character
274
+ records now have `.id`, `.name`, and name-based string formatting.
275
+
276
+ **Migration:** `match.platform` is now a `Platform` object. Use
277
+ `match.platform_id` or `match.platform.id` for its previous numeric value.
278
+ `to_dict()` serializes references as `{id, name, is_known, source, ...}` objects;
279
+ the original `map_id`, `game_mode_id`, and other numeric fields remain present.
280
+ `.raw` preserves the input payload. The packaged catalogs resolve names
281
+ without additional network requests. Unmapped codes produce an explicit
282
+ `Unknown ...` reference with `is_known=False`; absent fields return `None`.
283
+ See [readable match references](docs/GAME_REFERENCES.md) for mapping sources
284
+ and gameplay-code limits.
285
+
286
+ The overall, hero, and class win-rate methods accept `method="estimate"` (the
287
+ default), `"exact"`, or `"cached"`. Overall estimates average available
288
+ RivalsData/RivalsTracker competitive rates; hero estimates average each
289
+ provider's per-hero rates; class estimates aggregate provider hero-participation
290
+ counts within each class before averaging provider rates. These estimates are
291
+ fast and approximate, and include provider sample counts. Exact calculations
292
+ traverse all available history pages and deduplicate by match ID. Hero/class
293
+ exact calculations also inspect match details and assign each match to the
294
+ player's longest-played hero, which can require one detail lookup per match.
295
+ Cached calculations make no requests and reuse a prior
296
+ `matches.fetch(limit="all")` in the same Python process, including across client
297
+ instances. If the cached history fetch had provider errors, the calculation
298
+ does not retry missing data; inspect its coverage/errors. Cached hero/class
299
+ rates use cached playtime details when available and otherwise fall back to
300
+ the history row's hero. The returned metadata reports sources, unknown results,
301
+ provider errors, and hero-attribution fallback counts. See the
302
+ [win-rate method guide](docs/PROVIDER_INTEGRATION.md#win-rate-calculation-choices)
303
+ for the assumptions and costs of each method.
304
+ MCP tools expose the same three methods. `player.proficiency.fetch()` returns a
305
+ `ProficiencyResponse`. Nested match
218
306
  teams and participants are converted to `MatchTeam` and `MatchPlayer`; embedded
219
307
  character records use `Character`. Models support mapping access
220
308
  (`player["level"]`) and attribute access (`player.level`). Unknown upstream
@@ -235,6 +323,16 @@ with RivalsClient() as rd:
235
323
 
236
324
  ## Public resources
237
325
 
326
+ For a player selection list, use `rd.search_players("silo")`, or the MCP tool
327
+ `search_players(name="silo")`. Each candidate includes a name and numeric UID;
328
+ pass the selected UID to `get_player_profile(uid=283622404)` for their profile
329
+ overview. `search_player_candidates` remains an alias, and `get_player` still
330
+ accepts a UID or exact name. MCP `search_players` now returns a list instead
331
+ of one resolved account. Live checks on 2026-10-02 confirmed that the
332
+ candidate search includes `siloء` (UID `283622404`) when searching `silo`.
333
+ See [the player-search audit](docs/PLAYER_SEARCH_AUDIT.md) for provider comparisons
334
+ and exact-name versus suggestion behavior.
335
+
238
336
  - `rd.leaderboards.fetch(...)` — global player ranking.
239
337
  - `rd.heroes.tier_list(...)`, `.get(hero_id)`, `.meta(hero_id, range=90)`,
240
338
  `.leaderboard(hero_id, **filters)` — hero metrics and ranking.
@@ -281,7 +379,7 @@ Existing functions combine public RivalsData, RivalsTracker, and Tracker.gg data
281
379
 
282
380
  ## Documentation
283
381
 
284
- - [2.0.0 release notes and migration guide](docs/CHANGELOG.md)
382
+ - [Release notes and migration guide](docs/CHANGELOG.md)
285
383
  - [Provider integration and data-selection rules](docs/PROVIDER_INTEGRATION.md)
286
384
  - [Research findings and unsupported features](docs/PROVIDER_FEATURE_GAPS.md)
287
385
  - [RivalsTracker API audit](docs/RIVALSTRACKER_API_AUDIT.md)
@@ -1,5 +1,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0 — 2026-10-02
4
+
5
+ This release changes `match.platform` from a scalar to a typed `Platform`.
6
+ Use `match.platform.id` or `match.platform_id` for the previous numeric value.
7
+ Serialized matches now contain a structured `platform` and readable reference
8
+ objects alongside legacy numeric fields. See `docs/GAME_REFERENCES.md` for
9
+ migration details.
10
+
11
+ - Added readable typed map, queue, gameplay, platform, rank, season, and hero
12
+ references to match/history models, including nested participants and draft
13
+ entries. Names resolve offline; unknown IDs remain explicit. Map 1288 now
14
+ formats as Hell's Heaven, with its Hydra Charteris Base location accessible.
15
+ `match.platform` is now a Platform object; use `.platform_id` for its scalar.
16
+ Serialized responses include structured reference objects alongside legacy IDs.
17
+ - Lazy details retain missing same-match context from their originating history
18
+ row, with sources recorded in `provider_metadata.history_context`.
19
+ - Added lazy `Match.get_details(refresh=False)` to typed history rows from both
20
+ providers, bounded/all fetches, and iteration. It returns a new typed full
21
+ match through the same combined fetcher as `client.matches.get(...)`.
22
+ - Match details preserve raw provider responses, failures, completeness,
23
+ per-field provenance, and unresolved conflicts. They add provider-only roster
24
+ entries and hero segments while retaining ambiguous identities separately;
25
+ responses for a different match are rejected.
26
+ - Separated `session_hit_rate` from accuracy. Added `accuracy_percent` for
27
+ player/hero percentage access, preserving RD's legacy player-percentage and
28
+ hero-ratio `accuracy` fields. Unusable accuracy is missing.
29
+ - Scoped shared detail caches by enrichment mode and isolated returned objects.
30
+ Partial details retry on lookup; `refresh=True` bypasses both detail and
31
+ provider response caches. MCP `get_match` also accepts `refresh`.
32
+
33
+ ## 2.1.0 — 2026-10-02
34
+
35
+ - `player.matches.fetch` now requires `limit`: pass a positive integer for a
36
+ bounded, resumable page of combined history, or `"all"` to traverse all
37
+ available pages. Results are merged and deduplicated across providers.
38
+ - Added overall, hero, and class win-rate methods to Python and MCP. Each
39
+ defaults to a quick provider-summary estimate and also supports exact
40
+ calculations over match history or request-free calculations from full
41
+ history already cached in the current Python process. Hero/class exact rates
42
+ attribute matches using the player's longest-played hero from match details,
43
+ with a summary-hero fallback when detail evidence is unavailable.
44
+ - Match history is shared in a bounded process-level calculation cache so
45
+ separate client instances can reuse previously fetched full history. Results
46
+ expose provider coverage, unknown outcomes, and hero attribution fallbacks;
47
+ exact hero/class calls may require a detail lookup for each match.
48
+ - MCP `search_players(name)` now returns a list of matching players with names
49
+ and numeric game UIDs instead of automatically resolving one account. Callers
50
+ must handle list results, including an empty list for no matches.
51
+ - Added MCP `get_player_profile(uid)` to fetch the selected player's overview by
52
+ numeric UID. `get_player(uid_or_name)` remains available, and
53
+ `search_player_candidates` is an alias for the list search.
54
+
3
55
  ## 2.0.0 — 2026-10-02
4
56
 
5
57
  This release changes the project from a RivalsData-only wrapper into **rivals-api**,
@@ -0,0 +1,91 @@
1
+ # Readable game references
2
+
3
+ `Match` exposes `map: Map`, `game_mode: GameMode`,
4
+ `gameplay_mode: GameplayMode` (`game_play_mode` is an alias),
5
+ `platform: Platform`, `rank: Rank`, `season_info: Season`, and `hero: Character`.
6
+ Unavailable references are `None`. Printing or formatting a reference uses its
7
+ name; `.name` returns a string, `.id` retains its upstream identifier.
8
+ `Map.location` gives the broader location. `match.winner` references the winning
9
+ `MatchTeam` when known; `match.result` gives Victory, Defeat, or Draw when the
10
+ response establishes it. Team names are neutral Team 1/Team 2 labels; they do
11
+ not claim which side attacked or defended. Match and participant `.id` aliases
12
+ their respective match/game-player identifiers.
13
+
14
+ `MatchPlayer.hero` uses an explicitly reported hero or top hero, retaining the
15
+ corresponding Character's segment statistics when available. `top_hero`,
16
+ `rank_info`, and `platform_info` resolve their corresponding fields. Draft
17
+ entries have typed `hero` and `team` references. Stats records with `rank_level`,
18
+ `season`, `os`, or `login_os` expose `rank_info`, `season_info`, or
19
+ `platform_info`; player profiles also expose `platform_info`. Numeric fields
20
+ remain available. Account IDs, replay identifiers, party IDs, cosmetic IDs and
21
+ other opaque identifiers are preserved as reported: no established name
22
+ catalog was found for them, and party identifiers are not treated as match
23
+ team camps.
24
+
25
+ ## Serialization and compatibility
26
+
27
+ References are mapping-compatible models. `match["map"].name` and
28
+ `match.map.name` are equivalent. `to_dict()` recursively serializes references
29
+ into ordinary JSON objects with `id`, `name`, `is_known`, `source`, and any
30
+ type-specific fields. It retains legacy `map_id`, `game_mode_id`,
31
+ `game_play_mode_id`, `season`, `hero_id`, and `rank_level` fields.
32
+
33
+ `match.platform` now holds a `Platform`; use `.platform_id` or `.platform.id`
34
+ for the original scalar. Serialized matches include both `platform_id` and the
35
+ structured `platform`. `.raw` remains the original input, including numeric
36
+ platform values on responses fetched from providers. Models round-trip through
37
+ `Match(match.to_dict())` without losing labels or nested Character types.
38
+
39
+ Unknown codes retain their IDs, display `Unknown map`, `Unknown platform`,
40
+ etc., and report `is_known=False`. No label is inferred by truncating a number,
41
+ guessing a future season, or treating every console as PlayStation. Known
42
+ provider labels can resolve a new map even before the packaged catalog changes.
43
+ Names are resolved locally; constructing history references makes no detail or
44
+ metadata requests. `get_details()` also fills missing map/queue/platform/season
45
+ context from the same history match, recording those fields and their sources
46
+ under `provider_metadata.history_context`. Detail values take precedence.
47
+
48
+ ## Catalog evidence (checked 2026-10-02)
49
+
50
+ The packaged `src/rivals_api/game_catalog.json` contains 118 map variants and
51
+ 20 verified season identifiers. It prefers the specific map names from
52
+ [RivalsData's current public match catalog](https://rivalsdata.com/assets/matches-BtVAubcc.js)
53
+ and fills additional variants and location labels from
54
+ [RivalsTracker's public map catalog](https://rivalstracker.com/_nuxt/CUE0zxkU.js).
55
+ For example, `1288` is **Hell's Heaven**, location **Hydra Charteris Base**,
56
+ objective **Domination**; `1245` is **Spider-Islands**, objective **Convoy**.
57
+ Provider-supplied `map_name` / `map_mode_name` values take precedence when usable.
58
+ The catalogs contain provider-maintained labels; resolving a label is not a
59
+ claim that every provider label has been independently verified in-game.
60
+
61
+ Queue codes `1/2/3/4` mean Quick Match / Competitive / Custom / Arcade. Code 5
62
+ is Tutorial in RivalsTracker; 7 is Practice vs AI; 9/10 are Tournament. Sources
63
+ disagree about code 6: RivalsData labels it **Duel**, RivalsTracker **Practice**.
64
+ The model follows the selected field's provider when available, exposes both
65
+ under `.alternatives`, and reports an unknown name if its source is unestablished.
66
+ See the [RivalsData catalog](https://rivalsdata.com/assets/matches-BtVAubcc.js)
67
+ and [RivalsTracker match UI](https://rivalstracker.com/_nuxt/DDKdWPNg.js).
68
+
69
+ The raw `game_play_mode_id` has no verified universal objective-name mapping.
70
+ The same code `200` can be returned for different map objectives. Accordingly,
71
+ `GameplayMode.name` comes from the match's `map_mode_name` or its map variant's
72
+ catalog entry. Its `.id` retains the raw gameplay code and `.map_id` records the
73
+ context. Without a known map/objective it stays unknown. It must not be used as
74
+ a global `200 = Convoy` lookup.
75
+
76
+ Platform codes **1 = PC, 2 = PlayStation, 4 = Xbox** are established by both
77
+ [RivalsData's leaderboard platform options](https://rivalsdata.com/assets/index-B7e1fRs3.js)
78
+ and [RivalsTracker's leaderboard](https://rivalstracker.com/_nuxt/C7pX95rV.js).
79
+ Code 3 is unestablished, so it remains unknown. Platform strings such as `pc`,
80
+ `psn`, and `xbl` retain their original string ID while resolving their name.
81
+ Rank levels and divisions follow the
82
+ [provider rank catalog](https://rivalsdata.com/assets/ranks-D6blxaOO.js), with
83
+ RivalsTracker's observed levels 23–25 labeled One Above All. These are rank
84
+ levels, not hero-stat tier filter IDs, which use a different numbering scheme.
85
+ Season labels follow that same RivalsData catalog and the recorded Tracker
86
+ profile season metadata: internal ID 20 is Season 10, 19 is Season 9.5, and
87
+ 1 is Season 0. No future season label is extrapolated.
88
+
89
+ Catalog updates should verify the current public assets and retain map variant
90
+ keys; several RivalsTracker entries have an inner `ModeID` that differs from
91
+ their dictionary key. The variant dictionary key is the lookup identifier.
@@ -0,0 +1,131 @@
1
+ # Player search: `silo` and `siloء`
2
+
3
+ Live checks on 2026-10-02. The requested behavior is a list of accounts for
4
+ `silo` that includes `siloء`, whose final character is U+0621 (Arabic hamza).
5
+ The exact-name result identifies that account as game UID **283622404**.
6
+
7
+ ## Results
8
+
9
+ | Source | Search for `silo` | Search for `siloء` | Meets the requested suggestion behavior now? |
10
+ |---|---|---|---|
11
+ | RivalsData | One result: `Silo`, UID 1254981449 | One result: `siloء`, UID 283622404 | No |
12
+ | RivalsTracker | 10 results; `siloء` is fifth, UID 283622404 | One result: `siloء`, UID 283622404 | Yes |
13
+ | RivalsMeta | 10 results; `siloء` is fifth, UID 283622404 | One result: `siloء`, UID 283622404 | Yes |
14
+ | Tracker.gg autocomplete | 20 results, excluding `siloء` | One result: `siloء` | No for the shorter query |
15
+ | Tracker.gg submitted search | One result: `Silo` | One result: `siloء` | No for the shorter query |
16
+ | OP.GG website | Five displayed results after loading; `siloء` is fifth | Navigates to the `siloء` profile | Yes at the website level; a reusable search API contract was not established |
17
+
18
+ RivalsTracker and RivalsMeta returned identical search arrays in these checks.
19
+ This does not establish that they have independent databases or search coverage.
20
+ Tracker.gg's exact-name profile route also returned a public overview response
21
+ for `siloء`. These observations show that the character can be represented and
22
+ the account can be found. They do not establish why the short-query suggestions
23
+ omit it, nor that special characters in general are filtered out.
24
+
25
+ RivalsTracker's observed list, in order:
26
+
27
+ 1. `-SILO_`
28
+ 2. `Silo`
29
+ 3. `Silo_`
30
+ 4. `Silo_RAE`
31
+ 5. `siloء`
32
+ 6. `Silo_Koios`
33
+ 7. `Silo af`
34
+ 8. `Silo.is.bot`
35
+ 9. `silo_greedy`
36
+ 10. `Silo_92`
37
+
38
+ ## Exact requests and evidence
39
+
40
+ - RivalsData: `POST https://api.rivalsdata.com/players/search`, JSON
41
+ `{"name":"silo"}` or `{"name":"siloء"}`. Both returned HTTP 200.
42
+ The friend's record contains `aid: "11001_283622404"`.
43
+ - RivalsTracker: `POST https://api.rivalstracker.com/api/find-player`, same JSON
44
+ bodies. Both returned HTTP 200. The friend's row was
45
+ `{"aid":"283622404","name":"siloء","cur_head_icon_id":"31048204"}`.
46
+ - RivalsMeta: `POST https://rivalsmeta.com/api/find-player`, same JSON bodies.
47
+ Both returned HTTP 200 and the same search rows as RivalsTracker. This route
48
+ and body were found in the site's current search component and runtime config.
49
+ - Tracker.gg: `GET https://api.tracker.gg/api/v2/marvel-rivals/standard/search`
50
+ with `platform=ign`, `query=silo` or `query=siloء`, and `autocomplete=true`
51
+ for suggestions. Submitted search omits `autocomplete`. Direct HTTP was
52
+ challenged with 403; the existing Camoufox provider fallback succeeded for
53
+ all four searches. Exact profile:
54
+ `/api/v2/marvel-rivals/standard/profile/ign/silo%D8%A1`.
55
+ - OP.GG: [short-name search](https://op.gg/marvel-rivals/search/silo) and
56
+ [full-name search](https://op.gg/marvel-rivals/search/silo%D8%A1), checked in
57
+ Camoufox. The short-name page initially showed four rows; after a longer load
58
+ it showed `Silofael`, `Silonace`, `silosue`, `Siloger`, and `siloء`. The exact
59
+ search opened the profile, which showed privacy and update notices. Those
60
+ notices were respected; no update button was clicked and no private stats
61
+ were requested. The list itself is the relevant evidence here.
62
+
63
+ All these are observed website behaviors, not permanent API guarantees.
64
+ Local raw responses and browser observations are saved in the ignored
65
+ `dist/search-audit/` directory. No cookies, authorization headers, or API keys
66
+ were saved in the research artifacts.
67
+
68
+ ## Existing wrapper behavior
69
+
70
+ The Python method `RivalsClient.search_players()` already uses RivalsTracker's
71
+ multi-result `/find-player` endpoint. A live call to `search_players("silo")`
72
+ returned all 10 candidates, preserving `siloء` and adding numeric UID 283622404.
73
+ It currently queries RivalsTracker alone; it does not merge all three providers.
74
+
75
+ ```python
76
+ from rivals_api import RivalsClient
77
+
78
+ with RivalsClient() as client:
79
+ candidates = client.search_players("silo")
80
+ # Includes a row with name == "siloء" and uid == 283622404.
81
+ ```
82
+
83
+ Following this audit, the MCP search/profile flow was updated:
84
+
85
+ | MCP tool | What it calls | Live result for `silo` |
86
+ |---|---|---|
87
+ | `search_players(name="silo")` | `client.search_players("silo")` | 10 candidates, including `siloء`, UID 283622404 |
88
+ | `search_player_candidates(name="silo")` | `client.search_players("silo")` | 10 candidates, including `siloء`, UID 283622404 |
89
+ | `get_player_profile(uid=283622404)` | `get_player("283622404")` | Profile overview for the selected numeric UID; not a candidate search |
90
+
91
+ The original MCP `search_players` resolved one account through RivalsData and
92
+ returned `Silo`, UID 1254981449. It now returns the same list as the candidate
93
+ tool. For a selection list, use **`search_players`**, then **`get_player_profile`**
94
+ with the chosen UID. The candidate tool remains a compatibility alias.
95
+ Entering `silo` into a single-account resolver should not be treated as a
96
+ selection of `siloء`.
97
+
98
+ ## Other trackers and APIs checked
99
+
100
+ - [RivalCounter](https://www.rivalcounter.com/) routes player searches to
101
+ filtering its top-500 leaderboard snapshot. Its frontend labels this as
102
+ matching leaderboard entries. The current PC snapshot did not contain this
103
+ UID; this is not a general player-discovery replacement.
104
+ - [MarvelRivalsAPI's documented search](https://docs.marvelrivalsapi.com/search-player-19312750e0)
105
+ is `GET /api/v1/find-player/{username}` and documents one name/UID result and
106
+ an `x-api-key` header. The anonymous `silo` probe returned a gateway error
107
+ (502), so live account coverage was not established.
108
+ - [RivalsIsland](https://api.rivalsisland.com/) documents
109
+ `GET /players/search?name=...` in `/docs/openapi.json`, with a singular
110
+ `player` response. Both anonymous queries returned 401, missing API key.
111
+ Its UI describes exact-name lookup; suggestion coverage is unverified.
112
+ - `mrapi.org` did not resolve in this environment. No current search behavior
113
+ could be verified.
114
+
115
+ ## Recommendation
116
+
117
+ Use the RivalsTracker candidate path for this task. RivalsMeta is a
118
+ confirmed alternative endpoint, and OP.GG also finds the account on its website.
119
+ An additional provider is not needed to produce the requested list today.
120
+
121
+ If “always included” is a product requirement, third-party suggestions alone
122
+ cannot guarantee it. Persist the user's verified association between the alias
123
+ `silo` and UID 283622404, then merge that known account with live suggestions,
124
+ deduplicating by UID and preserving the displayed name `siloء`. Keep aliases
125
+ scoped to the user or application that chose them; do not globally equate `Silo`
126
+ with `siloء`. Resolve future names through the stored UID when available, and
127
+ retain a clearly labeled saved candidate during upstream outages.
128
+
129
+ The follow-up MCP change routes `search_players` to the existing candidate
130
+ search and adds the UID-only profile tool. A persistent alias index is not
131
+ implemented.
@@ -17,7 +17,7 @@ implemented feature. Provider APIs are unofficial/undocumented and may change.
17
17
 
18
18
  - Distribution: `rivals-api`; preferred import: `rivals_api`.
19
19
  - Compatibility: `rivalsdata` and `RivalsDataClient` remain aliases.
20
- - Version: `2.0.0`.
20
+ - Version: `3.0.0`.
21
21
  - Python `>=3.10`, Hatchling build, `src/` layout.
22
22
  - Runtime HTTP dependency: `curl-cffi`; optional browser fallback: Camoufox.
23
23
  - Public entry point: `RivalsClient`.
@@ -67,11 +67,40 @@ details, leaderboards, heroes, team-ups, insights, factions, profiles and
67
67
  favorites. New analytics and public community resources are documented in
68
68
  `docs/PROVIDER_INTEGRATION.md` and the feature-gap inventory.
69
69
 
70
+ History rows carry their originating client and expose `match.get_details()`.
71
+ Both bounded/all fetches and iteration return these bound `Match` objects. The
72
+ method returns a new typed full `Match` through `client.matches.get(...)`, with
73
+ `refresh=True` available to bypass detail/provider caches. The client reference
74
+ is excluded from model serialization. Keep it open while fetching details.
75
+ The shared fetcher in `match_details.py` validates provider match IDs, preserves
76
+ raw responses and errors, merges roster/hero records by identity, and exposes
77
+ per-field source decisions and completeness. Ambiguous player bridges remain
78
+ unmatched. Accuracy percent is distinct from session hit rate; raw RD hero
79
+ accuracy retains ratio units. Detail caches are scoped to the enrichment setting;
80
+ partial cached evidence remains usable for request-free calculations but is
81
+ retried by detail lookups.
82
+
83
+ Match models resolve map, queue, gameplay, platform, rank, season, and hero
84
+ references offline from packaged provider catalogs. References format as names
85
+ and expose `.id`; the original numeric fields remain except `match.platform`,
86
+ which is a typed Platform with `.platform_id` providing the previous scalar.
87
+ Gameplay labels come from the map/objective context rather than guessing a
88
+ global interpretation of `game_play_mode_id`. Unknown codes stay explicit.
89
+ The catalog covers 118 map variants and 20 observed seasons; source evidence
90
+ and serialization/migration details are in `docs/GAME_REFERENCES.md`.
91
+
70
92
  The MCP server exposes read-only tools over stdio or Streamable HTTP. It does
71
93
  not implement authentication; remote deployments must supply an authenticated
72
94
  gateway. The dashboard shows a snapshot and performs provider requests when a
73
95
  tool runs.
74
96
 
97
+ MCP `search_players(name)` returns a list of RivalsTracker candidates with names
98
+ and numeric game UIDs. `get_player_profile(uid)` retrieves the chosen profile
99
+ by positive integer UID, including its current-season competitive win rate.
100
+ `search_player_candidates` is an alias for the list search; the existing
101
+ `get_player(uid_or_name)` tool remains available. The search tool previously
102
+ resolved one account, so callers must migrate to handling list results.
103
+
75
104
  ## Development and release
76
105
 
77
106
  Create an isolated virtual environment and install `.[dev]`. Install