Skip to content

Commit

Permalink
feat: Update to version 1.1
Browse files Browse the repository at this point in the history
- Add UserNotFound and InvalidParams exception.
- The API has changed its data format.
- Updated the model to accommodate the new format.
- Old format models have been moved to "models.v1".
- Use the "fetch_user_v1" function to retrieve data in the old format.
  • Loading branch information
KT-Yeh committed Jun 8, 2023
1 parent 8dcd3b6 commit 4a892d2
Show file tree
Hide file tree
Showing 15 changed files with 634 additions and 160 deletions.
46 changes: 39 additions & 7 deletions mihomo/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

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


Expand Down Expand Up @@ -48,6 +49,8 @@ async def request(
self,
uid: int | str,
language: Language,
*,
params: dict[str, str] = {},
) -> typing.Any:
"""
Makes an HTTP request to the API.
Expand All @@ -61,18 +64,32 @@ async def request(
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:
"""
Expand All @@ -86,8 +103,23 @@ async def fetch_user(self, uid: int) -> StarrailInfoParsed:
"""
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

Expand Down
28 changes: 25 additions & 3 deletions mihomo/errors.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,3 +28,15 @@ def __init__(
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"
1 change: 1 addition & 0 deletions mihomo/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .base import *
from .character import *
from .combat import *
from .equipment import *
from .player import *
7 changes: 2 additions & 5 deletions mihomo/models/base.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
from pydantic import BaseModel, Field

from .character import Character
from .player import Player, PlayerSpaceInfo
from .player import Player


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"""
129 changes: 50 additions & 79 deletions mihomo/models/character.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,55 @@

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.
Expand All @@ -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
Expand All @@ -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"""
Loading

0 comments on commit 4a892d2

Please sign in to comment.