mirror of
https://github.com/PaiGramTeam/SIMNet.git
synced 2024-11-22 06:17:57 +00:00
✨ Add Star Rail Client
This commit is contained in:
parent
7294850914
commit
cd97a751b9
3
simnet/__init__.py
Normal file
3
simnet/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from simnet.client.starrail import StarRailClient
|
||||||
|
|
||||||
|
__all__ = ("StarRailClient",)
|
56
simnet/client/chronicle/base.py
Normal file
56
simnet/client/chronicle/base.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from typing import Optional, Any
|
||||||
|
from simnet.client.base import BaseClient
|
||||||
|
from simnet.client.routes import RECORD_URL
|
||||||
|
from simnet.utils.enum_ import Region, Game
|
||||||
|
from simnet.utils.types import QueryParamTypes
|
||||||
|
|
||||||
|
|
||||||
|
class BaseChronicleClient(BaseClient):
|
||||||
|
"""The base class for the Chronicle API client.
|
||||||
|
|
||||||
|
This class provides the basic functionality for making requests to the
|
||||||
|
Chronicle API endpoints. It is meant to be subclassed by other clients
|
||||||
|
that provide a more specific interface to the Chronicle API.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
region (Region): The region associated with the API client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def request_game_record(
|
||||||
|
self,
|
||||||
|
endpoint: str,
|
||||||
|
data: Optional[Any] = None,
|
||||||
|
params: Optional[QueryParamTypes] = None,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
region: Optional[Region] = None,
|
||||||
|
game: Optional[Game] = None,
|
||||||
|
):
|
||||||
|
"""Make a request towards the game record endpoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint (str): The 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.
|
||||||
|
region (Optional[Region], optional): The region associated with the request.
|
||||||
|
game (Optional[Game], optional): The game associated with the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The response from the server.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
NetworkError: If an HTTP error occurs while making the request.
|
||||||
|
TimedOut: If the request times out.
|
||||||
|
BadRequest: If the response contains an error.
|
||||||
|
"""
|
||||||
|
base_url = RECORD_URL.get_url(region or self.region)
|
||||||
|
|
||||||
|
if game:
|
||||||
|
base_url = base_url / game.value / "api"
|
||||||
|
|
||||||
|
url = base_url / endpoint
|
||||||
|
new_ds = self.region == Region.CHINESE
|
||||||
|
|
||||||
|
return await self.request_lab(
|
||||||
|
url, data=data, params=params, lang=lang, new_ds=new_ds
|
||||||
|
)
|
143
simnet/client/chronicle/starrail.py
Normal file
143
simnet/client/chronicle/starrail.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
from typing import Optional, Mapping, Dict, Any
|
||||||
|
|
||||||
|
from simnet.client.chronicle.base import BaseChronicleClient
|
||||||
|
from simnet.errors import BadRequest, DataNotPublic
|
||||||
|
from simnet.models.starrail.chronicle.characters import StarShipDetailCharacters
|
||||||
|
from simnet.models.starrail.chronicle.notes import StarRailNote
|
||||||
|
from simnet.models.starrail.chronicle.stats import StarRailUserStats
|
||||||
|
from simnet.utils.enum_ import Game
|
||||||
|
from simnet.utils.player import recognize_starrail_server, recognize_region
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailBattleChronicleClient(BaseChronicleClient):
|
||||||
|
"""A client for retrieving data from StarRail's battle chronicle component.
|
||||||
|
|
||||||
|
This class is used to retrieve various data objects from StarRail's battle chronicle component,
|
||||||
|
including real-time notes, user statistics, and character information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def _request_starrail_record(
|
||||||
|
self,
|
||||||
|
endpoint: str,
|
||||||
|
player_id: Optional[int] = None,
|
||||||
|
method: str = "GET",
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
payload: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> Mapping[str, Any]:
|
||||||
|
"""Get an arbitrary object from StarRail's battle chronicle.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint (str): The endpoint of the object to retrieve.
|
||||||
|
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.
|
||||||
|
payload (Optional[Dict[str, Any]], optional): The request payload. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Mapping[str, Any]: The requested object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
BadRequest: If the request is invalid.
|
||||||
|
DataNotPublic: If the requested data is not public.
|
||||||
|
"""
|
||||||
|
payload = dict(payload or {})
|
||||||
|
|
||||||
|
player_id = player_id or self.player_id
|
||||||
|
payload = dict(
|
||||||
|
role_id=player_id, server=recognize_starrail_server(player_id), **payload
|
||||||
|
)
|
||||||
|
|
||||||
|
data, params = None, None
|
||||||
|
if method == "POST":
|
||||||
|
data = payload
|
||||||
|
else:
|
||||||
|
params = payload
|
||||||
|
|
||||||
|
return await self.request_game_record(
|
||||||
|
endpoint,
|
||||||
|
lang=lang,
|
||||||
|
game=Game.STARRAIL,
|
||||||
|
region=recognize_region(player_id, game=Game.STARRAIL),
|
||||||
|
params=params,
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_starrail_notes(
|
||||||
|
self,
|
||||||
|
player_id: Optional[int] = None,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
autoauth: bool = True,
|
||||||
|
) -> StarRailNote:
|
||||||
|
"""Get StarRail's real-time notes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player_id (Optional[int], optional): The player ID. Defaults to None.
|
||||||
|
lang (Optional[str], optional): The language of the data. Defaults to None.
|
||||||
|
autoauth (bool, optional): Whether to automatically authenticate the user. Defaults to True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StarRailNote: The requested real-time notes.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
BadRequest: If the request is invalid.
|
||||||
|
DataNotPublic: If the requested data is not public.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = await self._request_starrail_record("note", player_id, lang=lang)
|
||||||
|
except DataNotPublic as e:
|
||||||
|
# error raised only when real-time notes are not enabled
|
||||||
|
if player_id and self.player_id != player_id:
|
||||||
|
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
|
||||||
|
data = await self._request_starrail_record("note", player_id, lang=lang)
|
||||||
|
|
||||||
|
return StarRailNote(**data)
|
||||||
|
|
||||||
|
async def get_starrail_user(
|
||||||
|
self,
|
||||||
|
player_id: Optional[int] = None,
|
||||||
|
*,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
) -> StarRailUserStats:
|
||||||
|
"""Get StarRail user statistics.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player_id (Optional[int], optional): The player ID. Defaults to None.
|
||||||
|
lang (Optional[str], optional): The language of the data. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StarRailUserStats: The requested user statistics.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
BadRequest: If the request is invalid.
|
||||||
|
DataNotPublic: If the requested data is not public.
|
||||||
|
"""
|
||||||
|
data = await self._request_starrail_record("index", player_id, lang=lang)
|
||||||
|
return StarRailUserStats(**data)
|
||||||
|
|
||||||
|
async def get_starrail_characters(
|
||||||
|
self,
|
||||||
|
player_id: Optional[int] = None,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
) -> StarShipDetailCharacters:
|
||||||
|
"""Get StarRail character information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player_id (Optional[int], optional): The player ID. Defaults to None.
|
||||||
|
lang (Optional[str], optional): The language of the data. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StarShipDetailCharacters: The requested character information.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
BadRequest: If the request is invalid.
|
||||||
|
DataNotPublic: If the requested data is not public.
|
||||||
|
"""
|
||||||
|
payload = {"need_wiki": "true"}
|
||||||
|
data = await self._request_starrail_record(
|
||||||
|
"avatar/info", player_id, lang=lang, payload=payload
|
||||||
|
)
|
||||||
|
return StarShipDetailCharacters(**data)
|
@ -154,3 +154,20 @@ class GameRoute(BaseRoute):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return self.urls[region][game]
|
return self.urls[region][game]
|
||||||
|
|
||||||
|
|
||||||
|
RECORD_URL = InternationalRoute(
|
||||||
|
overseas="https://bbs-api-os.hoyolab.com/game_record",
|
||||||
|
chinese="https://api-takumi-record.mihoyo.com/game_record/app",
|
||||||
|
)
|
||||||
|
|
||||||
|
GACHA_INFO_URL = GameRoute(
|
||||||
|
overseas=dict(
|
||||||
|
genshin="https://hk4e-api-os.hoyoverse.com/event/gacha_info/api",
|
||||||
|
hkrpg="",
|
||||||
|
),
|
||||||
|
chinese=dict(
|
||||||
|
genshin="https://hk4e-api.mihoyo.com/event/gacha_info/api",
|
||||||
|
hkrpg="https://api-takumi.mihoyo.com/common/gacha_record/api",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
8
simnet/client/starrail.py
Normal file
8
simnet/client/starrail.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from simnet.client.chronicle.starrail import StarRailBattleChronicleClient
|
||||||
|
from simnet.client.wish.starrail import WishClient
|
||||||
|
|
||||||
|
__all__ = ("StarRailClient",)
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailClient(StarRailBattleChronicleClient, WishClient):
|
||||||
|
"""A simple http client for StarRail endpoints."""
|
18
simnet/client/starrail.pyi
Normal file
18
simnet/client/starrail.pyi
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from simnet.client.chronicle.starrail import StarRailBattleChronicleClient
|
||||||
|
from simnet.client.wish.starrail import WishClient
|
||||||
|
from simnet.utils.enum_ import Region
|
||||||
|
from simnet.utils.types import CookieTypes, HeaderTypes
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailClient(StarRailBattleChronicleClient, WishClient):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
cookies: Optional[CookieTypes] = None,
|
||||||
|
headers: Optional[HeaderTypes] = None,
|
||||||
|
account_id: Optional[int] = None,
|
||||||
|
player_id: Optional[int] = None,
|
||||||
|
region: Region = Region.OVERSEAS,
|
||||||
|
lang: str = "en-us",
|
||||||
|
): ...
|
128
simnet/client/wish/base.py
Normal file
128
simnet/client/wish/base.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
from typing import Optional, Any, List, Dict
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
from simnet.client.base import BaseClient
|
||||||
|
from simnet.client.routes import GACHA_INFO_URL
|
||||||
|
from simnet.utils.enum_ import Game
|
||||||
|
from simnet.utils.lang import create_short_lang_code
|
||||||
|
|
||||||
|
|
||||||
|
class BaseWishClient(BaseClient):
|
||||||
|
"""The base class for the Wish API client."""
|
||||||
|
|
||||||
|
async def request_gacha_info(
|
||||||
|
self,
|
||||||
|
endpoint: str,
|
||||||
|
game: Game,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
authkey: Optional[str] = None,
|
||||||
|
params: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Make a request towards the gacha info endpoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint (str): The endpoint to request data from.
|
||||||
|
game (Game): The game to make the request for.
|
||||||
|
lang (Optional[str] , optional): The language code to use for the request.
|
||||||
|
If not provided, the class default will be used.
|
||||||
|
authkey (Optional[str] , optional): The authorization key for making the request.
|
||||||
|
params (Optional[Dict[str, Any]], optional): The query parameters for the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]
|
||||||
|
The response data as a dictionary.
|
||||||
|
"""
|
||||||
|
params = dict(params or {})
|
||||||
|
|
||||||
|
if authkey is None:
|
||||||
|
raise RuntimeError("No authkey provided")
|
||||||
|
|
||||||
|
base_url = GACHA_INFO_URL.get_url(self.region, game)
|
||||||
|
url = base_url / endpoint
|
||||||
|
|
||||||
|
params["authkey_ver"] = 1
|
||||||
|
params["authkey"] = unquote(authkey)
|
||||||
|
params["lang"] = create_short_lang_code(lang or self.lang)
|
||||||
|
|
||||||
|
return await self.request_api("GET", url, params=params)
|
||||||
|
|
||||||
|
async def wish_history(
|
||||||
|
self,
|
||||||
|
banner_types: List[int],
|
||||||
|
limit: Optional[int] = None,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
authkey: Optional[str] = None,
|
||||||
|
end_id: int = 0,
|
||||||
|
) -> List[object]:
|
||||||
|
"""
|
||||||
|
Get the wish history for a list of banner types.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
banner_types (List[int]): The list of banner types to get the wish history for.
|
||||||
|
limit (Optional[int] , optional): The maximum number of wishes to retrieve.
|
||||||
|
If not provided, all available wishes will be returned.
|
||||||
|
lang (Optional[str], optional): The language code to use for the request.
|
||||||
|
If not provided, the class default will be used.
|
||||||
|
authkey (Optional[str], optional): The authorization key for making the request.
|
||||||
|
end_id (int, optional): The ending ID of the last wish to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Any]: A list of Wish objects representing the retrieved wishes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_wish_page(
|
||||||
|
self,
|
||||||
|
end_id: int,
|
||||||
|
banner_type: int,
|
||||||
|
game: Game,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
authkey: Optional[str] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get a single page of wishes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
end_id (int): The ending ID of the last wish to retrieve.
|
||||||
|
banner_type (int): The type of banner to retrieve wishes from.
|
||||||
|
game (Game): The game to make the request for.
|
||||||
|
lang (Optional[str], optional): The language code to use for the request.
|
||||||
|
If not provided, the class default will be used.
|
||||||
|
authkey (Optional[str], optional): The authorization key for making the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: The response data as a dictionary.
|
||||||
|
"""
|
||||||
|
return await self.request_gacha_info(
|
||||||
|
"getGachaLog",
|
||||||
|
game=game,
|
||||||
|
lang=lang,
|
||||||
|
authkey=authkey,
|
||||||
|
params=dict(gacha_type=banner_type, size=20, end_id=end_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_banner_names(
|
||||||
|
self,
|
||||||
|
game: Game,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
authkey: Optional[str] = None,
|
||||||
|
) -> Dict[int, str]:
|
||||||
|
"""
|
||||||
|
Get a list of banner names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
game (Game): The game to make the request for.
|
||||||
|
lang (Optional[str], optional): The language code to use for the request.
|
||||||
|
If not provided, the class default will be used.
|
||||||
|
authkey (Optional[str], optional): The authorization key for making the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[int, str]: A dictionary mapping banner type IDs to their corresponding names.
|
||||||
|
"""
|
||||||
|
data = await self.request_gacha_info(
|
||||||
|
"getConfigList",
|
||||||
|
game=game,
|
||||||
|
lang=lang,
|
||||||
|
authkey=authkey,
|
||||||
|
)
|
||||||
|
return {int(i["key"]): i["name"] for i in data["gacha_type_list"]}
|
41
simnet/client/wish/starrail.py
Normal file
41
simnet/client/wish/starrail.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from typing import Optional, List
|
||||||
|
from simnet.client.wish.base import BaseWishClient
|
||||||
|
from simnet.models.starrail.wish import StarRailWish
|
||||||
|
from simnet.utils.enum_ import Game
|
||||||
|
|
||||||
|
|
||||||
|
class WishClient(BaseWishClient):
|
||||||
|
"""The WishClient class for making requests towards the Wish API."""
|
||||||
|
|
||||||
|
async def wish_history(
|
||||||
|
self,
|
||||||
|
banner_types: List[int],
|
||||||
|
limit: Optional[int] = None,
|
||||||
|
lang: Optional[str] = None,
|
||||||
|
authkey: Optional[str] = None,
|
||||||
|
end_id: int = 0,
|
||||||
|
) -> List[StarRailWish]:
|
||||||
|
"""
|
||||||
|
Get the wish history for a list of banner types.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
banner_types (List[int], optional): The list of banner types to get the wish history for.
|
||||||
|
limit (Optional[int] , optional): The maximum number of wishes to retrieve.
|
||||||
|
If not provided, all available wishes will be returned.
|
||||||
|
lang (Optional[str], optional): The language code to use for the request.
|
||||||
|
If not provided, the class default will be used.
|
||||||
|
authkey (Optional[str], optional): The authorization key for making the request.
|
||||||
|
end_id (int, optional): The ending ID of the last wish to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[StarRailWish]: A list of StarRailWish objects representing the retrieved wishes.
|
||||||
|
"""
|
||||||
|
wish: List[StarRailWish] = []
|
||||||
|
banner_names = await self.get_banner_names(
|
||||||
|
game=Game.STARRAIL, lang=lang, authkey=authkey
|
||||||
|
)
|
||||||
|
for banner_type in banner_types:
|
||||||
|
data = await self.get_wish_page(end_id, banner_type, Game.STARRAIL)
|
||||||
|
banner_name = banner_names[banner_type]
|
||||||
|
wish = [StarRailWish(**i, banner_name=banner_name) for i in data["list"]]
|
||||||
|
return wish
|
19
simnet/models/starrail/character.py
Normal file
19
simnet/models/starrail/character.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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).
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
element: str
|
||||||
|
rarity: int
|
||||||
|
icon: str
|
106
simnet/models/starrail/chronicle/characters.py
Normal file
106
simnet/models/starrail/chronicle/characters.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
from simnet.models.base import APIModel
|
||||||
|
from simnet.models.starrail.character import BaseCharacter
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
level: int
|
||||||
|
rank: int
|
||||||
|
name: str
|
||||||
|
desc: str
|
||||||
|
icon: str
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
level: int
|
||||||
|
pos: int
|
||||||
|
name: str
|
||||||
|
desc: str
|
||||||
|
icon: str
|
||||||
|
rarity: int
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
pos: int
|
||||||
|
name: str
|
||||||
|
icon: str
|
||||||
|
desc: str
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
image: str
|
||||||
|
equip: Optional[Equipment]
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
avatar_list: List[StarRailDetailCharacter]
|
52
simnet/models/starrail/chronicle/notes.py
Normal file
52
simnet/models/starrail/chronicle/notes.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from datetime import timedelta, datetime
|
||||||
|
from typing import List, Literal, Sequence
|
||||||
|
|
||||||
|
from simnet.models.base import APIModel
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailExpedition(APIModel):
|
||||||
|
"""Represents a StarRail Expedition.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
avatars (List[str]): A list of avatar names participating in the expedition.
|
||||||
|
status (Literal["Ongoing", "Finished"]): The status of the expedition.
|
||||||
|
remaining_time (timedelta): The time remaining for the expedition to finish.
|
||||||
|
name (str): The name of the expedition.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
avatars: List[str]
|
||||||
|
status: Literal["Ongoing", "Finished"]
|
||||||
|
remaining_time: timedelta
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def finished(self) -> bool:
|
||||||
|
"""Returns whether the expedition has finished."""
|
||||||
|
return self.remaining_time <= timedelta(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def completion_time(self) -> datetime:
|
||||||
|
"""Returns the time at which the expedition will be completed."""
|
||||||
|
return datetime.now().astimezone() + self.remaining_time
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailNote(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_epedition_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_stamina: int
|
||||||
|
max_stamina: int
|
||||||
|
stamina_recover_time: timedelta
|
||||||
|
accepted_epedition_num: int
|
||||||
|
total_expedition_num: int
|
||||||
|
expeditions: Sequence[StarRailExpedition]
|
40
simnet/models/starrail/chronicle/stats.py
Normal file
40
simnet/models/starrail/chronicle/stats.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from typing import List
|
||||||
|
from pydantic import Field
|
||||||
|
from simnet.models.base import APIModel
|
||||||
|
from simnet.models.starrail.chronicle.characters import PartialCharacter
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
active_days: int
|
||||||
|
avatar_num: int
|
||||||
|
achievement_num: int
|
||||||
|
chest_num: int
|
||||||
|
abyss_process: str
|
||||||
|
|
||||||
|
|
||||||
|
class PartialStarRailUserStats(APIModel):
|
||||||
|
"""
|
||||||
|
Partial data of StarRail user, containing statistics and character information.
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailUserStats(PartialStarRailUserStats):
|
||||||
|
"""Complete data of StarRail user, containing statistics and character information."""
|
56
simnet/models/starrail/wish.py
Normal file
56
simnet/models/starrail/wish.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from enum import IntEnum
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import Field, validator
|
||||||
|
|
||||||
|
from simnet.models.base import APIModel
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailBannerType(IntEnum):
|
||||||
|
"""Banner types in wish histories."""
|
||||||
|
|
||||||
|
NOVICE = 2
|
||||||
|
"""Temporary novice banner."""
|
||||||
|
|
||||||
|
STANDARD = PERMANENT = 1
|
||||||
|
"""Permanent standard banner."""
|
||||||
|
|
||||||
|
CHARACTER = 11
|
||||||
|
"""Rotating character banner."""
|
||||||
|
|
||||||
|
WEAPON = 12
|
||||||
|
"""Rotating weapon banner."""
|
||||||
|
|
||||||
|
|
||||||
|
class StarRailWish(APIModel):
|
||||||
|
"""Wish made on any banner."""
|
||||||
|
|
||||||
|
uid: int
|
||||||
|
"""User ID of the wish maker."""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
"""ID of the wished item."""
|
||||||
|
|
||||||
|
type: str = Field(alias="item_type")
|
||||||
|
"""Type of the wished item."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""Name of the wished item."""
|
||||||
|
|
||||||
|
rarity: int = Field(alias="rank_type")
|
||||||
|
"""Rarity of the wished item."""
|
||||||
|
|
||||||
|
time: datetime
|
||||||
|
"""Time when the wish was made."""
|
||||||
|
|
||||||
|
banner_type: StarRailBannerType = Field(alias="gacha_type")
|
||||||
|
"""Type of the banner the wish was made on."""
|
||||||
|
|
||||||
|
banner_name: str
|
||||||
|
"""Name of the banner the wish was made on."""
|
||||||
|
|
||||||
|
@validator("banner_type", pre=True)
|
||||||
|
def cast_banner_type(cls, v: Any) -> int:
|
||||||
|
"""Converts the banner type from any type to int."""
|
||||||
|
return int(v)
|
62
tests/conftest.py
Normal file
62
tests/conftest.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
|
|
||||||
|
from simnet.client.cookies import Cookies
|
||||||
|
from simnet.client.starrail import StarRailClient
|
||||||
|
from simnet.utils.cookies import parse_cookie
|
||||||
|
from simnet.utils.enum_ import Region
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def event_loop(): # skipcq: PY-D0003
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
yield loop
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def cookies() -> "Cookies": # skipcq: PY-D0003
|
||||||
|
cookies_str = os.environ.get("COOKIES")
|
||||||
|
if not cookies_str:
|
||||||
|
pytest.exit("No cookies set", 1)
|
||||||
|
|
||||||
|
_cookies = Cookies(parse_cookie(cookies_str))
|
||||||
|
|
||||||
|
return _cookies
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def player_id() -> int: # skipcq: PY-D0003
|
||||||
|
_player_id = os.environ.get("PLAYER_ID")
|
||||||
|
if not _player_id:
|
||||||
|
pytest.exit("No player id set", 1)
|
||||||
|
return int(_player_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def account_id() -> int: # skipcq: PY-D0003
|
||||||
|
_account_id = os.environ.get("ACCOUNT_ID")
|
||||||
|
if not _account_id:
|
||||||
|
pytest.exit("No player id set", 1)
|
||||||
|
return int(_account_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture
|
||||||
|
async def starrail_client( # skipcq: PY-D0003
|
||||||
|
player_id: int, account_id: int, cookies: "Cookies" # skipcq: PYL-W0621
|
||||||
|
):
|
||||||
|
async with StarRailClient(
|
||||||
|
player_id=player_id,
|
||||||
|
cookies=cookies,
|
||||||
|
account_id=account_id,
|
||||||
|
region=Region.CHINESE,
|
||||||
|
) as client_instance:
|
||||||
|
yield client_instance
|
30
tests/test_starrail.py
Normal file
30
tests/test_starrail.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from simnet.client.starrail import StarRailClient
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
class TestStarRailClient:
|
||||||
|
@staticmethod
|
||||||
|
async def test_get_starrail_user(starrail_client: "StarRailClient"):
|
||||||
|
user = await starrail_client.get_starrail_user()
|
||||||
|
assert user is not None
|
||||||
|
assert user.stats.chest_num > 0
|
||||||
|
assert len(user.characters) > 0
|
||||||
|
character = user.characters[-1]
|
||||||
|
assert character.id > 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_get_starrail_notes(starrail_client: "StarRailClient"):
|
||||||
|
notes = await starrail_client.get_starrail_notes()
|
||||||
|
assert notes is not None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_get_starrail_characters(starrail_client: "StarRailClient"):
|
||||||
|
characters = await starrail_client.get_starrail_characters()
|
||||||
|
assert len(characters.avatar_list) > 0
|
||||||
|
character = characters.avatar_list[-1]
|
||||||
|
assert character.id > 0
|
Loading…
Reference in New Issue
Block a user