mirror of
https://github.com/PaiGramTeam/SIMNet.git
synced 2024-11-22 22:37:24 +00:00
371 lines
14 KiB
Python
371 lines
14 KiB
Python
import asyncio
|
|
from typing import Optional, Any, List, Dict, Union
|
|
|
|
from simnet.client.components.chronicle.base import BaseChronicleClient
|
|
from simnet.client.routes import RECORD_URL
|
|
from simnet.errors import DataNotPublic, BadRequest
|
|
from simnet.models.genshin.chronicle.abyss import SpiralAbyss, SpiralAbyssPair
|
|
from simnet.models.genshin.chronicle.character_detail import GenshinCharacterListInfo, GenshinDetailCharacters
|
|
from simnet.models.genshin.chronicle.characters import Character
|
|
from simnet.models.genshin.chronicle.img_theater import ImgTheater
|
|
from simnet.models.genshin.chronicle.notes import Notes, NotesWidget, NotesOverseaWidget
|
|
from simnet.models.genshin.chronicle.stats import (
|
|
PartialGenshinUserStats,
|
|
GenshinUserStats,
|
|
FullGenshinUserStats,
|
|
)
|
|
from simnet.models.lab.record import RecordCard
|
|
from simnet.utils.enums import Game, Region
|
|
from simnet.utils.player import recognize_genshin_server, recognize_region
|
|
|
|
__all__ = ("GenshinBattleChronicleClient",)
|
|
|
|
|
|
class GenshinBattleChronicleClient(BaseChronicleClient):
|
|
"""A client for retrieving data from Genshin'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_genshin_record(
|
|
self,
|
|
endpoint: str,
|
|
player_id: Optional[int] = None,
|
|
method: str = "GET",
|
|
endpoint_type: str = "api",
|
|
lang: Optional[str] = None,
|
|
payload: Optional[Dict[str, Any]] = None,
|
|
):
|
|
"""Get an arbitrary object from StarRail's battle chronicle.
|
|
|
|
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.
|
|
payload (Optional[Dict[str, Any]], optional): The request payload. Defaults to None.
|
|
|
|
Returns:
|
|
Dict[str, Any]: The requested object.
|
|
|
|
Raises:
|
|
BadRequest: If the request is invalid.
|
|
DataNotPublic: If the requested data is not public.
|
|
ValueError: If the player ID is not specified.
|
|
"""
|
|
player_id = player_id or self.player_id
|
|
|
|
if player_id is None:
|
|
raise ValueError("Player ID is not specified.")
|
|
|
|
if payload is None:
|
|
payload = dict(role_id=player_id, server=recognize_genshin_server(player_id))
|
|
else:
|
|
payload = dict(role_id=player_id, server=recognize_genshin_server(player_id), **payload)
|
|
|
|
data, params = None, None
|
|
if method == "POST":
|
|
data = payload
|
|
else:
|
|
params = payload
|
|
|
|
return await self.request_game_record(
|
|
endpoint,
|
|
endpoint_type=endpoint_type,
|
|
lang=lang,
|
|
game=Game.GENSHIN,
|
|
region=recognize_region(player_id, game=Game.GENSHIN),
|
|
params=params,
|
|
data=data,
|
|
)
|
|
|
|
async def get_partial_genshin_user(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
) -> PartialGenshinUserStats:
|
|
"""Get partial genshin user without character equipment.
|
|
|
|
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:
|
|
PartialGenshinUserStats: The requested partial genshin user without character equipment.
|
|
"""
|
|
data = await self._request_genshin_record("index", player_id, lang=lang)
|
|
return PartialGenshinUserStats(**data)
|
|
|
|
async def get_genshin_characters(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
) -> List[Character]:
|
|
"""Get genshin user characters.
|
|
|
|
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:
|
|
Character: The requested genshin user characters.
|
|
"""
|
|
data = await self._request_genshin_record("character", player_id, lang=lang, method="POST")
|
|
return [Character(**i) for i in data["avatars"]]
|
|
|
|
async def get_genshin_user(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
) -> GenshinUserStats:
|
|
"""Get genshin user stats.
|
|
|
|
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:
|
|
GenshinUserStats: The requested genshin user stats.
|
|
"""
|
|
data, character_data = await asyncio.gather(
|
|
self._request_genshin_record("index", player_id, lang=lang),
|
|
self._request_genshin_record("character", player_id, lang=lang, method="POST"),
|
|
)
|
|
data = {**data, **character_data}
|
|
|
|
return GenshinUserStats(**data)
|
|
|
|
async def get_genshin_spiral_abyss(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
previous: bool = False,
|
|
lang: Optional[str] = None,
|
|
) -> SpiralAbyss:
|
|
"""Get genshin spiral abyss runs.
|
|
|
|
Args:
|
|
player_id (Optional[int], optional): The player ID. Defaults to None.
|
|
previous (bool, optional): Whether to retrieve the data for the previous season of the Spiral Abyss.
|
|
Defaults to False.
|
|
lang (Optional[str], optional): The language of the data. Defaults to None.
|
|
|
|
Returns:
|
|
SpiralAbyss: genshin spiral abyss runs.
|
|
"""
|
|
payload = dict(schedule_type=2 if previous else 1)
|
|
data = await self._request_genshin_record("spiralAbyss", player_id, lang=lang, payload=payload)
|
|
|
|
return SpiralAbyss(**data)
|
|
|
|
async def get_genshin_imaginarium_theater(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
need_detail: Optional[bool] = True,
|
|
*,
|
|
previous: bool = False,
|
|
lang: Optional[str] = None,
|
|
) -> ImgTheater:
|
|
"""Get genshin imaginarium theater runs.
|
|
|
|
Args:
|
|
player_id (Optional[int], optional): The player ID. Defaults to None.
|
|
need_detail (Optional[bool], optional): Whether to retrieve detailed data. Defaults to True.
|
|
previous (bool, optional): Whether to retrieve the data for the previous season of the Imaginarium Theater.
|
|
lang (Optional[str], optional): The language of the data. Defaults to None.
|
|
|
|
Returns:
|
|
ImgTheater: genshin imaginarium theater runs.
|
|
"""
|
|
payload = {
|
|
"need_detail": need_detail,
|
|
"schedule_type": 2 if previous else 1,
|
|
}
|
|
data = await self._request_genshin_record("role_combat", player_id, lang=lang, payload=payload)
|
|
|
|
return ImgTheater(**data)
|
|
|
|
async def get_genshin_notes(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
autoauth: bool = True,
|
|
) -> Notes:
|
|
"""Get Genshin'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:
|
|
Notes: 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_genshin_record("dailyNote", 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
|
|
|
|
await self.update_settings(3, True, game=Game.GENSHIN)
|
|
data = await self._request_genshin_record("dailyNote", lang=lang)
|
|
|
|
return Notes(**data)
|
|
|
|
async def get_full_genshin_user(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
) -> FullGenshinUserStats:
|
|
"""Get a genshin user with all their possible data.
|
|
|
|
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:
|
|
Character: The requested genshin user with all their possible data.
|
|
"""
|
|
index, character, abyss1, abyss2, activities = await asyncio.gather(
|
|
self._request_genshin_record("index", player_id, lang=lang),
|
|
self._request_genshin_record("character", player_id, lang=lang, method="POST"),
|
|
self.get_genshin_spiral_abyss(player_id, lang=lang, previous=False),
|
|
self.get_genshin_spiral_abyss(player_id, lang=lang, previous=True),
|
|
self.get_genshin_activities(player_id, lang=lang),
|
|
)
|
|
user = {**index, **character}
|
|
abyss = SpiralAbyssPair(current=abyss1, previous=abyss2)
|
|
|
|
return FullGenshinUserStats(**user, abyss=abyss, activities=activities)
|
|
|
|
async def get_genshin_activities(self, player_id: Optional[int] = None, *, lang: Optional[str] = None) -> Dict:
|
|
"""Get genshin activities.
|
|
|
|
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:
|
|
Dict: The requested get_genshin_activities.
|
|
"""
|
|
return await self._request_genshin_record("activities", player_id, lang=lang)
|
|
|
|
async def get_record_card(
|
|
self,
|
|
account_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
) -> Optional[RecordCard]:
|
|
"""Get a genshin player record cards.
|
|
|
|
Args:
|
|
account_id: Optional[int], the user's account ID, defaults to None
|
|
lang: Optional[str], the language version of the request, defaults to None
|
|
|
|
Returns:
|
|
Genshin player record cards.
|
|
|
|
Returns:
|
|
Optional[RecordCard]: RecordCard objects.
|
|
"""
|
|
account_id = account_id or self.account_id
|
|
|
|
record_cards = await self.get_record_cards(account_id, lang=lang)
|
|
|
|
for record_card in record_cards:
|
|
if record_card.game == Game.GENSHIN:
|
|
return record_card
|
|
|
|
return None
|
|
|
|
async def get_genshin_notes_by_stoken(
|
|
self,
|
|
lang: Optional[str] = None,
|
|
) -> Union[NotesWidget, NotesOverseaWidget]:
|
|
"""Get Genshin's real-time notes.
|
|
|
|
Args:
|
|
lang (Optional[str], optional): The language of the data. Defaults to None.
|
|
|
|
Returns:
|
|
NotesWidget (Union[NotesWidget, NotesOverseaWidget]): The requested real-time notes.
|
|
|
|
Raises:
|
|
BadRequest: If the request is invalid.
|
|
"""
|
|
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))
|
|
if self.region == Region.OVERSEAS:
|
|
route = RECORD_URL.get_url(self.region, self.game) / "../community/apihub/api/widget/data"
|
|
params = {"game_id": "2"}
|
|
data = await self.request_lab(route, params=params, lang=lang)
|
|
model = NotesOverseaWidget
|
|
else:
|
|
data = await self._request_genshin_record("widget/v2", endpoint_type="aapi", lang=lang)
|
|
model = NotesWidget
|
|
return model(**data)
|
|
|
|
async def get_genshin_character_list(
|
|
self,
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
) -> List[GenshinCharacterListInfo]:
|
|
"""Retrieve a list of Genshin Impact character information for a player.
|
|
|
|
Args:
|
|
player_id (Optional[int]): The ID of the player. Defaults to None.
|
|
lang (Optional[str]): The language for the character information. Defaults to None.
|
|
|
|
Returns:
|
|
List[GenshinCharacterListInfo]: A list of GenshinCharacterListInfo objects containing character details.
|
|
"""
|
|
|
|
data = await self._request_genshin_record("character/list", player_id, method="POST", lang=lang)
|
|
return [GenshinCharacterListInfo(**i) for i in data["list"]]
|
|
|
|
async def get_genshin_character_detail(
|
|
self,
|
|
characters: List[int],
|
|
player_id: Optional[int] = None,
|
|
*,
|
|
lang: Optional[str] = None,
|
|
) -> GenshinDetailCharacters:
|
|
"""Retrieve detailed information about Genshin Impact characters.
|
|
|
|
Args:
|
|
characters (List[int]): The IDs of the characters to retrieve details for.
|
|
player_id (Optional[int]): The ID of the player. Defaults to None.
|
|
lang (Optional[str]): The language for the character information. Defaults to None.
|
|
|
|
Returns:
|
|
GenshinDetailCharacters: An object containing detailed information.
|
|
"""
|
|
|
|
ids = [characters] if isinstance(characters, int) else characters
|
|
payload = {"character_ids": ids}
|
|
data = await self._request_genshin_record(
|
|
"character/detail", player_id, method="POST", lang=lang, payload=payload
|
|
)
|
|
return GenshinDetailCharacters(**data)
|