mirror of
https://github.com/PaiGramTeam/SIMNet.git
synced 2024-11-16 03:55:28 +00:00
✨ Support starrail calculator
This commit is contained in:
parent
2d54ab5ca1
commit
f15dcce603
@ -42,7 +42,7 @@ class CalculatorClient(BaseClient):
|
||||
"""
|
||||
params = dict(params or {})
|
||||
|
||||
base_url = CALCULATOR_URL.get_url(self.region)
|
||||
base_url = CALCULATOR_URL.get_url(self.region, self.game)
|
||||
url = base_url / endpoint
|
||||
|
||||
if method == "GET":
|
||||
|
122
simnet/client/components/calculator/starrail.py
Normal file
122
simnet/client/components/calculator/starrail.py
Normal file
@ -0,0 +1,122 @@
|
||||
from typing import Optional, Any, Dict, List, Literal
|
||||
|
||||
from simnet.client.base import BaseClient
|
||||
from simnet.client.routes import CALCULATOR_URL
|
||||
from simnet.models.starrail.calculator import (
|
||||
StarrailCalculatorCharacter,
|
||||
StarrailCalculatorCharacterDetails,
|
||||
)
|
||||
from simnet.utils.enums import Region
|
||||
from simnet.utils.player import recognize_starrail_server
|
||||
|
||||
|
||||
class StarrailCalculatorClient(BaseClient):
|
||||
"""A client for retrieving data from star rail's calculator component."""
|
||||
|
||||
async def request_calculator(
|
||||
self,
|
||||
endpoint: str,
|
||||
*,
|
||||
method: str = "POST",
|
||||
lang: Optional[str] = None,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Make a request towards the calculator endpoint.
|
||||
|
||||
Args:
|
||||
endpoint (str): The calculator endpoint to send the request to.
|
||||
method (str): The HTTP method to use for the request (default "POST").
|
||||
lang (str): The language to use for the request (default None).
|
||||
params (dict): The parameters to include in the request URL (default None).
|
||||
data (dict): The data to include in the request body (default None).
|
||||
|
||||
Returns:
|
||||
dict: The data returned by the calculator endpoint.
|
||||
"""
|
||||
params = dict(params or {})
|
||||
|
||||
base_url = CALCULATOR_URL.get_url(self.region, self.game)
|
||||
url = base_url / endpoint
|
||||
|
||||
if method == "GET":
|
||||
params["game"] = self.game.value
|
||||
params["lang"] = lang or self.lang
|
||||
data = None
|
||||
else:
|
||||
data = dict(data or {})
|
||||
data["lang"] = lang or self.lang
|
||||
|
||||
headers = {}
|
||||
if self.region == Region.CHINESE:
|
||||
headers["Referer"] = "https://webstatic.mihoyo.com/"
|
||||
|
||||
data = await self.request_lab(url, method=method, params=params, data=data, headers=headers)
|
||||
|
||||
return data
|
||||
|
||||
async def get_calculator_characters(
|
||||
self,
|
||||
tab_from: Literal["TabOwned", "TabAll"] = "TabOwned",
|
||||
page: int = 1,
|
||||
size: int = 100,
|
||||
player_id: Optional[int] = None,
|
||||
lang: Optional[str] = None,
|
||||
) -> List[StarrailCalculatorCharacter]:
|
||||
"""Get all characters provided by the Enhancement Progression Calculator.
|
||||
|
||||
Args:
|
||||
tab_from (Literal["TabOwned", "TabAll"], optional): The tab to get characters from. Defaults to "TabOwned".
|
||||
page (int, optional): The page to get characters from. Defaults to 1.
|
||||
size (int, optional): The number of characters to get per page. Defaults to 100.
|
||||
player_id (int): The player ID to use for syncing (default None).
|
||||
lang (str): The language to use for the request (default None).
|
||||
|
||||
Returns:
|
||||
list: A list of CalculatorCharacter objects representing the characters retrieved from the calculator.
|
||||
"""
|
||||
player_id = player_id or self.player_id
|
||||
params = {
|
||||
"tab_from": tab_from,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"uid": player_id,
|
||||
"region": recognize_starrail_server(player_id),
|
||||
}
|
||||
data = await self.request_calculator("avatar/list", method="GET", params=params, lang=lang)
|
||||
return [StarrailCalculatorCharacter(**i) for i in data.get("list", [])]
|
||||
|
||||
async def get_character_details(
|
||||
self,
|
||||
character: int,
|
||||
tab_from: Literal["TabOwned", "TabAll"] = "TabOwned",
|
||||
player_id: Optional[int] = None,
|
||||
lang: Optional[str] = None,
|
||||
) -> StarrailCalculatorCharacterDetails:
|
||||
"""
|
||||
Get the weapon, artifacts and talents of a character.
|
||||
|
||||
Args:
|
||||
character (int): The ID of the character to retrieve details for.
|
||||
tab_from (Literal["TabOwned", "TabAll"], optional): The tab to get characters from. Defaults to "TabOwned".
|
||||
player_id (Optional[int], optional): The player ID to use for the request. Defaults to None.
|
||||
lang (Optional[str], optional): The language to use for the calculator. Defaults to None.
|
||||
|
||||
Returns:
|
||||
StarrailCalculatorCharacterDetails: The details of the character.
|
||||
"""
|
||||
player_id = player_id or self.player_id
|
||||
params = dict(
|
||||
item_id=int(character),
|
||||
uid=player_id,
|
||||
region=recognize_starrail_server(player_id),
|
||||
tab_from=tab_from,
|
||||
change_target_level=0,
|
||||
)
|
||||
data = await self.request_calculator(
|
||||
"avatar/detail",
|
||||
method="GET",
|
||||
lang=lang,
|
||||
params=params,
|
||||
)
|
||||
return StarrailCalculatorCharacterDetails(**data)
|
@ -274,9 +274,15 @@ TAKUMI_URL = InternationalRoute(
|
||||
chinese="https://api-takumi.mihoyo.com/",
|
||||
)
|
||||
|
||||
CALCULATOR_URL = InternationalRoute(
|
||||
overseas="https://sg-public-api.hoyoverse.com/event/calculateos/",
|
||||
chinese="https://api-takumi.mihoyo.com/event/e20200928calculate/v1/",
|
||||
CALCULATOR_URL = GameRoute(
|
||||
overseas=dict(
|
||||
genshin="https://sg-public-api.hoyoverse.com/event/calculateos/",
|
||||
hkrpg="https://sg-public-api.hoyolab.com/event/rpgcalc/",
|
||||
),
|
||||
chinese=dict(
|
||||
genshin="https://api-takumi.mihoyo.com/event/e20200928calculate/v1/",
|
||||
hkrpg="https://api-takumi.mihoyo.com/event/rpgcalc/",
|
||||
),
|
||||
)
|
||||
|
||||
DETAIL_LEDGER_URL = GameRoute(
|
||||
|
@ -1,6 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
from simnet.client.components.auth import AuthClient
|
||||
from simnet.client.components.calculator.starrail import StarrailCalculatorClient
|
||||
from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
from simnet.client.components.diary.starrail import StarrailDiaryClient
|
||||
@ -13,6 +14,7 @@ __all__ = ("StarRailClient",)
|
||||
|
||||
|
||||
class StarRailClient(
|
||||
StarrailCalculatorClient,
|
||||
StarRailBattleChronicleClient,
|
||||
StarRailWishClient,
|
||||
StarrailDiaryClient,
|
||||
|
@ -1,6 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
from simnet.client.components.auth import AuthClient
|
||||
from simnet.client.components.calculator.starrail import StarrailCalculatorClient
|
||||
from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
from simnet.client.components.diary.starrail import StarrailDiaryClient
|
||||
@ -11,6 +12,7 @@ from simnet.utils.enums import Region
|
||||
from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes
|
||||
|
||||
class StarRailClient(
|
||||
StarrailCalculatorClient,
|
||||
StarRailBattleChronicleClient,
|
||||
StarRailWishClient,
|
||||
StarrailDiaryClient,
|
||||
|
180
simnet/models/starrail/calculator.py
Normal file
180
simnet/models/starrail/calculator.py
Normal file
@ -0,0 +1,180 @@
|
||||
"""Starrail calculator models."""
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
from pydantic import Field, validator
|
||||
|
||||
from simnet.models.base import APIModel
|
||||
from simnet.models.starrail.character import StarRailBaseCharacter, StarRailElement, StarRailDestiny
|
||||
|
||||
__all__ = (
|
||||
"CALCULATOR_ELEMENTS",
|
||||
"CALCULATOR_DESTINIES",
|
||||
"StarrailCalculatorCharacter",
|
||||
"StarrailCalculatorWeapon",
|
||||
"StarrailCalculatorSkill",
|
||||
"StarrailCalculatorCharacterDetails",
|
||||
)
|
||||
CALCULATOR_ELEMENTS: Dict[int, StarRailElement] = {
|
||||
1: StarRailElement.Physical,
|
||||
2: StarRailElement.Pyro,
|
||||
4: StarRailElement.Cryo,
|
||||
8: StarRailElement.Electro,
|
||||
16: StarRailElement.Anemo,
|
||||
32: StarRailElement.Quantum,
|
||||
64: StarRailElement.Nombre,
|
||||
}
|
||||
CALCULATOR_DESTINIES: Dict[int, StarRailDestiny] = {
|
||||
1: StarRailDestiny.HuiMie,
|
||||
2: StarRailDestiny.XunLie,
|
||||
3: StarRailDestiny.ZhiShi,
|
||||
4: StarRailDestiny.TongXie,
|
||||
5: StarRailDestiny.XuWu,
|
||||
6: StarRailDestiny.CunHu,
|
||||
7: StarRailDestiny.FengRao,
|
||||
}
|
||||
|
||||
|
||||
class StarrailCalculatorCharacter(StarRailBaseCharacter):
|
||||
"""Character meant to be used with calculators.
|
||||
|
||||
Attributes:
|
||||
id (int): The ID of the character.
|
||||
element (str): The element of the character.
|
||||
icon (str): The icon of the character.
|
||||
name (str): The name of the character.
|
||||
path (str): The path type of the character.
|
||||
max_level (int): The maximum level of the character.
|
||||
cur_level (int): The current level of the character.
|
||||
target_level (int): The target level of the character.
|
||||
is_forward (bool): Whether the character is forward.
|
||||
"""
|
||||
|
||||
id: int = Field(alias="item_id")
|
||||
element: StarRailElement = Field(alias="damage_type")
|
||||
icon: str = Field(alias="icon_url")
|
||||
|
||||
name: str = Field(alias="item_name")
|
||||
path: StarRailDestiny = Field(alias="avatar_base_type")
|
||||
|
||||
max_level: int
|
||||
cur_level: int
|
||||
target_level: int
|
||||
is_forward: bool
|
||||
|
||||
@validator("element", pre=True)
|
||||
def parse_element(cls, v: Any) -> str:
|
||||
"""Parse the element of a character.
|
||||
|
||||
Args:
|
||||
v (Any): The value of the element.
|
||||
|
||||
Returns:
|
||||
str: The parsed element.
|
||||
"""
|
||||
if isinstance(v, str) and v.isnumeric():
|
||||
return CALCULATOR_ELEMENTS[int(v)].value
|
||||
return v
|
||||
|
||||
@validator("path", pre=True)
|
||||
def parse_path(cls, v: Any) -> str:
|
||||
"""Parse the path type of character.
|
||||
|
||||
Args:
|
||||
v (Any): The value of the path type.
|
||||
|
||||
Returns:
|
||||
str: The parsed path type.
|
||||
"""
|
||||
if isinstance(v, str) and v.isnumeric():
|
||||
return CALCULATOR_DESTINIES[int(v)].value
|
||||
return v
|
||||
|
||||
|
||||
class StarrailCalculatorWeapon(APIModel):
|
||||
"""Weapon meant to be used with calculators.
|
||||
|
||||
Attributes:
|
||||
id (int): The ID of the weapon.
|
||||
name (str): The name of the weapon.
|
||||
icon (str): The icon of the weapon.
|
||||
rarity (int): The rarity of the weapon.
|
||||
path (str): The path type of the weapon.
|
||||
max_level (int): The maximum level of the weapon.
|
||||
cur_level (int): The current level of the weapon.
|
||||
target_level (int): The target level of the weapon.
|
||||
is_forward (bool): Whether the weapon is forward.
|
||||
"""
|
||||
|
||||
id: int = Field(alias="item_id")
|
||||
name: str = Field(alias="item_name")
|
||||
icon: str = Field(alias="item_url")
|
||||
rarity: int
|
||||
path: StarRailDestiny = Field(alias="avatar_base_type")
|
||||
|
||||
max_level: int
|
||||
cur_level: int
|
||||
target_level: int
|
||||
is_forward: bool
|
||||
|
||||
@validator("path", pre=True)
|
||||
def parse_path(cls, v: Any) -> str:
|
||||
"""Parse the path type of weapon.
|
||||
|
||||
Args:
|
||||
v (Any): The value of the path type.
|
||||
|
||||
Returns:
|
||||
str: The parsed path type.
|
||||
"""
|
||||
if isinstance(v, str) and v.isnumeric():
|
||||
return CALCULATOR_DESTINIES[int(v)].value
|
||||
return v
|
||||
|
||||
|
||||
class StarrailCalculatorSkill(APIModel):
|
||||
"""Talent of a character meant to be used with calculators.
|
||||
|
||||
Attributes:
|
||||
id (int): The ID of the talent.
|
||||
pre_point (int): The previous point of the talent.
|
||||
point_type (int): The type of the talent.
|
||||
anchor (str): The anchor of the talent.
|
||||
icon (str): The icon of the talent.
|
||||
max_level (int): The maximum level of the talent.
|
||||
cur_level (int): The current level of the talent.
|
||||
target_level (int): The target level of the talent.
|
||||
progress (str): The progress of the talent.
|
||||
min_level_limit (int): The minimum level limit of the talent.
|
||||
"""
|
||||
|
||||
id: int = Field(alias="point_id")
|
||||
pre_point: int
|
||||
point_type: int
|
||||
anchor: str
|
||||
icon: str = Field(alias="item_url")
|
||||
|
||||
max_level: int
|
||||
cur_level: int
|
||||
target_level: int
|
||||
progress: str
|
||||
min_level_limit: int
|
||||
|
||||
@property
|
||||
def learned(self) -> bool:
|
||||
return self.progress == "Learned"
|
||||
|
||||
|
||||
class StarrailCalculatorCharacterDetails(APIModel):
|
||||
"""Details of a synced calculator
|
||||
|
||||
Attributes:
|
||||
avatar (StarrailCalculatorCharacter): The character of the calculator.
|
||||
equipment (Optional[StarrailCalculatorWeapon]): The weapon of the calculator.
|
||||
skills (List[StarrailCalculatorSkill]): The skills of the calculator.
|
||||
skills_other (List[StarrailCalculatorSkill]): The other skills of the calculator.
|
||||
"""
|
||||
|
||||
avatar: StarrailCalculatorCharacter
|
||||
equipment: Optional[StarrailCalculatorWeapon] = None
|
||||
skills: List[StarrailCalculatorSkill]
|
||||
skills_other: List[StarrailCalculatorSkill]
|
@ -1,7 +1,35 @@
|
||||
"""Starrail base character model."""
|
||||
from enum import Enum
|
||||
|
||||
from simnet.models.base import APIModel
|
||||
|
||||
|
||||
class StarRailDestiny(str, Enum):
|
||||
"""命途"""
|
||||
|
||||
HuiMie = "毁灭"
|
||||
ZhiShi = "智识"
|
||||
XunLie = "巡猎"
|
||||
CunHu = "存护"
|
||||
FengRao = "丰饶"
|
||||
TongXie = "同谐"
|
||||
XuWu = "虚无"
|
||||
|
||||
|
||||
class StarRailElement(str, Enum):
|
||||
"""属性"""
|
||||
|
||||
Physical = "物理"
|
||||
Pyro = "火"
|
||||
Anemo = "风"
|
||||
Electro = "雷"
|
||||
Cryo = "冰"
|
||||
Nombre = "虚数"
|
||||
Quantum = "量子"
|
||||
Null = "NULL"
|
||||
"""无"""
|
||||
|
||||
|
||||
class StarRailBaseCharacter(APIModel):
|
||||
"""Base character model."""
|
||||
|
||||
|
@ -5,6 +5,7 @@ import pytest_asyncio
|
||||
|
||||
from simnet.client.components.calculator.genshin import CalculatorClient
|
||||
from simnet.client.components.chronicle.genshin import GenshinBattleChronicleClient
|
||||
from simnet.utils.enums import Game
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from simnet.client.cookies import Cookies
|
||||
@ -14,13 +15,14 @@ if TYPE_CHECKING:
|
||||
@pytest_asyncio.fixture
|
||||
async def calculator_client(genshin_player_id: int, account_id: int, region: "Region", cookies: "Cookies"):
|
||||
if genshin_player_id is None:
|
||||
pytest.skip("Test case test_starrail_battle_chronicle_client skipped: No genshin player id set.")
|
||||
pytest.skip("Test case test_genshin_calculator_client skipped: No genshin player id set.")
|
||||
async with CalculatorClient(
|
||||
player_id=genshin_player_id,
|
||||
cookies=cookies,
|
||||
account_id=account_id,
|
||||
region=region,
|
||||
) as client_instance:
|
||||
client_instance.game = Game.GENSHIN
|
||||
yield client_instance
|
||||
|
||||
|
||||
|
37
tests/test_starrail_calculator_client.py
Normal file
37
tests/test_starrail_calculator_client.py
Normal file
@ -0,0 +1,37 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from simnet.client.components.calculator.starrail import StarrailCalculatorClient
|
||||
from simnet.utils.enums import Game
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from simnet.client.cookies import Cookies
|
||||
from simnet.utils.enums import Region
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def calculator_client(starrail_player_id: int, account_id: int, region: "Region", cookies: "Cookies"):
|
||||
if starrail_player_id is None:
|
||||
pytest.skip("Test case test_starrail_calculator_client skipped: No starrail player id set.")
|
||||
async with StarrailCalculatorClient(
|
||||
player_id=starrail_player_id,
|
||||
cookies=cookies,
|
||||
account_id=account_id,
|
||||
region=region,
|
||||
lang="zh-cn",
|
||||
) as client_instance:
|
||||
client_instance.game = Game.STARRAIL
|
||||
yield client_instance
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestCalculatorClient:
|
||||
@staticmethod
|
||||
async def test_character_details(calculator_client: "StarrailCalculatorClient"):
|
||||
characters = await calculator_client.get_calculator_characters()
|
||||
character_details = await calculator_client.get_character_details(characters[-1].id)
|
||||
assert len(character_details.skills) == 4
|
||||
for talent in character_details.skills:
|
||||
assert talent.cur_level > -1
|
Loading…
Reference in New Issue
Block a user