first commit

This commit is contained in:
KT 2023-05-28 21:13:13 +08:00
commit dcd09ee97e
13 changed files with 534 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
__pycache__
venv
build
mihomo.egg-info
*.ipynb

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2023 KT
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

40
README.md Normal file
View File

@ -0,0 +1,40 @@
# mihomo
A simple Python Pydantic model (type hint and autocompletion support) for Honkai: Star Rail parsed data from the Mihomo API.
API url: https://api.mihomo.me/sr_info_parsed/{UID}?lang={LANG}
## Installation
```
pip install -U git+https://github.com/KT-Yeh/mihomo.git
```
## Usage
An example for https://api.mihomo.me/sr_info_parsed/800333171?lang=en
```py
import asyncio
from mihomo import MihomoAPI, Language
client = MihomoAPI(language=Language.EN)
async def main():
data = await client.fetch_user(800333171)
print(f"Name: {data.player.name}")
print(f"Level: {data.player.level}")
print(f"Signature: {data.player.signature}")
print(f"Achievements: {data.player_details.achievements}")
print(f"Characters count: {data.player_details.characters}")
print(f"Profile picture url: {client.get_icon_url(data.player.icon)}")
for character in data.characters:
print("-----------")
print(f"Name: {character.name}")
print(f"rarity: {character.rarity}")
print(f"Level: {character.level}")
print(f"Avatar url: {client.get_icon_url(character.icon)}")
print(f"Preview url: {client.get_icon_url(character.preview)}")
print(f"portrait url: {client.get_icon_url(character.portrait)}")
asyncio.run(main())
```

3
mihomo/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .client import *
from .errors import *
from .models import *

102
mihomo/client.py Normal file
View File

@ -0,0 +1,102 @@
import typing
from enum import Enum
import aiohttp
from .errors import HttpRequestError
from .models import StarrailInfoParsed
from .tools import remove_empty_dict, replace_trailblazer_name
class Language(Enum):
CHT = "cht"
DE = "de"
EN = "en"
ES = "es"
FR = "fr"
ID = "id"
JP = "jp"
KR = "kr"
PT = "pt"
RU = "ru"
TH = "th"
VI = "vi"
class MihomoAPI:
"""
Represents an client for Mihomo API.
Args:
language (Language, optional):
The language to use for API responses.Defaults to Language.CHT.
Attributes:
- BASE_URL (str): The base URL of the API.
- ASSET_URL (str): The base URL for the asset files.
"""
BASE_URL: typing.Final[str] = "https://api.mihomo.me/sr_info_parsed"
ASSET_URL: typing.Final[str] = "https://raw.githubusercontent.com/Mar-7th/StarRailRes/master"
def __init__(self, language: Language = Language.CHT):
self.lang = language
async def request(
self,
uid: int | str,
language: Language,
) -> typing.Any:
"""
Makes an HTTP request to the API.
Args:
- uid (int | str): The user ID.
- language (Language): The language to use for the API response.
Returns:
typing.Any: The response from the API.
Raises:
HttpRequestError: If the HTTP request fails.
"""
url = self.BASE_URL + "/" + str(uid)
params = {"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))
async def fetch_user(self, uid: int) -> StarrailInfoParsed:
"""
Fetches user data from the API.
Args:
uid (int): The user ID.
Returns:
StarrailInfoParsed: The parsed user data from mihomo API.
"""
data = await self.request(uid, self.lang)
data = remove_empty_dict(data)
data = StarrailInfoParsed.parse_obj(data)
data = replace_trailblazer_name(data)
return data
def get_icon_url(self, icon: str) -> str:
"""
Gets the asset url for the given icon.
Args:
icon (str): The icon name.
Returns:
str: The asset url for the icon.
"""
return self.ASSET_URL + "/" + icon

20
mihomo/errors.py Normal file
View File

@ -0,0 +1,20 @@
class HttpRequestError(Exception):
"""Http request failed"""
status: int = 0
reason: str = ""
message: str = ""
def __init__(
self,
status: int,
reason: str,
message: str | None = None,
*args: object,
) -> None:
if not message:
message = f"[{status}] {reason}"
self.status = status
self.reason = reason
self.message = message
super().__init__(message, *args)

View File

@ -0,0 +1,4 @@
from .base import *
from .character import *
from .equipment import *
from .player import *

22
mihomo/models/base.py Normal file
View File

@ -0,0 +1,22 @@
from pydantic import BaseModel, Field
from .character import Character
from .player import Player, PlayerSpaceInfo
class StarrailInfoParsed(BaseModel):
"""
Mihomo parsed data
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"""

152
mihomo/models/character.py Normal file
View File

@ -0,0 +1,152 @@
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_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.
- 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_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"""
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 data.get("relic") is not None:
data["relic"] = list(data["relic"].values())
return data
@property
def icon(self) -> str:
"""Character avatar image"""
return f"icon/character/{self.id}.png"

View File

@ -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

64
mihomo/models/player.py Normal file
View File

@ -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 = Field(..., 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"""

22
mihomo/tools.py Normal file
View File

@ -0,0 +1,22 @@
from typing import TypeVar
from .models import StarrailInfoParsed
T = TypeVar("T")
def remove_empty_dict(data: T) -> T:
if isinstance(data, dict):
for key in data.keys():
data[key] = None if (data[key] == {}) else remove_empty_dict(data[key])
elif isinstance(data, list):
for i in range(len(data)):
data[i] = remove_empty_dict(data[i])
return data
def replace_trailblazer_name(data: StarrailInfoParsed) -> StarrailInfoParsed:
for i in range(len(data.characters)):
if data.characters[i].name == r"{NICKNAME}":
data.characters[i].name = data.player.name
return data

22
pyproject.toml Normal file
View File

@ -0,0 +1,22 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "mihomo"
version = "0.0.1"
authors = [
{ name="KT", email="xns77477@gmail.com" },
]
description = "A simple Python Pydantic model for Honkai: Star Rail parsed data from the Mihomo API."
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
"aiohttp",
"pydantic",
]