Skip to content

Commit

Permalink
Merge pull request #28 from CNDRD/uhh
Browse files Browse the repository at this point in the history
New version time
  • Loading branch information
CNDRD authored Apr 20, 2024
2 parents ab0c860 + d204a5a commit 80bf379
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 99 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dist/

serviceAccountKey.json
testing.py
wss.py
creds/

.DS_Store
Expand Down
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ from siegeapi import Auth
import asyncio

async def sample():
auth = Auth("UBI_EMAIL", "UBI_PASSWORD")
player = await auth.get_player(uid="7e0f63df-a39b-44c5-8de0-d39a05926e77")
auth = Auth(UBISOFT_EMAIL, UBISOFT_PASSW)
player = await auth.get_player(name="CNDRD")

print(f"Name: {player.name}")
print(f"Profile pic URL: {player.profile_pic_url}")


await player.load_persona()
print(f"Streamer nickname: {player.persona.nickname}")
print(f"Nickname enabled: {player.persona.enabled}")

await player.load_playtime()
print(f"Total Time Played: {player.total_time_played:,} seconds")
print(f"Total Time Played: {player.total_time_played:,} seconds / {player.total_time_played_hours:,} hours")
print(f"Level: {player.level}")

await player.load_ranked_v2()
Expand All @@ -47,25 +51,26 @@ async def sample():
print(f"XP: {player.xp:,}")
print(f"Total XP: {player.total_xp:,}")
print(f"XP to level up: {player.xp_to_level_up:,}")

await auth.close()

asyncio.get_event_loop().run_until_complete(sample())
# Or `asyncio.run(sample())`
asyncio.run(sample())
```
### Output
```text
Name: CNDRD
Profile pic URL: https://ubisoft-avatars.akamaized.net/7e0f63df-a39b-44c5-8de0-d39a05926e77/default_256_256.png
Total Time Played: 9,037,159 seconds
Level: 305
Ranked Points: 4188
Rank: Diamond 4
Max Rank Points: 4289
Max Rank: Diamond 3
XP: 11,858
Total XP: 20,694,358
XP to level up: 131,642
Streamer nickname: d1kCheeze
Nickname enabled: True
Total Time Played: 9,265,890 seconds / 2,573 hours
Level: 308
Ranked Points: 3919
Rank: Emerald 1
Max Rank Points: 3933
Max Rank: Emerald 1
XP: 124,375
Total XP: 21,238,875
XP to level up: 20,625
```

---
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

setup(
name="siegeapi",
version="6.2.5",
version="6.3.0",
url="https://github.com/CNDRD/siegeapi",
description="Rainbow Six Siege API interface",
author="CNDRD",
packages=find_packages(),
license="MIT",
include_package_data=True,
install_requires=["aiohttp>=3.6.0,<3.8.0"],
install_requires=["aiohttp>=3.6.0"],
python_requires=">=3.8.0",
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down
24 changes: 14 additions & 10 deletions siegeapi/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

PLATFORMS = ["uplay", "xbl", "psn"]


class Auth:
""" Holds the authentication information """

Expand All @@ -28,6 +29,7 @@ def __init__(
password: Optional[str] = None,
token: Optional[str] = None,
appid: Optional[str] = None,
spaceids: Optional[Dict[Literal['uplay', 'psn', 'xbl', 'xplay'], str]] = None,
creds_path: Optional[str] = None,
cachetime: int = 120,
max_connect_retries: int = 1,
Expand All @@ -52,10 +54,14 @@ def __init__(
self.new_key: str = ""
self.spaceid: str = ""
self.spaceids: Dict[str, str] = {
"uplay": "0d2ae42d-4c27-4cb7-af6c-2099062302bb",
"psn": "0d2ae42d-4c27-4cb7-af6c-2099062302bb",
"xbl": "0d2ae42d-4c27-4cb7-af6c-2099062302bb"
"uplay": "5172a557-50b5-4665-b7db-e3f2e8c5041d",
"psn": "05bfb3f7-6c21-4c42-be1f-97a33fb5cf66",
"xbl": "98a601e5-ca91-4440-b1c5-753f601a2c90",
"xplay": "0d2ae42d-4c27-4cb7-af6c-2099062302bb"
}
if spaceids:
self.spaceids.update(spaceids)

self.profileid: str = ""
self.userid: str = ""
self.expiration: str = ""
Expand Down Expand Up @@ -91,18 +97,16 @@ async def _find_players(self, name: Optional[str], platform: Literal["uplay", "x
raise TypeError(f"'platform' has to be one of the following: {PLATFORMS}; Not {platform}")

if name:
data = await self.get(f"https://public-ubiservices.ubi.com/v3/profiles?"
f"nameOnPlatform={parse.quote(name)}&platformType={parse.quote(platform)}")
data = await self.get(f"https://public-ubiservices.ubi.com/v3/profiles?nameOnPlatform={parse.quote(name)}&platformType={parse.quote(platform)}")
else:
data = await self.get(f"https://public-ubiservices.ubi.com/v3/users/{uid}/profiles?"
f"platformType={parse.quote(platform)}")
data = await self.get(f"https://public-ubiservices.ubi.com/v3/users/{uid}/profiles?platformType={parse.quote(platform)}")

if not isinstance(data, dict):
await self.close()
raise InvalidRequest(f"Expected a JSON object, got {type(data)}")

if "profiles" in data:
results = [Player(self, x) for x in data.get("profiles",{}) if x.get("platformType", "") == platform]
results = [Player(self, x) for x in data.get("profiles", {}) if x.get("platformType", "") == platform]
if not results:
await self.close()
raise InvalidRequest("No results")
Expand Down Expand Up @@ -181,7 +185,7 @@ def load_creds(self) -> None:

async def connect(self, _new: bool = False) -> None:
"""Connect to the Ubisoft API.
This method will automatically called when needed."""
This method will be automatically called when needed."""
self.load_creds()

if self._login_cooldown > time.time():
Expand Down Expand Up @@ -296,7 +300,7 @@ async def get(self, *args, retries: int = 0, json_: bool = True, new: bool = Fal
if retries >= self.max_connect_retries:
# wait 30 seconds before sending another request
# pyright type checker doesn't like the below line
self._login_cooldown = 30 + time.time() # type: ignore
self._login_cooldown = 30 + time.time() # type: ignore

# key no longer works, so remove key and let the following .get() call refresh it
self.key = None
Expand Down
4 changes: 4 additions & 0 deletions siegeapi/linked_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ def __init__(self, data):
self.platform_type: str = data.get("platformType", "")
self.id_on_platform: str = data.get("idOnPlatform", "")
self.name_on_platform: str = data.get("nameOnPlatform", "")

def __repr__(self) -> str:
return str(vars(self))

11 changes: 7 additions & 4 deletions siegeapi/maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ def __repr__(self) -> str:

class Maps:
def __init__(self, data: dict):
self.all: MapRoles = MapRoles(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("all", {}))
self.casual: MapRoles = MapRoles(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("casual", {}))
self.ranked: MapRoles = MapRoles(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("ranked", {}))
self.unranked: MapRoles = MapRoles(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("unranked", {}))
platform_data = data.get("platforms", {})
platform_data = platform_data.get(list(platform_data.keys())[0] if len(platform_data.keys()) > 0 else "PC", {})

self.all: MapRoles = MapRoles(platform_data.get("gameModes", {}).get("all", {}))
self.casual: MapRoles = MapRoles(platform_data.get("gameModes", {}).get("casual", {}))
self.ranked: MapRoles = MapRoles(platform_data.get("gameModes", {}).get("ranked", {}))
self.unranked: MapRoles = MapRoles(platform_data.get("gameModes", {}).get("unranked", {}))
self._start_date: str = str(data.get("startDate", ""))
self._end_date: str = str(data.get("endDate", ""))

Expand Down
18 changes: 11 additions & 7 deletions siegeapi/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def __init__(self, data: dict, op_about: bool):
def _get_from_operators_const(self, value: str) -> Union[str, int, list]:
return operator_dict.get(self.name.lower(), {}).get(value, "Missing Data")

# for typing
def _get_str_from_operators_const(self, value: str) -> str:
returnv = self._get_from_operators_const(value)
if isinstance(returnv, str):
Expand All @@ -45,7 +44,9 @@ def _get_list_from_operators_const(self, value: str) -> list:
if isinstance(returnv, list):
return returnv
raise ValueError(f"Value {value} is not a list")


def __repr__(self) -> str:
return str(vars(self))


class OperatorsGameMode:
Expand All @@ -59,11 +60,14 @@ def __repr__(self) -> str:

class Operators:
def __init__(self, data: dict, op_about: bool):
self.all: OperatorsGameMode = OperatorsGameMode(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("all", {}), op_about)
self.casual: OperatorsGameMode = OperatorsGameMode(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("casual", {}), op_about)
self.ranked: OperatorsGameMode = OperatorsGameMode(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("ranked", {}), op_about)
self.unranked: OperatorsGameMode = OperatorsGameMode(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("unranked", {}), op_about)
self.newcomer: OperatorsGameMode = OperatorsGameMode(data.get("platforms",{}).get("PC",{}).get("gameModes",{}).get("newcomer", {}), op_about)
platform_data = data.get("platforms", {})
platform_data = platform_data.get(list(platform_data.keys())[0] if len(platform_data.keys()) > 0 else "PC", {})

self.all: OperatorsGameMode = OperatorsGameMode(platform_data.get("gameModes", {}).get("all", {}), op_about)
self.casual: OperatorsGameMode = OperatorsGameMode(platform_data.get("gameModes", {}).get("casual", {}), op_about)
self.ranked: OperatorsGameMode = OperatorsGameMode(platform_data.get("gameModes", {}).get("ranked", {}), op_about)
self.unranked: OperatorsGameMode = OperatorsGameMode(platform_data.get("gameModes", {}).get("unranked", {}), op_about)
self.newcomer: OperatorsGameMode = OperatorsGameMode(platform_data.get("gameModes", {}).get("newcomer", {}), op_about)
self._start_date: str = str(data.get("startDate", ""))
self._end_date: str = str(data.get("endDate", ""))

Expand Down
10 changes: 10 additions & 0 deletions siegeapi/persona.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

class Persona():
def __init__(self, data):
self.tag = data.get('personaTag', None)
self.enabled = data.get('obj', {'Enabled': False}).get('Enabled', False)
self.nickname = data.get('nickname', None)

def __repr__(self) -> str:
return str(vars(self))

63 changes: 51 additions & 12 deletions siegeapi/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@

from .utils import season_code_to_id, get_total_xp, get_xp_to_next_lvl
from .linked_accounts import LinkedAccount
from .exceptions import InvalidRequest
from .rank_profile import FullProfile
from .url_builder import UrlBuilder
from .operators import Operators
from .summaries import Summary
from .persona import Persona
from .weapons import Weapons
from .trends import Trends
from .ranks import Rank
from .maps import Maps
import re

if TYPE_CHECKING:
from .auth import Auth

import re


PLATFORM_URL_NAMES = {"uplay": "OSBOR_PC_LNCH_A", "psn": "OSBOR_PS4_LNCH_A", "xbl": "OSBOR_XBOXONE_LNCH_A", "xplay": "OSBOR_XPLAY_LNCH_A"}
DATE_PATTERN = re.compile(r"^((2\d)\d{2})(0[1-9]|1[012])([012]\d|3[01])$")
ALL_SEASONS = list(range(6, 28))
ALL_SEASONS: list[int] = list(range(6, 28))



class Player:
Expand All @@ -31,16 +35,25 @@ def __init__(self, auth: Auth, data: dict):
self._platform: str = data.get("platformType","")
self._platform_url: str = PLATFORM_URL_NAMES[self._platform]
self._spaceid: str = self._auth.spaceids[self._platform]
self._spaceids: Dict[str, str] = self._auth.spaceids
self._platform_group: str = "PC" if self._platform == "uplay" else "Console"
self._url_builder: UrlBuilder = UrlBuilder(self._spaceid, self._platform_url, self.uid, self._platform_group)
self._url_builder: UrlBuilder = UrlBuilder(
spaceid=self._spaceid,
spaceids=self._spaceids,
platform_url=self._platform_url,
platform_group=self._platform_group,
player_uid=self.uid,
player_id=self.id,
)

self.profile_pic_url_146: str = f"https://ubisoft-avatars.akamaized.net/{self.uid}/default_146_146.png"
self.profile_pic_url_256: str = f"https://ubisoft-avatars.akamaized.net/{self.uid}/default_256_256.png"
self.profile_pic_url_500: str = f"https://ubisoft-avatars.akamaized.net/{self.uid}/default_tall.png"
self.profile_pic_url: str = self.profile_pic_url_256
self.linked_accounts: List[LinkedAccount] = []

self.name: str = data.get("nameOnPlatform","")
self.name: str = data.get("nameOnPlatform", "")
self.persona: Optional[Persona] = None
self.level: int = 0
self.alpha_pack: float = 0
self.xp: int = 0
Expand All @@ -53,13 +66,14 @@ def __init__(self, auth: Auth, data: dict):
self.pve_time_played: int = 0

self.rank_skill_records: Dict[int, Dict[str, Optional[Rank]]] | Dict = {}
self.casual_skill_records: Dict[int, Dict[str,Optional[Rank]]] | Dict = {}
self.casual_skill_records: Dict[int, Dict[str, Optional[Rank]]] | Dict = {}

self.ranked_summary: dict = {}
self.casual_summary: dict = {}
self.unranked_summary: dict = {}
self.all_summary: dict = {}

self.standard_profile: Optional[FullProfile] = None
self.unranked_profile: Optional[FullProfile] = None
self.ranked_profile: Optional[FullProfile] = None
self.casual_profile: Optional[FullProfile] = None
Expand Down Expand Up @@ -142,7 +156,7 @@ async def load_playtime(self) -> None:
self.total_time_played = int(stats.get("PTotalTimePlayed", {}).get("value", 0))
self.total_time_played_hours = self.total_time_played // 3600 if self.total_time_played else 0

async def load_skill_records(self, seasons: List[int] = ALL_SEASONS, boards: List[str] = ["pvp_ranked","pvp_casual"], regions: List[str] = ["emea","ncsa","apac","global"]) -> None:
async def load_skill_records(self, seasons: List[int] = ALL_SEASONS, boards: List[str] = ["pvp_ranked", "pvp_casual"], regions: List[str] = ["emea","ncsa","apac","global"]) -> None:
"""Loads the player's skill records.
Can get data only for seasons 6 (Health - Y2S2) until 27 (Brutal Swarm - Y7S3) because of ranked 2.0
Expand Down Expand Up @@ -187,7 +201,7 @@ async def load_skill_records(self, seasons: List[int] = ALL_SEASONS, boards: Lis
self.casual_skill_records.setdefault(season_id, {})
self.casual_skill_records[season_id][region_id] = rank_obj if played else None

async def load_summaries(self, gamemodes: List[str] = ["all","ranked","unranked","casual"], team_roles: List[str] = ['all', 'Attacker', 'Defender']) -> None:
async def load_summaries(self, gamemodes: List[str] = ["all", "ranked", "unranked", "casual"], team_roles: List[str] = ['all', 'Attacker', 'Defender']) -> None:
"""Loads the player's seasonal summaries.
Note that the summaries are not returned, they must be accessed via their attributes (ranked_summary, unranked_summary, casual_summary, all_summary)
Expand All @@ -197,14 +211,15 @@ async def load_summaries(self, gamemodes: List[str] = ["all","ranked","unranked"
Raises:
ValueError: If the API response is not valid.
"""
"""
_gamemodes: str = ",".join(gamemodes)
_team_roles: str = ",".join(team_roles)
data = await self._auth.get(self._url_builder.seasonal_summaries(_gamemodes, _team_roles))

if not isinstance(data, dict):
raise ValueError(f"Failed to load summaries. Response: {data}")

data_gamemodes = data.get('profileData',{}).get(self.uid,{}).get("platforms",{}).get(self._platform_group,{}).get("gameModes",[])
data_gamemodes = data.get('profileData', {}).get(self.uid, {}).get("platforms", {}).get(self._platform_group, {}).get("gameModes", [])

for gamemode in data_gamemodes:
roles = data_gamemodes[gamemode]['teamRoles']
Expand Down Expand Up @@ -307,13 +322,15 @@ async def load_ranked_v2(self) -> Tuple[Optional[FullProfile], Optional[FullProf

data = await self._auth.get(self._url_builder.full_profiles(), new=True)
if not isinstance(data, dict):
raise ValueError(f"Failed to load full profiles. Response: {data}") # maybe return None instead?
raise ValueError(f"Failed to load full profiles. Response: {data}") # maybe return None instead?

boards = data.get('platform_families_full_profiles', [])[0].get('board_ids_full_profiles', [])

for board in boards:

if board.get('board_id') == 'unranked':
if board.get('board_id') == 'standard':
self.standard_profile = FullProfile(board.get('full_profiles', [])[0])
elif board.get('board_id') == 'unranked':
self.unranked_profile = FullProfile(board.get('full_profiles', [])[0])
elif board.get('board_id') == 'ranked':
self.ranked_profile = FullProfile(board.get('full_profiles', [])[0])
Expand All @@ -324,4 +341,26 @@ async def load_ranked_v2(self) -> Tuple[Optional[FullProfile], Optional[FullProf
elif board.get('board_id') == 'event':
self.event_profile = FullProfile(board.get('full_profiles', [])[0])

return (self.unranked_profile, self.ranked_profile, self.casual_profile, self.warmup_profile, self.event_profile)
return self.unranked_profile, self.ranked_profile, self.casual_profile, self.warmup_profile, self.event_profile

async def load_persona(self) -> Persona:
""""Loads the player's streamer nickname.
Raises:
ValueError: If the API response is not valid.
Returns:
Persona: The player's persona.
"""

# Ubi throws a 404 at us if the user doesn't have a persona set (or never set it, idk)
try:
data = await self._auth.get(self._url_builder.persona())
except InvalidRequest as e:
data = {}

if not isinstance(data, dict):
raise ValueError(f"Failed to load persona. Response: {data}")

self.persona = Persona(data)
return self.persona
Loading

0 comments on commit 80bf379

Please sign in to comment.