diff --git a/mihomo/client.py b/mihomo/client.py index 1243f1cf..06190fb8 100644 --- a/mihomo/client.py +++ b/mihomo/client.py @@ -3,8 +3,9 @@ from enum import Enum import aiohttp -from .errors import HttpRequestError +from .errors import HttpRequestError, InvalidParams, UserNotFound from .models import StarrailInfoParsed +from .models.v1 import StarrailInfoParsedV1 from .tools import remove_empty_dict, replace_trailblazer_name @@ -48,6 +49,8 @@ class MihomoAPI: self, uid: int | str, language: Language, + *, + params: dict[str, str] = {}, ) -> typing.Any: """ Makes an HTTP request to the API. @@ -61,18 +64,32 @@ class MihomoAPI: Raises: HttpRequestError: If the HTTP request fails. + InvalidParams: If the API request contains invalid parameters. + UserNotFound: If the requested user is not found. """ url = self.BASE_URL + "/" + str(uid) - params = {} if language != Language.CHS: params.update({"lang": language.value}) + async with aiohttp.ClientSession() as session: async with session.get(url, params=params) as response: - if response.status == 200: - return await response.json(encoding="utf-8") - else: - raise HttpRequestError(response.status, str(response.reason)) + match response.status: + case 200: + return await response.json(encoding="utf-8") + case 400: + try: + data = await response.json(encoding="utf-8") + except: + raise InvalidParams() + else: + if isinstance(data, dict) and (detail := data.get("detail")): + raise InvalidParams(detail) + raise InvalidParams() + case 404: + raise UserNotFound() + case _: + raise HttpRequestError(response.status, str(response.reason)) async def fetch_user(self, uid: int) -> StarrailInfoParsed: """ @@ -86,8 +103,23 @@ class MihomoAPI: """ data = await self.request(uid, self.lang) - data = remove_empty_dict(data) data = StarrailInfoParsed.parse_obj(data) + return data + + async def fetch_user_v1(self, uid: int) -> StarrailInfoParsedV1: + """ + Fetches user data from the API using version 1. + + Args: + uid (int): The user ID. + + Returns: + StarrailInfoParsedV1: The parsed user data from the Mihomo API (version 1). + + """ + data = await self.request(uid, self.lang, params={"version": "v1"}) + data = remove_empty_dict(data) + data = StarrailInfoParsedV1.parse_obj(data) data = replace_trailblazer_name(data) return data diff --git a/mihomo/errors.py b/mihomo/errors.py index 5967bae1..5ecd78ca 100644 --- a/mihomo/errors.py +++ b/mihomo/errors.py @@ -1,9 +1,19 @@ -class HttpRequestError(Exception): - """Http request failed""" +class BaseException(Exception): + """Base exception class.""" + + message: str = "" + + def __init__(self, message: str | None = None, *args: object) -> None: + if message is not None: + self.message = message + super().__init__(self.message, *args) + + +class HttpRequestError(BaseException): + """Exception raised when an HTTP request fails.""" status: int = 0 reason: str = "" - message: str = "" def __init__( self, @@ -18,3 +28,15 @@ class HttpRequestError(Exception): self.reason = reason self.message = message super().__init__(message, *args) + + +class UserNotFound(BaseException): + """Exception raised when a user is not found.""" + + message = "User not found." + + +class InvalidParams(BaseException): + """Exception raised when invalid parameters are provided.""" + + message: str = "Invalid parameters" diff --git a/mihomo/models/__init__.py b/mihomo/models/__init__.py index 39438cfc..8e735d75 100644 --- a/mihomo/models/__init__.py +++ b/mihomo/models/__init__.py @@ -1,4 +1,5 @@ from .base import * from .character import * +from .combat import * from .equipment import * from .player import * diff --git a/mihomo/models/base.py b/mihomo/models/base.py index 76147454..55efd72a 100644 --- a/mihomo/models/base.py +++ b/mihomo/models/base.py @@ -1,7 +1,7 @@ from pydantic import BaseModel, Field from .character import Character -from .player import Player, PlayerSpaceInfo +from .player import Player class StarrailInfoParsed(BaseModel): @@ -9,14 +9,11 @@ class StarrailInfoParsed(BaseModel): Mihomo parsed data Attributes: - - player (`Player`): The player's basic info. - - player_details (`PlayerSpaceInfo`): The player's details. + - player (`Player`): The player's info. - characters (list[`Character`]): The list of characters. """ player: Player """Player's basic info""" - player_details: PlayerSpaceInfo = Field(..., alias="PlayerSpaceInfo") - """Player's details""" characters: list[Character] """The list of characters""" diff --git a/mihomo/models/character.py b/mihomo/models/character.py index da77e0c8..784a55bd 100644 --- a/mihomo/models/character.py +++ b/mihomo/models/character.py @@ -2,66 +2,55 @@ from typing import Any from pydantic import BaseModel, Field, root_validator +from .combat import Attribute, Element, Path, Property from .equipment import LightCone, Relic, RelicSet -class EidolonIcon(BaseModel): - """ - Represents an Eidolon icon. - - Attributes: - - icon (`str`): The eidolon icon. - - unlock (`bool`): Indicates if the eidolon is unlocked. - """ - - icon: str - """The eidolon icon""" - unlock: bool - """Indicates if the eidolon is unlocked""" - - class Trace(BaseModel): """ Represents a character's skill trace. Attributes: + - id (`int`): The ID of the trace. - name (`str`): The name of the trace. - - level (`int`): The level of the trace. + - level (`int`): The current level of the trace. + - max_level (`int`): The maximum level of the trace. + - element (`Element` | None): The element of the trace, or None if not applicable. - type (`str`): The type of the trace. + - type_text (`str`): The type text of the trace. + - effect (`str`): The effect of the trace. + - effect_text (`str`): The effect text of the trace. + - simple_desc (`str`): The simple description of the trace. + - desc (`str`): The detailed description of the trace. - icon (`str`): The trace icon. """ + id: int + """The ID of the trace""" name: str """The name of the trace""" level: int - """The level of the trace""" + """The current level of the trace""" + max_level: int + """The maximum level of the trace""" + element: Element | None = None + """The element of the trace""" type: str """The type of the trace""" + type_text: str + """The type text of the trace""" + effect: str + """The effect of the trace""" + effect_text: str + """The effect text of the trace""" + simple_desc: str + """The simple description of the trace""" + desc: str + """The detailed description of the trace""" icon: str """The trace icon""" -class Stat(BaseModel): - """ - Represents a character's stat. - - Attributes: - - name (`str`): The name of the stat. - - base (`str`): The base value of the stat. - - addition (`str` | `None`): The additional value of the stat, or None if not applicable. - - icon (`str`): The stat icon. - """ - - name: str - """The name of the stat""" - base: str - """The base value of the stat""" - addition: str | None = None - """The additional value of the stat""" - icon: str - """The stat icon""" - - class Character(BaseModel): """ Represents a character. @@ -72,26 +61,25 @@ class Character(BaseModel): - name (`str`): The character's name. - rarity (`int`): The character's rarity. - level (`int`): The character's level. - - Eidolon + - ascension (`int`): Ascension level. - eidolon (`int`): The character's eidolon rank. - - eidolon_text (`str`): The text representation of the eidolon. - - eidolon_icons (list[`EidolonIcon`]): The list of eidolon icons. - Image - icon (`str`): The character avatar image - preview (`str`): The character's preview image. - portrait (`str`): The character's portrait image. - - Combat type - - path (`str`): The character's path. - - path_icon (`str`): The character's path icon. - - element (`str`): The character's element. - - element_icon (`str`): The character's element icon. - - color (`str`): The character's element color. + - Combat + - path (`Path`): The character's path. + - element (`Element`): The character's element. - Equipment - traces (list[`Trace`]): The list of character's skill traces. - light_cone (`LightCone` | `None`): The character's light cone (weapon), or None if not applicable. - relics (list[`Relic`] | `None`): The list of character's relics, or None if not applicable. - relic_set (list[`RelicSet`] | `None`): The list of character's relic sets, or None if not applicable. - stats (list[`Stat`]): The list of character's stats. + - Stats + - attributes (list[`Attribute`]): The list of character's attributes. + - additions (list[`Attribute`]): The list of character's additional attributes. + - properties (list[`Property`]): The list of character's properties. """ id: str @@ -102,52 +90,35 @@ class Character(BaseModel): """Character's rarity""" level: int """Character's level""" - + ascension: int = Field(..., alias="promotion") + """Ascension Level""" eidolon: int = Field(..., alias="rank") """Character's eidolon rank""" - eidolon_text: str = Field(..., alias="rank_text") - """The text representation of the eidolon""" - eidolon_icons: list[EidolonIcon] = Field(..., alias="rank_icons") - """The list of eidolon icons""" + icon: str + """Character avatar image""" preview: str """Character preview image""" portrait: str """Character portrait image""" - path: str + path: Path """Character's path""" - path_icon: str - """Character's path icon""" - - element: str + element: Element """Character's element""" - element_icon: str - """Character's element icon""" - color: str - """Character's element color""" - - traces: list[Trace] = Field(..., alias="skill") + traces: list[Trace] = Field(..., alias="skills") """The list of character's skill traces""" light_cone: LightCone | None = None """Character's light cone (weapon)""" - relics: list[Relic] | None = Field(None, alias="relic") + relics: list[Relic] = [] """The list of character's relics""" - relic_set: list[RelicSet] | None = None + relic_sets: list[RelicSet] = [] """The list of character's relic sets""" - stats: list[Stat] = Field(..., alias="property") - """The list of character's stats""" - @root_validator(pre=True) - def dict_to_list(cls, data: dict[str, Any]): - # The keys of the original dict is not necessary, so remove them here. - if isinstance(data, dict) and data.get("relic") is not None: - if isinstance(data["relic"], dict): - data["relic"] = list(data["relic"].values()) - return data - - @property - def icon(self) -> str: - """Character avatar image""" - return f"icon/character/{self.id}.png" + attributes: list[Attribute] + """The list of character's attributes""" + additions: list[Attribute] + """The list of character's additional attributes""" + properties: list[Property] + """The list of character's properties""" diff --git a/mihomo/models/combat.py b/mihomo/models/combat.py new file mode 100644 index 00000000..8b9f57d6 --- /dev/null +++ b/mihomo/models/combat.py @@ -0,0 +1,97 @@ +from pydantic import BaseModel, Field + + +class Element(BaseModel): + """ + Represents an element. + + Attributes: + - id (`str`): The ID of the element. + - name (`str`): The name of the element. + - color (`str`): The color of the element. + - icon (`str`): The element icon. + """ + + id: str + """The ID of the element""" + name: str + """The name of the element""" + color: str + """The color of the element""" + icon: str + """The element icon""" + + +class Path(BaseModel): + """ + Paths are congregations of Imaginary energy, with which the ideals harmonize. + + Attributes: + - id (`str`): The ID of the path. + - name (`str`): The name of the path. + - icon (`str`): The path icon. + """ + + id: str + """The ID of the path""" + name: str + """The name of the path""" + icon: str + """The path icon""" + + +class Attribute(BaseModel): + """ + Represents an attribute. + + Attributes: + - field (`str`): The field of the attribute. + - name (`str`): The name of the attribute. + - icon (`str`): The attribute icon image. + - value (`float`): The value of the attribute. + - displayed_value (`str`): The displayed value of the attribute. + - is_percent (`bool`): Indicates if the value is in percentage. + """ + + field: str + """The field of the attribute""" + name: str + """The name of the attribute""" + icon: str + """The attribute icon image""" + value: float + """The value of the attribute""" + displayed_value: str = Field(..., alias="display") + """The displayed value of the attribute""" + is_percent: bool = Field(..., alias="percent") + """Indicates if the value is in percentage""" + + +class Property(BaseModel): + """ + Represents a property. + + Attributes: + - type (`str`): The type of the property. + - field (`str`): The field of the property. + - name (`str`): The name of the property. + - icon (`str`): The property icon image. + - value (`float`): The value of the property. + - displayed_value (`str`): The displayed value of the property. + - is_percent (`bool`): Indicates if the value is in percentage. + """ + + type: str + """The type of the property""" + field: str + """The field of the property""" + name: str + """The name of the property""" + icon: str + """The property icon image""" + value: float + """The value of the property""" + displayed_value: str = Field(..., alias="display") + """The displayed value of the property""" + is_percent: bool = Field(..., alias="percent") + """Indicates if the value is in percentage""" diff --git a/mihomo/models/equipment.py b/mihomo/models/equipment.py index 43c69aaa..6c26bb65 100644 --- a/mihomo/models/equipment.py +++ b/mihomo/models/equipment.py @@ -1,38 +1,51 @@ from pydantic import BaseModel, Field +from .combat import Attribute, Path, Property + class LightCone(BaseModel): """ Represents a light cone (weapon). Attributes: + - id (`int`): The ID of the light cone. - name (`str`): The name of the light cone. - rarity (`int`): The rarity of the light cone. - superimpose (`int`): The superimpose rank of the light cone. - level (`int`): The level of the light cone. - - icon (`str`): The light cone icon. + - ascension (`int`): The ascension level of the light cone. + - icon (`str`): The light cone icon image. + - preview (`str`): The light cone preview image. + - portrait (`str`): The light cone portrait image. + - path (`Path`): The path of the light cone. + - attributes (list[`Attribute`]): The list of attributes of the light cone. + - properties (list[`Property`]): The list of properties of the light cone. """ + id: int + """The ID of the light cone""" name: str + """The name of the light cone""" rarity: int + """The rarity of the light cone""" superimpose: int = Field(..., alias="rank") + """The superimpose rank of the light cone""" level: int + """The level of the light cone""" + ascension: int = Field(..., alias="promotion") + """The ascension level of the light cone""" icon: str - - -class RelicProperty(BaseModel): - """ - Represents a property of a relic. - - Attributes: - - name (`str`): The name of the relic property. - - value (`str`): The value of the relic property. - - icon (`str`): The property icon. - """ - - name: str - value: str - icon: str + """The light cone icon image""" + preview: str + """The light cone preview image""" + portrait: str + """The light cone portrait image""" + path: Path + """The path of the light cone""" + attributes: list[Attribute] + """The list of attributes of the light cone""" + properties: list[Property] + """The list of properties of the light cone""" class Relic(BaseModel): @@ -40,7 +53,10 @@ class Relic(BaseModel): Represents a relic. Attributes: + - id (`int`): The ID of the relic. - name (`str`): The name of the relic. + - set_id (`int`): The ID of the relic set. + - set_name (`str`): The name of the relic set. - rarity (`int`): The rarity of the relic. - level (`int`): The level of the relic. - main_property (`RelicProperty`): The main property of the relic. @@ -48,12 +64,24 @@ class Relic(BaseModel): - icon (`str`): The relic icon. """ + id: int + """The ID of the relic""" name: str + """The name of the relic""" + set_id: int + """The ID of the relic set""" + set_name: str + """The name of the relic set""" rarity: int + """The rarity of the relic""" level: int - main_property: RelicProperty - sub_property: list[RelicProperty] + """The level of the relic""" + main_property: Property = Field(..., alias="main_affix") + """The main property of the relic""" + sub_properties: list[Property] = Field(..., alias="sub_affix") + """The list of sub properties of the relic""" icon: str + """The relic icon""" class RelicSet(BaseModel): @@ -61,11 +89,17 @@ class RelicSet(BaseModel): Represents a set of relics. Attributes: + - id (`int`): The ID of the relic set. - name (`str`): The name of the relic set. - - icon (`str`): The relic set icon. - - desc (`int`): The description of the relic set. + - desc (`str`): The description of the relic set. + - properties (list[`Property`]): The list of properties of the relic set. """ + id: int + """The ID of the relic set""" name: str - icon: str - desc: int + """The name of the relic set""" + desc: str + """The description of the relic set""" + properties: list[Property] + """The list of properties of the relic set""" diff --git a/mihomo/models/player.py b/mihomo/models/player.py index 74f5448c..13ec9539 100644 --- a/mihomo/models/player.py +++ b/mihomo/models/player.py @@ -1,28 +1,12 @@ -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, root_validator -class Player(BaseModel): - """ - Player basic info - - Attributes: - - uid (`str`): The player's uid. - - name (`str`): The player's nickname. - - level (`int`): The player's Trailblaze level. - - icon (`str`): The player's profile picture. - - signature (`str`): The player's bio. - """ - - uid: str - """Player's uid""" - name: str - """Player's nickname""" - level: int - """Trailblaze level""" - icon: str +class Avatar(BaseModel): """Profile picture""" - signature: str - """Bio""" + + id: int + name: str + icon: str class ForgottenHall(BaseModel): @@ -30,21 +14,31 @@ class ForgottenHall(BaseModel): Attributes: - memory (`int`): The progress of the memory. - - memory_of_chaos_id (`int` | `None`): The ID of the memory of chaos, or None if not applicable. - - memory_of_chaos (`int` | `None`): The progress of the memory of chaos, or None if not applicable. + - memory_of_chaos_id (`int`): The ID of the memory of chaos, or None if not applicable. + - memory_of_chaos (`int`): The progress of the memory of chaos, or None if not applicable. """ - memory: int = Field(..., alias="PreMazeGroupIndex") + memory: int = Field(..., alias="pre_maze_group_index") """The progress of the memory""" - memory_of_chaos_id: int | None = Field(None, alias="MazeGroupID") - memory_of_chaos: int | None = Field(None, alias="MazeGroupIndex") + memory_of_chaos_id: int = Field(..., alias="maze_group_id") + """The ID of the memory of chaos""" + memory_of_chaos: int = Field(..., alias="maze_group_index") """The progress of the memory of chaos""" -class PlayerSpaceInfo(BaseModel): - """Player details +class Player(BaseModel): + """ + Player basic info Attributes: + - uid (`int`): The player's uid. + - name (`str`): The player's nickname. + - level (`int`): The player's Trailblaze level. + - world_level (`int`): The player's Equilibrium level. + - avatar (`Avatar`): The player's profile picture. + - signature (`str`): The player's bio. + - is_display (`bool`): Is the player's profile display enabled. + - forgotten_hall (`ForgottenHall` | None): The progress of the Forgotten Hall, or None if not applicable. - simulated_universes (`int`): The number of simulated universes passed. - light_cones (`int`): The number of light cones owned. @@ -52,13 +46,28 @@ class PlayerSpaceInfo(BaseModel): - achievements (`int`): The number of achievements unlocked. """ - forgotten_hall: ForgottenHall | None = Field(None, alias="ChallengeData") + uid: int + """Player's uid""" + name: str = Field(..., alias="nickname") + """Player's nickname""" + level: int + """Trailblaze level""" + world_level: int + """Equilibrium level""" + avatar: Avatar + """Profile picture""" + signature: str + """Bio""" + is_display: bool + """Is the player's profile display enabled.""" + + forgotten_hall: ForgottenHall | None = Field(None, alias="challenge_data") """The progress of the Forgotten Hall""" - simulated_universes: int = Field(0, alias="PassAreaProgress") + simulated_universes: int = Field(0, alias="pass_area_progress") """Number of simulated universes passed""" - light_cones: int = Field(0, alias="LightConeCount") + light_cones: int = Field(0, alias="light_cone_count") """Number of light cones owned""" - characters: int = Field(0, alias="AvatarCount") + characters: int = Field(0, alias="avatar_count") """Number of characters owned""" - achievements: int = Field(0, alias="AchievementCount") + achievements: int = Field(0, alias="achievement_count") """Number of achievements unlocked""" diff --git a/mihomo/models/v1/__init__.py b/mihomo/models/v1/__init__.py new file mode 100644 index 00000000..8cd02e99 --- /dev/null +++ b/mihomo/models/v1/__init__.py @@ -0,0 +1,4 @@ +from .base import * +from .character import * +from .equipment import * +from .player import * diff --git a/mihomo/models/v1/base.py b/mihomo/models/v1/base.py new file mode 100644 index 00000000..cb8a2e8e --- /dev/null +++ b/mihomo/models/v1/base.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel, Field + +from .character import Character +from .player import Player, PlayerSpaceInfo + + +class StarrailInfoParsedV1(BaseModel): + """ + Mihomo parsed data V1 + + Attributes: + - player (`Player`): The player's basic info. + - player_details (`PlayerSpaceInfo`): The player's details. + - characters (list[`Character`]): The list of characters. + """ + + player: Player + """Player's basic info""" + player_details: PlayerSpaceInfo = Field(..., alias="PlayerSpaceInfo") + """Player's details""" + characters: list[Character] + """The list of characters""" diff --git a/mihomo/models/v1/character.py b/mihomo/models/v1/character.py new file mode 100644 index 00000000..c9532325 --- /dev/null +++ b/mihomo/models/v1/character.py @@ -0,0 +1,150 @@ +from typing import Any + +from pydantic import BaseModel, Field, root_validator + +from .equipment import LightCone, Relic, RelicSet + + +class EidolonIcon(BaseModel): + """ + Represents an Eidolon icon. + + Attributes: + - icon (`str`): The eidolon icon. + - unlock (`bool`): Indicates if the eidolon is unlocked. + """ + + icon: str + """The eidolon icon""" + unlock: bool + """Indicates if the eidolon is unlocked""" + + +class Trace(BaseModel): + """ + Represents a character's skill trace. + + Attributes: + - name (`str`): The name of the trace. + - level (`int`): The level of the trace. + - type (`str`): The type of the trace. + - icon (`str`): The trace icon. + """ + + name: str + """The name of the trace""" + level: int + """The level of the trace""" + type: str + """The type of the trace""" + icon: str + """The trace icon""" + + +class Stat(BaseModel): + """ + Represents a character's stat. + + Attributes: + - name (`str`): The name of the stat. + - base (`str`): The base value of the stat. + - addition (`str` | `None`): The additional value of the stat, or None if not applicable. + - icon (`str`): The stat icon. + """ + + name: str + """The name of the stat""" + base: str + """The base value of the stat""" + addition: str | None = None + """The additional value of the stat""" + icon: str + """The stat icon""" + + +class Character(BaseModel): + """ + Represents a character. + + Attributes: + - Basic info: + - id (`str`): The character's ID. + - name (`str`): The character's name. + - rarity (`int`): The character's rarity. + - level (`int`): The character's level. + - Eidolon + - eidolon (`int`): The character's eidolon rank. + - eidolon_icons (list[`EidolonIcon`]): The list of eidolon icons. + - Image + - icon (`str`): The character avatar image + - preview (`str`): The character's preview image. + - portrait (`str`): The character's portrait image. + - Combat type + - path (`str`): The character's path. + - path_icon (`str`): The character's path icon. + - element (`str`): The character's element. + - element_icon (`str`): The character's element icon. + - color (`str`): The character's element color. + - Equipment + - traces (list[`Trace`]): The list of character's skill traces. + - light_cone (`LightCone` | `None`): The character's light cone (weapon), or None if not applicable. + - relics (list[`Relic`] | `None`): The list of character's relics, or None if not applicable. + - relic_set (list[`RelicSet`] | `None`): The list of character's relic sets, or None if not applicable. + - stats (list[`Stat`]): The list of character's stats. + """ + + id: str + """Character's ID""" + name: str + """Character's name""" + rarity: int + """Character's rarity""" + level: int + """Character's level""" + + eidolon: int = Field(..., alias="rank") + """Character's eidolon rank""" + eidolon_icons: list[EidolonIcon] = Field(..., alias="rank_icons") + """The list of eidolon icons""" + + preview: str + """Character preview image""" + portrait: str + """Character portrait image""" + + path: str + """Character's path""" + path_icon: str + """Character's path icon""" + + element: str + """Character's element""" + element_icon: str + """Character's element icon""" + + color: str + """Character's element color""" + + traces: list[Trace] = Field(..., alias="skill") + """The list of character's skill traces""" + light_cone: LightCone | None = None + """Character's light cone (weapon)""" + relics: list[Relic] | None = Field(None, alias="relic") + """The list of character's relics""" + relic_set: list[RelicSet] | None = None + """The list of character's relic sets""" + stats: list[Stat] = Field(..., alias="property") + """The list of character's stats""" + + @root_validator(pre=True) + def dict_to_list(cls, data: dict[str, Any]): + # The keys of the original dict is not necessary, so remove them here. + if isinstance(data, dict) and data.get("relic") is not None: + if isinstance(data["relic"], dict): + data["relic"] = list(data["relic"].values()) + return data + + @property + def icon(self) -> str: + """Character avatar image""" + return f"icon/character/{self.id}.png" diff --git a/mihomo/models/v1/equipment.py b/mihomo/models/v1/equipment.py new file mode 100644 index 00000000..f2da5099 --- /dev/null +++ b/mihomo/models/v1/equipment.py @@ -0,0 +1,71 @@ +from pydantic import BaseModel, Field + + +class LightCone(BaseModel): + """ + Represents a light cone (weapon). + + Attributes: + - name (`str`): The name of the light cone. + - rarity (`int`): The rarity of the light cone. + - superimpose (`int`): The superimpose rank of the light cone. + - level (`int`): The level of the light cone. + - icon (`str`): The light cone icon. + """ + + name: str + rarity: int + superimpose: int = Field(..., alias="rank") + level: int + icon: str + + +class RelicProperty(BaseModel): + """ + Represents a property of a relic. + + Attributes: + - name (`str`): The name of the relic property. + - value (`str`): The value of the relic property. + - icon (`str`): The property icon. + """ + + name: str + value: str + icon: str + + +class Relic(BaseModel): + """ + Represents a relic. + + Attributes: + - name (`str`): The name of the relic. + - rarity (`int`): The rarity of the relic. + - level (`int`): The level of the relic. + - main_property (`RelicProperty`): The main property of the relic. + - sub_property (list[`RelicProperty`]): The list of sub properties of the relic. + - icon (`str`): The relic icon. + """ + + name: str + rarity: int + level: int + main_property: RelicProperty + sub_property: list[RelicProperty] + icon: str + + +class RelicSet(BaseModel): + """ + Represents a set of relics. + + Attributes: + - name (`str`): The name of the relic set. + - icon (`str`): The relic set icon. + - desc (`int`): The description of the relic set. + """ + + name: str + icon: str + desc: int diff --git a/mihomo/models/v1/player.py b/mihomo/models/v1/player.py new file mode 100644 index 00000000..98f9999d --- /dev/null +++ b/mihomo/models/v1/player.py @@ -0,0 +1,64 @@ +from pydantic import BaseModel, Field + + +class Player(BaseModel): + """ + Player basic info + + Attributes: + - uid (`str`): The player's uid. + - name (`str`): The player's nickname. + - level (`int`): The player's Trailblaze level. + - icon (`str`): The player's profile picture. + - signature (`str`): The player's bio. + """ + + uid: str + """Player's uid""" + name: str + """Player's nickname""" + level: int + """Trailblaze level""" + icon: str + """Profile picture""" + signature: str + """Bio""" + + +class ForgottenHall(BaseModel): + """The progress of the Forgotten Hall + + Attributes: + - memory (`int`): The progress of the memory. + - memory_of_chaos_id (`int` | `None`): The ID of the memory of chaos, or None if not applicable. + - memory_of_chaos (`int` | `None`): The progress of the memory of chaos, or None if not applicable. + """ + + memory: int | None = Field(None, alias="PreMazeGroupIndex") + """The progress of the memory""" + memory_of_chaos_id: int | None = Field(None, alias="MazeGroupID") + memory_of_chaos: int | None = Field(None, alias="MazeGroupIndex") + """The progress of the memory of chaos""" + + +class PlayerSpaceInfo(BaseModel): + """Player details + + Attributes: + - forgotten_hall (`ForgottenHall` | None): The progress of the Forgotten Hall, or None if not applicable. + - simulated_universes (`int`): The number of simulated universes passed. + - light_cones (`int`): The number of light cones owned. + - characters (`int`): The number of characters owned. + - achievements (`int`): The number of achievements unlocked. + """ + + forgotten_hall: ForgottenHall | None = Field(None, alias="ChallengeData") + """The progress of the Forgotten Hall""" + simulated_universes: int = Field(0, alias="PassAreaProgress") + """Number of simulated universes passed""" + light_cones: int = Field(0, alias="LightConeCount") + """Number of light cones owned""" + characters: int = Field(0, alias="AvatarCount") + """Number of characters owned""" + achievements: int = Field(0, alias="AchievementCount") + """Number of achievements unlocked""" diff --git a/mihomo/tools.py b/mihomo/tools.py index 63682531..25e21b8f 100644 --- a/mihomo/tools.py +++ b/mihomo/tools.py @@ -1,8 +1,10 @@ from typing import TypeVar from .models import Character, StarrailInfoParsed +from .models.v1 import Character, StarrailInfoParsedV1 T = TypeVar("T") +ParsedData = TypeVar("ParsedData", StarrailInfoParsed, StarrailInfoParsedV1) def remove_empty_dict(data: T) -> T: @@ -24,7 +26,7 @@ def remove_empty_dict(data: T) -> T: return data -def replace_trailblazer_name(data: StarrailInfoParsed) -> StarrailInfoParsed: +def replace_trailblazer_name(data: StarrailInfoParsedV1) -> StarrailInfoParsedV1: """ Replaces the trailblazer name with the player's name. @@ -40,17 +42,17 @@ def replace_trailblazer_name(data: StarrailInfoParsed) -> StarrailInfoParsed: return data -def remove_duplicate_character(data: StarrailInfoParsed) -> StarrailInfoParsed: +def remove_duplicate_character(data: ParsedData) -> ParsedData: """ Removes duplicate characters from the given StarrailInfoParsed data. Args: - - data (`StarrailInfoParsed`): The input StarrailInfoParsed data. + - data (`ParsedData`): The input StarrailInfoParsed data. Returns: - - `StarrailInfoParsed`: The updated StarrailInfoParsed data without duplicate characters. + - `ParsedData`: The updated StarrailInfoParsed data without duplicate characters. """ - new_characters: list[Character] = [] + new_characters = [] characters_ids: set[str] = set() for character in data.characters: if character.id not in characters_ids: @@ -60,19 +62,17 @@ def remove_duplicate_character(data: StarrailInfoParsed) -> StarrailInfoParsed: return data -def merge_character_data( - new_data: StarrailInfoParsed, old_data: StarrailInfoParsed -) -> StarrailInfoParsed: +def merge_character_data(new_data: ParsedData, old_data: ParsedData) -> ParsedData: """ Append the old data characters to the list of new data characters. The player's info from the old data will be omitted/discarded. Args: - - new_data (`StarrailInfoParsed`): The new data to be merged. - - old_data (`StarrailInfoParsed`): The old data to merge into. + - new_data (`ParsedData`): The new data to be merged. + - old_data (`ParsedData`): The old data to merge into. Returns: - - `StarrailInfoParsed`: The merged new data. + - `ParsedData`: The merged new data. """ for character in old_data.characters: new_data.characters.append(character) diff --git a/pyproject.toml b/pyproject.toml index e4e71843..5902241d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mihomo" -version = "0.0.1" +version = "1.1.0" authors = [ { name="KT", email="xns77477@gmail.com" }, ]