From 9aaaec2d88c2e52892484665293eb395510a6a7b Mon Sep 17 00:00:00 2001 From: omg-xtao <100690902+omg-xtao@users.noreply.github.com> Date: Fri, 9 Jun 2023 22:55:51 +0800 Subject: [PATCH] :sparkles: Add more starrail func --- simnet/client/base.py | 3 +- simnet/client/components/chronicle/base.py | 2 +- .../client/components/chronicle/starrail.py | 65 +++++++++++-- simnet/models/starrail/character.py | 34 ++++--- simnet/models/starrail/chronicle/base.py | 18 ++++ simnet/models/starrail/chronicle/challenge.py | 44 +++++++++ .../models/starrail/chronicle/characters.py | 84 ++++------------ simnet/models/starrail/chronicle/rogue.py | 96 +++++++++++++++++++ simnet/models/starrail/chronicle/stats.py | 52 ++++------ simnet/utils/player.py | 2 +- .../test_starrail_battle_chronicle_client.py | 10 ++ 11 files changed, 294 insertions(+), 116 deletions(-) create mode 100644 simnet/models/starrail/chronicle/base.py create mode 100644 simnet/models/starrail/chronicle/challenge.py create mode 100644 simnet/models/starrail/chronicle/rogue.py diff --git a/simnet/client/base.py b/simnet/client/base.py index 1168a38..8f69787 100644 --- a/simnet/client/base.py +++ b/simnet/client/base.py @@ -69,7 +69,8 @@ class BaseClient(AsyncContextManager["BaseClient"]): write=5.0, pool=1.0, ) - cookies = Cookies(parse_cookie(cookies)) if isinstance(cookies, str) else cookies + cookies = parse_cookie(cookies) if isinstance(cookies, str) else cookies + cookies = Cookies(cookies) self.headers = Headers(headers) self.player_id = player_id self.account_id = account_id or cookies.account_id diff --git a/simnet/client/components/chronicle/base.py b/simnet/client/components/chronicle/base.py index 28349a4..73e1fc1 100644 --- a/simnet/client/components/chronicle/base.py +++ b/simnet/client/components/chronicle/base.py @@ -87,7 +87,7 @@ class BaseChronicleClient(BaseClient): if game is None and switch_id == 3: game = Game.GENSHIN - game_id = {Game.HONKAI: 1, Game.GENSHIN: 2, Game.STARRAIL: 3}[game] + game_id = {Game.HONKAI: 1, Game.GENSHIN: 2, Game.STARRAIL: 6}[game] await self.request_game_record( "card/wapi/changeDataSwitch", diff --git a/simnet/client/components/chronicle/starrail.py b/simnet/client/components/chronicle/starrail.py index 8baf106..c05395f 100644 --- a/simnet/client/components/chronicle/starrail.py +++ b/simnet/client/components/chronicle/starrail.py @@ -4,9 +4,11 @@ from typing import Optional, Mapping, Dict, Any from simnet.client.components.chronicle.base import BaseChronicleClient from simnet.errors import BadRequest, DataNotPublic from simnet.models.lab.record import RecordCard -from simnet.models.starrail.chronicle.characters import StarShipDetailCharacters +from simnet.models.starrail.chronicle.challenge import StarRailChallenge +from simnet.models.starrail.chronicle.characters import StarRailDetailCharacters from simnet.models.starrail.chronicle.notes import StarRailNote -from simnet.models.starrail.chronicle.stats import StarRailUserStats +from simnet.models.starrail.chronicle.rogue import StarRailRogue +from simnet.models.starrail.chronicle.stats import StarRailUserStats, StarRailUserInfo from simnet.utils.enum_ import Game from simnet.utils.player import recognize_starrail_server, recognize_region @@ -92,6 +94,7 @@ class StarRailBattleChronicleClient(BaseChronicleClient): raise BadRequest(e.response, "Cannot view real-time notes of other users.") from e if not autoauth: raise BadRequest(e.response, "Real-time notes are not enabled.") from e + await self.update_settings(3, True, game=Game.STARRAIL) data = await self._request_starrail_record("note", player_id, lang=lang) return StarRailNote(**data) @@ -119,14 +122,14 @@ class StarRailBattleChronicleClient(BaseChronicleClient): self._request_starrail_record("index", player_id, lang=lang), self._request_starrail_record("role/basicInfo", player_id, lang=lang), ) - index_data["info"] = basic_info - return StarRailUserStats(**index_data) + basic_data = StarRailUserInfo(**basic_info) + return StarRailUserStats(**index_data, info=basic_data) async def get_starrail_characters( self, player_id: Optional[int] = None, lang: Optional[str] = None, - ) -> StarShipDetailCharacters: + ) -> StarRailDetailCharacters: """Get StarRail character information. Args: @@ -134,7 +137,7 @@ class StarRailBattleChronicleClient(BaseChronicleClient): lang (Optional[str], optional): The language of the data. Defaults to None. Returns: - StarShipDetailCharacters: The requested character information. + StarRailDetailCharacters: The requested character information. Raises: BadRequest: If the request is invalid. @@ -142,7 +145,7 @@ class StarRailBattleChronicleClient(BaseChronicleClient): """ payload = {"need_wiki": "true"} data = await self._request_starrail_record("avatar/info", player_id, lang=lang, payload=payload) - return StarShipDetailCharacters(**data) + return StarRailDetailCharacters(**data) async def get_record_card( self, @@ -171,3 +174,51 @@ class StarRailBattleChronicleClient(BaseChronicleClient): return record_card return None + + async def get_starrail_challenge( + self, + player_id: Optional[int] = None, + previous: bool = False, + lang: Optional[str] = None, + ) -> StarRailChallenge: + """Get starrail challenge runs. + + Args: + player_id (Optional[int], optional): The player ID. Defaults to None. + previous (bool, optional): Whether to get previous runs. Defaults to False. + lang (Optional[str], optional): The language of the data. Defaults to None. + + Returns: + StarRailChallenge: The requested challenge runs. + + Raises: + BadRequest: If the request is invalid. + DataNotPublic: If the requested data is not public. + """ + payload = dict(schedule_type=2 if previous else 1, need_all="true") + data = await self._request_starrail_record("challenge", player_id, lang=lang, payload=payload) + return StarRailChallenge(**data) + + async def get_starrail_rogue( + self, + player_id: Optional[int] = None, + schedule_type: int = 3, + lang: Optional[str] = None, + ) -> StarRailRogue: + """Get starrail rogue runs. + + Args: + player_id (Optional[int], optional): The player ID. Defaults to None. + schedule_type (int, optional): The schedule type. Defaults to 3. + lang (Optional[str], optional): The language of the data. Defaults to None. + + Returns: + StarRailRogue: The requested rogue runs. + + Raises: + BadRequest: If the request is invalid. + DataNotPublic: If the requested data is not public. + """ + payload = dict(schedule_type=schedule_type, need_detail="true") + data = await self._request_starrail_record("rogue", player_id, lang=lang, payload=payload) + return StarRailRogue(**data) diff --git a/simnet/models/starrail/character.py b/simnet/models/starrail/character.py index 829e1a5..2e2665b 100644 --- a/simnet/models/starrail/character.py +++ b/simnet/models/starrail/character.py @@ -1,19 +1,31 @@ +"""Starrail base character model.""" from simnet.models.base import APIModel -class BaseCharacter(APIModel): - """ - A class representing a character in a game. - - Attributes: - id (:obj:`int`): The unique identifier of the character. - name (:obj:`str`): The name of the character. - element (:obj:`str`): The element that the character represents (e.g. fire, water, etc.). - rarity (:obj:`int`): The rarity of the character (e.g. 1-5 stars). - """ +class StarRailBaseCharacter(APIModel): + """Base character model.""" id: int - name: str element: str rarity: int icon: str + + +class StarRailPartialCharacter(StarRailBaseCharacter): + """Character without any equipment.""" + + name: str + level: int + rank: int + + +class FloorCharacter(StarRailBaseCharacter): + """Character in a floor.""" + + level: int + + +class RogueCharacter(StarRailBaseCharacter): + """Rogue character model.""" + + level: int diff --git a/simnet/models/starrail/chronicle/base.py b/simnet/models/starrail/chronicle/base.py new file mode 100644 index 0000000..2d05b01 --- /dev/null +++ b/simnet/models/starrail/chronicle/base.py @@ -0,0 +1,18 @@ +"""Starrail Chronicle Base Model.""" +import datetime + +from simnet.models.base import APIModel + + +class PartialTime(APIModel): + """Partial time model.""" + + year: int + month: int + day: int + hour: int + minute: int + + @property + def datetime(self) -> datetime.datetime: + return datetime.datetime(self.year, self.month, self.day, self.hour, self.minute) diff --git a/simnet/models/starrail/chronicle/challenge.py b/simnet/models/starrail/chronicle/challenge.py new file mode 100644 index 0000000..c573dd0 --- /dev/null +++ b/simnet/models/starrail/chronicle/challenge.py @@ -0,0 +1,44 @@ +"""Starrail chronicle challenge.""" +from typing import List + +from pydantic import Field + +from simnet.models.base import APIModel +from simnet.models.starrail.character import FloorCharacter + +from .base import PartialTime + +__all__ = ["StarRailFloor", "FloorNode", "StarRailChallenge"] + + +class FloorNode(APIModel): + """Node for a floor.""" + + challenge_time: PartialTime + avatars: List[FloorCharacter] + + +class StarRailFloor(APIModel): + """Floor in a challenge.""" + + name: str + round_num: int + star_num: int + node_1: FloorNode + node_2: FloorNode + is_chaos: bool + + +class StarRailChallenge(APIModel): + """Challenge in a season.""" + + season: int = Field(alias="schedule_id") + begin_time: PartialTime + end_time: PartialTime + + total_stars: int = Field(alias="star_num") + max_floor: str + total_battles: int = Field(alias="battle_num") + has_data: bool + + floors: List[StarRailFloor] = Field(alias="all_floor_detail") diff --git a/simnet/models/starrail/chronicle/characters.py b/simnet/models/starrail/chronicle/characters.py index 4978df2..0772ec0 100644 --- a/simnet/models/starrail/chronicle/characters.py +++ b/simnet/models/starrail/chronicle/characters.py @@ -1,32 +1,21 @@ -from typing import Optional, List +"""Starrail chronicle character.""" +from typing import List, Optional from simnet.models.base import APIModel -from simnet.models.starrail.character import BaseCharacter + +from .. import character + +__all__ = [ + "StarRailEquipment", + "Rank", + "Relic", + "StarRailDetailCharacter", + "StarRailDetailCharacters", +] -class PartialCharacter(BaseCharacter): - """A character without any equipment. - - Attributes: - level (int): The level of the character. - rank (int): The rank of the character. - """ - - level: int - rank: int - - -class Equipment(APIModel): - """An equipment model used in StarRailDetailCharacter. - - Attributes: - id (int): The ID of the equipment. - level (int): The level of the equipment. - rank (int): The rank of the equipment. - name (str): The name of the equipment. - desc (str): The description of the equipment. - icon (str): The icon of the equipment. - """ +class StarRailEquipment(APIModel): + """Character equipment.""" id: int level: int @@ -37,17 +26,7 @@ class Equipment(APIModel): class Relic(APIModel): - """A relic model used in StarRailDetailCharacter. - - Attributes: - id (int): The ID of the relic. - level (int): The level of the relic. - pos (int): The position of the relic. - name (str): The name of the relic. - desc (str): The description of the relic. - icon (str): The icon of the relic. - rarity (int): The rarity of the relic. - """ + """Character relic.""" id: int level: int @@ -59,16 +38,7 @@ class Relic(APIModel): class Rank(APIModel): - """A rank model used in StarRailDetailCharacter. - - Attributes: - id (int): The ID of the rank. - pos (int): The position of the rank. - name (str): The name of the rank. - icon (str): The icon of the rank. - desc (str): The description of the rank. - is_unlocked (bool): Whether the rank is unlocked. - """ + """Character rank.""" id: int pos: int @@ -78,29 +48,17 @@ class Rank(APIModel): is_unlocked: bool -class StarRailDetailCharacter(PartialCharacter): - """A detailed character model used in StarShipDetailCharacters. - - Attributes: - image (str): The image of the character. - equip (Optional[Equipment]): The equipment of the character, if any. - relics (List[Relic]): The relics of the character. - ornaments (List[Relic]): The ornaments of the character. - ranks (List[Rank]): The ranks of the character. - """ +class StarRailDetailCharacter(character.StarRailPartialCharacter): + """StarRail character with equipment and relics.""" image: str - equip: Optional[Equipment] + equip: Optional[StarRailEquipment] relics: List[Relic] ornaments: List[Relic] ranks: List[Rank] -class StarShipDetailCharacters(APIModel): - """A model containing a list of detailed characters used in the StarShipDetail API. - - Attributes: - avatar_list (List[StarRailDetailCharacter]): The list of detailed characters. - """ +class StarRailDetailCharacters(APIModel): + """StarRail characters.""" avatar_list: List[StarRailDetailCharacter] diff --git a/simnet/models/starrail/chronicle/rogue.py b/simnet/models/starrail/chronicle/rogue.py new file mode 100644 index 0000000..b2d2d75 --- /dev/null +++ b/simnet/models/starrail/chronicle/rogue.py @@ -0,0 +1,96 @@ +"""Starrail Rogue models.""" +from typing import List + +from simnet.models.base import APIModel + +from ..character import RogueCharacter +from .base import PartialTime + + +class RogueUserRole(APIModel): + """Rogue User info.""" + + nickname: str + server: str + level: int + + +class RogueBasicInfo(APIModel): + """generalized rogue basic info.""" + + unlocked_buff_num: int + unlocked_miracle_num: int + unlocked_skill_points: int + + +class RogueRecordBasic(APIModel): + """Basic record info.""" + + id: int + finish_cnt: int + schedule_begin: PartialTime + schedule_end: PartialTime + + +class RogueBuffType(APIModel): + """Rogue buff type.""" + + id: int + name: str + cnt: int + + +class RogueBuffItem(APIModel): + """Rogue buff item.""" + + id: int + name: str + is_evoluted: bool + rank: int + + +class RogueBuff(APIModel): + """Rogue buff info.""" + + base_type: RogueBuffType + items: List[RogueBuffItem] + + +class RogueMiracle(APIModel): + """Rogue miracle info.""" + + id: int + name: str + icon: str + + +class RogueRecordDetail(APIModel): + """Detailed record info.""" + + name: str + finish_time: PartialTime + score: int + final_lineup: List[RogueCharacter] + base_type_list: List[RogueBuffType] + cached_avatars: List[RogueCharacter] + buffs: List[RogueBuff] + miracles: List[RogueMiracle] + difficulty: int + progress: int + + +class RogueRecord(APIModel): + """generic record data.""" + + basic: RogueRecordBasic + records: List[RogueRecordDetail] + has_data: bool + + +class StarRailRogue(APIModel): + """generic rogue data.""" + + role: RogueUserRole + basic_info: RogueBasicInfo + current_record: RogueRecord + last_record: RogueRecord diff --git a/simnet/models/starrail/chronicle/stats.py b/simnet/models/starrail/chronicle/stats.py index 5069e96..b8a8e28 100644 --- a/simnet/models/starrail/chronicle/stats.py +++ b/simnet/models/starrail/chronicle/stats.py @@ -1,20 +1,22 @@ -from typing import List +"""Starrail chronicle stats.""" +import typing + from pydantic import Field + from simnet.models.base import APIModel -from simnet.models.starrail.chronicle.characters import PartialCharacter + +from .. import character + +__all__ = [ + "PartialStarRailUserStats", + "StarRailUserInfo", + "StarRailUserStats", + "StarRailStats", +] -class Stats(APIModel): - """ - Statistics of user data. - - Attributes: - active_days (int): Number of days the user has been active. - avatar_num (int): Number of avatars the user has. - achievement_num (int): Number of achievements the user has earned. - chest_num (int): Number of chests the user has opened. - abyss_process (str): Progress of the user in the abyss mode. - """ +class StarRailStats(APIModel): + """Overall user stats.""" active_days: int avatar_num: int @@ -24,28 +26,14 @@ class Stats(APIModel): class PartialStarRailUserStats(APIModel): - """ - Partial data of StarRail user, containing statistics and character information. + """User stats with characters without equipment.""" - Attributes: - stats (Stats): Statistics of user data. - characters (List[PartialCharacter]): List of user's avatars/characters. - """ - - stats: Stats - characters: List[PartialCharacter] = Field(alias="avatar_list") + stats: StarRailStats + characters: typing.Sequence[character.StarRailPartialCharacter] = Field(alias="avatar_list") class StarRailUserInfo(APIModel): - """ - Information of StarRail user. - - Attributes: - nickname (str): User's nickname. - server (str): User's server. - level (int): User's level. - avatar (str): User's avatar url. - """ + """User info.""" nickname: str server: str = Field(alias="region") @@ -54,6 +42,6 @@ class StarRailUserInfo(APIModel): class StarRailUserStats(PartialStarRailUserStats): - """Complete data of StarRail user, containing statistics and character information.""" + """User stats.""" info: StarRailUserInfo diff --git a/simnet/utils/player.py b/simnet/utils/player.py index 25b190c..931bddd 100644 --- a/simnet/utils/player.py +++ b/simnet/utils/player.py @@ -11,7 +11,7 @@ UID_RANGE: Mapping[Game, Mapping[Region, Sequence[int]]] = { }, Game.STARRAIL: { Region.OVERSEAS: (6, 7, 8, 9), - Region.CHINESE: (1, 2), + Region.CHINESE: (1, 2, 5), }, Game.HONKAI: { Region.OVERSEAS: (1, 2), diff --git a/tests/test_starrail_battle_chronicle_client.py b/tests/test_starrail_battle_chronicle_client.py index 8f334ae..df01787 100644 --- a/tests/test_starrail_battle_chronicle_client.py +++ b/tests/test_starrail_battle_chronicle_client.py @@ -45,3 +45,13 @@ class TestStarrailBattleChronicleClient: assert len(characters.avatar_list) > 0 character = characters.avatar_list[-1] assert character.id > 0 + + @staticmethod + async def test_get_starrail_challenge(starrail_client: "StarRailBattleChronicleClient"): + challenge = await starrail_client.get_starrail_challenge() + assert challenge.season > 0 + + @staticmethod + async def test_get_starrail_rogue(starrail_client: "StarRailBattleChronicleClient"): + rogue = await starrail_client.get_starrail_rogue() + assert rogue.role is not None