diff --git a/docs/api.rst b/docs/api.rst index 0dcaa15..424b1e5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -29,6 +29,9 @@ All enumerations are subclasses of `enum.Enum`. .. autoclass:: rlapi.Platform :members: +.. autoclass:: rlapi.Stat + :members: + Rocket League API Models ------------------------ @@ -47,6 +50,18 @@ and are not meant to be created by the user of the library. .. autoclass:: rlapi.Playlist :members: +.. autoclass:: rlapi.SkillLeaderboard + :members: + +.. autoclass:: rlapi.SkillLeaderboardPlayer + :members: + +.. autoclass:: rlapi.StatLeaderboard + :members: + +.. autoclass:: rlapi.StatLeaderboardPlayer + :members: + .. autoclass:: rlapi.tier_estimates.TierEstimates :members: diff --git a/rlapi/__init__.py b/rlapi/__init__.py index a8d1f69..e506e08 100644 --- a/rlapi/__init__.py +++ b/rlapi/__init__.py @@ -22,7 +22,11 @@ from . import errors as errors # noqa from .client import Client as Client # noqa -from .enums import Platform as Platform, PlaylistKey as PlaylistKey # noqa +from .enums import ( # noqa + Platform as Platform, + PlaylistKey as PlaylistKey, + Stat as Stat, +) from .errors import ( # noqa HTTPException as HTTPException, IllegalUsername as IllegalUsername, @@ -30,6 +34,12 @@ RLApiException as RLApiException, Unauthorized as Unauthorized, ) +from .leaderboard import ( # noqa + SkillLeaderboard as SkillLeaderboard, + SkillLeaderboardPlayer as SkillLeaderboardPlayer, + StatLeaderboard as StatLeaderboard, + StatLeaderboardPlayer as StatLeaderboardPlayer, +) from .player import ( # noqa DIVISIONS as DIVISIONS, PLAYLISTS_WITH_SEASON_REWARDS as PLAYLISTS_WITH_SEASON_REWARDS, @@ -57,12 +67,18 @@ # enums "Platform", "PlaylistKey", + "Stat", # errors "HTTPException", "IllegalUsername", "PlayerNotFound", "RLApiException", "Unauthorized", + # leaderboard + "SkillLeaderboard", + "SkillLeaderboardPlayer", + "StatLeaderboard", + "StatLeaderboardPlayer", # player "DIVISIONS", "PLAYLISTS_WITH_SEASON_REWARDS", diff --git a/rlapi/client.py b/rlapi/client.py index 9157ded..27b710c 100644 --- a/rlapi/client.py +++ b/rlapi/client.py @@ -37,7 +37,8 @@ from . import errors from ._utils import TokenInfo, json_or_text -from .enums import Platform +from .enums import PlaylistKey, Platform, Stat +from .leaderboard import SkillLeaderboard, StatLeaderboard from .player import Player from .typedefs import TierBreakdownType @@ -191,12 +192,10 @@ async def _rlapi_request( *, params: Optional[Dict[str, str]] = None, force_refresh_token: bool = False, - ) -> List[Dict[str, Any]]: + ) -> Any: url = self.RLAPI_BASE + endpoint token = await self._get_access_token(force_refresh=force_refresh_token) headers = {"Authorization": f"Bearer {token}"} - # RL API returns JSON object on success - data: List[Dict[str, Any]] try: data = await self._request(url, headers, params=params) except errors.Unauthorized: @@ -585,3 +584,57 @@ async def _find_steam_ids(self, match: Match[str]) -> List[str]: ) return ids + + async def get_skill_leaderboard( + self, platform: Platform, playlist_key: PlaylistKey + ) -> SkillLeaderboard: + """ + Get skill leaderboard for the playlist on the given platform. + + Parameters + ---------- + platform: Platform + Platform to get the leaderboard for. + playlist_key: PlaylistKey + Playlist to get the leaderboard for. + + Returns + ------- + SkillLeaderboard + Skill leaderboard for the playlist on the given platform. + + Raises + ------ + HTTPException + HTTP request to Rocket League failed. + """ + endpoint = f"/leaderboard/skill/{platform.value}/{playlist_key.value}" + data = await self._rlapi_request(endpoint) + return SkillLeaderboard(platform, playlist_key, data) + + async def get_stat_leaderboard( + self, platform: Platform, stat: Stat + ) -> StatLeaderboard: + """ + Get leaderboard for the specified stat on the given platform. + + Parameters + ---------- + platform: Platform + Platform to get the leaderboard for. + stat: Stat + Stat to get the leaderboard for. + + Returns + ------- + StatLeaderboard + Leaderboard for the specified stat on the given platform. + + Raises + ------ + HTTPException + HTTP request to Rocket League failed. + """ + endpoint = f"/leaderboard/stat/{platform.value}/{stat.value}" + data = await self._rlapi_request(endpoint) + return StatLeaderboard(platform, stat, data) diff --git a/rlapi/enums.py b/rlapi/enums.py index 5f11585..901915b 100644 --- a/rlapi/enums.py +++ b/rlapi/enums.py @@ -73,6 +73,23 @@ def __str__(self) -> str: return _PLATFORM_FRIENDLY_NAMES[self] +class Stat(Enum): + """Represents player stat.""" + + #: Assists. + assists = "Assists" + #: Goals. + goals = "Goals" + #: MVPs. + mvps = "MVPs" + #: Saves. + saves = "Saves" + #: Shots. + shots = "Shots" + #: Wins. + wins = "Wins" + + _PLATFORM_FRIENDLY_NAMES = { Platform.steam: "Steam", Platform.ps4: "PlayStation 4", diff --git a/rlapi/leaderboard.py b/rlapi/leaderboard.py new file mode 100644 index 0000000..5ada574 --- /dev/null +++ b/rlapi/leaderboard.py @@ -0,0 +1,157 @@ +from typing import Any, Dict, Optional + +from .enums import Platform, PlaylistKey, Stat + +__all__ = ( + "SkillLeaderboardPlayer", + "SkillLeaderboard", + "StatLeaderboardPlayer", + "StatLeaderboard", +) + + +class SkillLeaderboardPlayer: + """SkillLeaderboardPlayer() + Represents Rocket League Player on a platform's skill leaderboard. + + Attributes + ---------- + platform: Platform + Platform that this leaderboard entry refers to. + playlist_key: PlaylistKey + Playlist that this leaderboard entry refers to. + user_id: str, optional + Player's user ID. + Only present for Steam and Epic Games players. + user_name: str + Player's username (display name). + tier: int + Player's tier on the specified playlist. + skill: int + Player's skill rating on the specified playlist. + + """ + + __slots__ = ("platform", "playlist_key", "user_name", "user_id", "tier", "skill") + + def __init__( + self, + platform: Platform, + playlist_key: PlaylistKey, + data: Dict[str, Any], + ) -> None: + self.platform = platform + self.playlist_key = playlist_key + self.user_name: str = data["user_name"] + self.user_id: Optional[str] = data.get("user_id") + if ( + self.user_id is not None + and self.user_id.startswith(f"{platform.value}|") + and self.user_id.endswith("|0") + ): + self.user_id = self.user_id[len(platform.value) + 1 : -2] + self.tier: int = data["tier"] + self.skill: int = data["skill"] + + +class SkillLeaderboard: + """SkillLeaderboard() + Represents Rocket League playlist's skill leaderboard for a single platform. + + Attributes + ---------- + platform: Platform + Platform that this leaderboard refers to. + playlist_key: PlaylistKey + Playlist that this leaderboard refers to. + players: list of `StatLeaderboardPlayer` + List of playlist's top 100 players on the platform. + + """ + + __slots__ = ("platform", "playlist_key", "players") + + def __init__( + self, + platform: Platform, + playlist_key: PlaylistKey, + data: Dict[str, Any], + ) -> None: + self.platform = platform + self.playlist_key = playlist_key + self.players = [ + SkillLeaderboardPlayer(platform, playlist_key, player_data) + for player_data in data["leaderboard"] + ] + + +class StatLeaderboardPlayer: + """StatLeaderboardPlayer() + Represents Rocket League Player on a platform's stat leaderboard. + + Attributes + ---------- + platform: Platform + Platform that this leaderboard entry refers to. + stat: Stat + Stat that this leaderboard entry refers to. + user_id: str, optional + Player's user ID. + Only present for Steam and Epic Games players. + user_name: str + Player's username (display name). + value: int + Value of the specified stat for the player. + + """ + + __slots__ = ("platform", "stat", "user_name", "user_id", "value") + + def __init__( + self, + platform: Platform, + stat: Stat, + data: Dict[str, Any], + ) -> None: + self.platform = platform + self.stat = stat + self.user_name: str = data["user_name"] + self.user_id: Optional[str] = data.get("user_id") + if ( + self.user_id is not None + and self.user_id.startswith(f"{platform.value}|") + and self.user_id.endswith("|0") + ): + self.user_id = self.user_id[len(platform.value) + 1 : -2] + self.value: int = data[stat.value] + + +class StatLeaderboard: + """StatLeaderboard() + Represents Rocket League stat leaderboard for a single platform. + + Attributes + ---------- + platform: Platform + Platform that this leaderboard refers to. + stat: Stat + Stat that this leaderboard refers to. + players: list of `StatLeaderboardPlayer` + List of stat's top 100 players on the platform. + + """ + + __slots__ = ("platform", "stat", "players") + + def __init__( + self, + platform: Platform, + stat: Stat, + data: Dict[str, Any], + ) -> None: + self.platform = platform + self.stat = stat + self.players = [ + StatLeaderboardPlayer(platform, stat, player_data) + for player_data in data[stat.value] + ] diff --git a/rlapi/player.py b/rlapi/player.py index 83673fe..99f2aab 100644 --- a/rlapi/player.py +++ b/rlapi/player.py @@ -15,7 +15,7 @@ import contextlib from typing import Any, Dict, Final, List, Optional, Union -from .enums import Platform, PlaylistKey +from .enums import Platform, PlaylistKey, Stat from .tier_estimates import TierEstimates from .typedefs import PlaylistBreakdownType, TierBreakdownType @@ -251,6 +251,11 @@ class PlayerStats: """PlayerStats() Represents player stats (assists, goals, MVPs, etc.). + .. container:: operations + + ``x[key]`` + Lookup player's stat value by `Stat` enum. + Attributes ---------- assists: int @@ -285,10 +290,8 @@ def __init__(self, data: List[Dict[str, Any]]) -> None: self.shots: int = stats.get("shots", 0) self.wins: int = stats.get("wins", 0) - def __getitem__(self, key: str) -> int: - if key not in self.__slots__: - raise KeyError(key) - return int(getattr(self, key)) + def __getitem__(self, stat: Stat) -> int: + return int(getattr(self, stat.name)) def __repr__(self) -> str: attrs = " ".join(f"{key}={getattr(self, key)}" for key in self.__slots__)