From d3b3a4b62ee5e453b28c91965926dcf8dccb6120 Mon Sep 17 00:00:00 2001 From: omg-xtao <100690902+omg-xtao@users.noreply.github.com> Date: Sun, 23 Jul 2023 17:41:26 +0800 Subject: [PATCH] :sparkles: support aapi --- simnet/client/components/chronicle/base.py | 4 +- simnet/client/components/chronicle/genshin.py | 40 ++++++++++++-- .../client/components/chronicle/starrail.py | 37 +++++++++++-- simnet/client/genshin.pyi | 1 - simnet/client/starrail.pyi | 1 - simnet/models/genshin/chronicle/notes.py | 54 ++++++++++++++++++- simnet/models/starrail/chronicle/notes.py | 38 +++++++++++++ 7 files changed, 164 insertions(+), 11 deletions(-) diff --git a/simnet/client/components/chronicle/base.py b/simnet/client/components/chronicle/base.py index 73e1fc1..6e0de7e 100644 --- a/simnet/client/components/chronicle/base.py +++ b/simnet/client/components/chronicle/base.py @@ -24,6 +24,7 @@ class BaseChronicleClient(BaseClient): async def request_game_record( self, endpoint: str, + endpoint_type: str = "api", data: Optional[Any] = None, params: Optional[QueryParamTypes] = None, lang: Optional[str] = None, @@ -34,6 +35,7 @@ class BaseChronicleClient(BaseClient): Args: endpoint (str): The endpoint to send the request to. + endpoint_type (str, optional): The type of endpoint to send the request to. data (Optional[Any], optional): The request payload. params (Optional[QueryParamTypes], optional): The query parameters for the request. lang (Optional[str], optional): The language for the response. @@ -51,7 +53,7 @@ class BaseChronicleClient(BaseClient): base_url = RECORD_URL.get_url(region or self.region) if game: - base_url = base_url / game.value / "api" + base_url = base_url / game.value / endpoint_type url = base_url / endpoint new_ds = self.region == Region.CHINESE diff --git a/simnet/client/components/chronicle/genshin.py b/simnet/client/components/chronicle/genshin.py index c3376ea..3c15900 100644 --- a/simnet/client/components/chronicle/genshin.py +++ b/simnet/client/components/chronicle/genshin.py @@ -2,17 +2,17 @@ import asyncio from typing import Optional, Any, List, Dict from simnet.client.components.chronicle.base import BaseChronicleClient -from simnet.errors import DataNotPublic, BadRequest +from simnet.errors import DataNotPublic, BadRequest, RegionNotSupported from simnet.models.genshin.chronicle.abyss import SpiralAbyss, SpiralAbyssPair from simnet.models.genshin.chronicle.characters import Character -from simnet.models.genshin.chronicle.notes import Notes +from simnet.models.genshin.chronicle.notes import Notes, NotesWidget from simnet.models.genshin.chronicle.stats import ( PartialGenshinUserStats, GenshinUserStats, FullGenshinUserStats, ) from simnet.models.lab.record import RecordCard -from simnet.utils.enum_ import Game +from simnet.utils.enum_ import Game, Region from simnet.utils.player import recognize_genshin_server, recognize_region __all__ = ("GenshinBattleChronicleClient",) @@ -30,6 +30,7 @@ class GenshinBattleChronicleClient(BaseChronicleClient): endpoint: str, player_id: Optional[int] = None, method: str = "GET", + endpoint_type: str = "api", lang: Optional[str] = None, payload: Optional[Dict[str, Any]] = None, ): @@ -37,6 +38,7 @@ class GenshinBattleChronicleClient(BaseChronicleClient): Args: endpoint (str): The endpoint of the object to retrieve. + endpoint_type (str, optional): The type of endpoint to send the request to. player_id (Optional[int], optional): The player ID. Defaults to None. method (str, optional): The HTTP method to use. Defaults to "GET". lang (Optional[str], optional): The language of the data. Defaults to None. @@ -68,6 +70,7 @@ class GenshinBattleChronicleClient(BaseChronicleClient): return await self.request_game_record( endpoint, + endpoint_type=endpoint_type, lang=lang, game=Game.GENSHIN, region=recognize_region(player_id, game=Game.GENSHIN), @@ -172,7 +175,7 @@ class GenshinBattleChronicleClient(BaseChronicleClient): autoauth (bool, optional): Whether to automatically authenticate the user. Defaults to True. Returns: - StarRailNote: The requested real-time notes. + Notes: The requested real-time notes. Raises: BadRequest: If the request is invalid. @@ -258,3 +261,32 @@ class GenshinBattleChronicleClient(BaseChronicleClient): return record_card return None + + async def get_genshin_notes_by_stoken( + self, + lang: Optional[str] = None, + ) -> NotesWidget: + """Get Genshin's real-time notes. + + Args: + lang (Optional[str], optional): The language of the data. Defaults to None. + + Returns: + NotesWidget: The requested real-time notes. + + Raises: + RegionNotSupported: If the region is not supported. + BadRequest: If the request is invalid. + """ + if self.region == Region.OVERSEAS: + raise RegionNotSupported("Notes widget is not supported in overseas region.") + stoken = self.cookies.get("stoken") + if stoken is None: + raise ValueError("stoken not found in cookies.") + stuid = self.cookies.get("stuid") + if stuid is None and self.account_id is None: + raise ValueError("account_id or stuid not found") + if self.account_id is not None and stuid is None: + self.cookies.set("stuid", str(self.account_id)) + data = await self._request_genshin_record("widget/v2", endpoint_type="aapi", lang=lang) + return NotesWidget(**data) diff --git a/simnet/client/components/chronicle/starrail.py b/simnet/client/components/chronicle/starrail.py index 5f2ca3d..a5133f8 100644 --- a/simnet/client/components/chronicle/starrail.py +++ b/simnet/client/components/chronicle/starrail.py @@ -2,16 +2,16 @@ import asyncio from typing import Optional, Mapping, Dict, Any from simnet.client.components.chronicle.base import BaseChronicleClient -from simnet.errors import BadRequest, DataNotPublic +from simnet.errors import BadRequest, DataNotPublic, RegionNotSupported from simnet.models.lab.record import RecordCard from simnet.models.starrail.chronicle.activity import StarRailActivity from simnet.models.starrail.chronicle.challenge import StarRailChallenge from simnet.models.starrail.chronicle.characters import StarRailDetailCharacters from simnet.models.starrail.chronicle.museum import StarRailMuseumBasic, StarRailMuseumDetail -from simnet.models.starrail.chronicle.notes import StarRailNote +from simnet.models.starrail.chronicle.notes import StarRailNote, StarRailNoteWidget 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.enum_ import Game, Region from simnet.utils.player import recognize_starrail_server, recognize_region __all__ = ("StarRailBattleChronicleClient",) @@ -28,6 +28,7 @@ class StarRailBattleChronicleClient(BaseChronicleClient): self, endpoint: str, player_id: Optional[int] = None, + endpoint_type: str = "api", method: str = "GET", lang: Optional[str] = None, payload: Optional[Dict[str, Any]] = None, @@ -61,6 +62,7 @@ class StarRailBattleChronicleClient(BaseChronicleClient): return await self.request_game_record( endpoint, + endpoint_type=endpoint_type, lang=lang, game=Game.STARRAIL, region=recognize_region(player_id, game=Game.STARRAIL), @@ -287,3 +289,32 @@ class StarRailBattleChronicleClient(BaseChronicleClient): """ data = await self._request_starrail_record("activity", uid, lang=lang) return StarRailActivity(**data) + + async def get_starrail_notes_by_stoken( + self, + lang: Optional[str] = None, + ) -> StarRailNoteWidget: + """Get StarRail's real-time notes. + + Args: + lang (Optional[str], optional): The language of the data. Defaults to None. + + Returns: + StarRailNoteWidget: The requested real-time notes. + + Raises: + RegionNotSupported: If the region is not supported. + BadRequest: If the request is invalid. + """ + if self.region == Region.OVERSEAS: + raise RegionNotSupported("Notes widget is not supported in overseas region.") + stoken = self.cookies.get("stoken") + if stoken is None: + raise ValueError("stoken not found in cookies.") + stuid = self.cookies.get("stuid") + if stuid is None and self.account_id is None: + raise ValueError("account_id or stuid not found") + if self.account_id is not None and stuid is None: + self.cookies.set("stuid", str(self.account_id)) + data = await self._request_starrail_record("widget", endpoint_type="aapi", lang=lang) + return StarRailNoteWidget(**data) diff --git a/simnet/client/genshin.pyi b/simnet/client/genshin.pyi index f3ac30e..f467700 100644 --- a/simnet/client/genshin.pyi +++ b/simnet/client/genshin.pyi @@ -11,7 +11,6 @@ from simnet.client.components.wish.genshin import GenshinWishClient from simnet.utils.enum_ import Region from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes - class GenshinClient( CalculatorClient, GenshinBattleChronicleClient, diff --git a/simnet/client/starrail.pyi b/simnet/client/starrail.pyi index d7999ca..ee9f7ee 100644 --- a/simnet/client/starrail.pyi +++ b/simnet/client/starrail.pyi @@ -9,7 +9,6 @@ from simnet.client.components.wish.starrail import StarRailWishClient from simnet.utils.enum_ import Region from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes - class StarRailClient( StarRailBattleChronicleClient, StarRailWishClient, StarrailDiaryClient, DailyRewardClient, AuthClient, LabClient ): diff --git a/simnet/models/genshin/chronicle/notes.py b/simnet/models/genshin/chronicle/notes.py index 587c3d1..ef0d86a 100644 --- a/simnet/models/genshin/chronicle/notes.py +++ b/simnet/models/genshin/chronicle/notes.py @@ -5,7 +5,7 @@ from pydantic import Field, root_validator from simnet.models.base import APIModel -__all__ = ("Expedition", "Notes") +__all__ = ("Expedition", "Notes", "ExpeditionWidget", "NotesWidget") def _process_timedelta(time: Union[int, timedelta, datetime]) -> datetime: @@ -184,3 +184,55 @@ class Notes(APIModel): raise ValueError("Transformer recovery time cannot exceed 7 days.") return values + + +class ExpeditionWidget(APIModel): + """The model for a real-time expedition. + + Attributes: + character (str): The expedition character icon url. + status (Literal["Ongoing", "Finished"]): The status of the expedition. + """ + + character: str = Field(alias="avatar_side_icon") + status: Literal["Ongoing", "Finished"] + + +class NotesWidget(APIModel): + """The model for real-time notes. + + Attributes: + current_resin (int): The current amount of resin. + max_resin (int): The maximum amount of resin. + remaining_resin_recovery_time (timedelta): The remaining time until resin recovery. + current_realm_currency (int): The current amount of realm currency. + max_realm_currency (int): The maximum amount of realm currency. + completed_commissions (int): The number of completed commissions. + max_commissions (int): The maximum number of commissions. + claimed_commission_reward (bool): Whether the commission reward has been claimed. + expeditions (List[Expedition]): The list of expeditions. + max_expeditions (int): The maximum number of expeditions. + + Raises: + ValueError: If the remaining resin recovery time is less than 0 or greater than 8 hours, + or if the remaining realm currency recovery time is less than 0 or greater than 24 hours. + """ + + current_resin: int + max_resin: int + remaining_resin_recovery_time: timedelta = Field(alias="resin_recovery_time") + + current_realm_currency: int = Field(alias="current_home_coin") + max_realm_currency: int = Field(alias="max_home_coin") + + completed_commissions: int = Field(alias="finished_task_num") + max_commissions: int = Field(alias="total_task_num") + claimed_commission_reward: bool = Field(alias="is_extra_task_reward_received") + + expeditions: List[ExpeditionWidget] + max_expeditions: int = Field(alias="max_expedition_num") + + @property + def resin_recovery_time(self) -> datetime: + """A property that returns the time when resin will be fully recovered.""" + return datetime.now().astimezone() + self.remaining_resin_recovery_time diff --git a/simnet/models/starrail/chronicle/notes.py b/simnet/models/starrail/chronicle/notes.py index d907855..c89d22f 100644 --- a/simnet/models/starrail/chronicle/notes.py +++ b/simnet/models/starrail/chronicle/notes.py @@ -72,3 +72,41 @@ class StarRailNote(APIModel): """Remaining echo of war rewards""" max_weekly_discounts: int = Field(alias="weekly_cocoon_limit") """Echo of war attempt limit""" + + +class StarRailNoteWidget(APIModel): + """Represents a StarRail Note. + + Attributes: + current_stamina (int): The current stamina of the user. + max_stamina (int): The maximum stamina of the user. + stamina_recover_time (timedelta): The time it takes for one stamina to recover. + accepted_expedition_num (int): The number of expeditions the user has accepted. + total_expedition_num (int): The total number of expeditions the user has participated in. + expeditions (Sequence[StarRailExpedition]): A list of expeditions the user has participated in. + current_train_score (int): The current daily training activity. + max_train_score (int): The max daily training activity. + current_rogue_score (int): The current simulated universe weekly points. + max_rogue_score (int): The max simulated universe weekly points. + has_signed (bool): Whether the user has signed in today. + """ + + current_stamina: int + max_stamina: int + stamina_recover_time: timedelta + accepted_expedition_num: int + total_expedition_num: int + expeditions: Sequence[StarRailExpedition] + + current_train_score: int + """Current daily training activity""" + max_train_score: int + """Max daily training activity""" + + current_rogue_score: int + """Current simulated universe weekly points""" + max_rogue_score: int + """Max simulated universe weekly points""" + + has_signed: bool + """Whether the user has signed in today"""