From 3b2387b2a8b0237fbc1d987a87e1c46e733ad259 Mon Sep 17 00:00:00 2001 From: seria Date: Mon, 9 Dec 2024 14:26:02 +0900 Subject: [PATCH] feat: Add initial mimo support --- genshin/client/components/hoyolab.py | 70 +++++++++++++++++++++++++++- genshin/client/routes.py | 2 + genshin/models/hoyolab/__init__.py | 1 + genshin/models/hoyolab/mimo.py | 60 ++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 genshin/models/hoyolab/mimo.py diff --git a/genshin/client/components/hoyolab.py b/genshin/client/components/hoyolab.py index 2f69c55b..541ef4b2 100644 --- a/genshin/client/components/hoyolab.py +++ b/genshin/client/components/hoyolab.py @@ -238,8 +238,74 @@ async def fetch_mi18n( self, filename: str, game: types.Game, *, lang: typing.Optional[str] = None ) -> typing.Mapping[str, str]: """Fetch a mi18n file.""" - data = await self.request( + return await self.request( routes.MI18N_URL.get_url(types.Region.OVERSEAS, game) / f"{filename}/{filename}-{lang or self.lang}.json", cache=client_cache.cache_key("mi18n", filename=filename, game=game, lang=lang or self.lang), ) - return data + + def _get_mimo_data( + self, game: typing.Optional[typing.Union[types.Game, typing.Literal["hoyolab"]]] = None + ) -> typing.Tuple[int, int]: + """Get the game and version IDs for Mimo tasks.""" + game = game or self.game + if game is None: + raise ValueError("No game specified.") + + if game == "hoyolab": + game_id = 5 + version_id = 23 + elif game is types.Game.ZZZ: + game_id = 8 + version_id = 13 + elif game is types.Game.STARRAIL: + game_id = 6 + version_id = 27 + else: + raise ValueError(f"{game!r} is not supported.") + + return game_id, version_id + + @base.region_specific(types.Region.OVERSEAS) + async def get_mimo_tasks( + self, + *, + game: typing.Optional[typing.Union[types.Game, typing.Literal["hoyolab"]]] = None, + lang: typing.Optional[str] = None, + ) -> typing.Sequence[models.MimoTask]: + """Get a list of Traveling Mimo missions (tasks).""" + game_id, version_id = self._get_mimo_data(game) + data = await self.request( + routes.MIMO_URL.get_url() / "task-list", + params=dict(game_id=game_id, lang=lang or self.lang, version_id=version_id), + ) + return [models.MimoTask(**i) for i in data["task_list"]] + + @base.region_specific(types.Region.OVERSEAS) + async def claim_mimo_task_reward( + self, + task_id: int, + *, + game: typing.Optional[typing.Union[types.Game, typing.Literal["hoyolab"]]] = None, + lang: typing.Optional[str] = None, + ) -> None: + """Claim a Traveling Mimo mission (task) reward.""" + game_id, version_id = self._get_mimo_data(game) + await self.request( + routes.MIMO_URL.get_url() / "receive-point", + params=dict(task_id=task_id, game_id=game_id, lang=lang or self.lang, version_id=version_id), + ) + + async def finish_mimo_task( + self, + task_id: int, + *, + game: typing.Optional[typing.Union[types.Game, typing.Literal["hoyolab"]]] = None, + lang: typing.Optional[str] = None, + ) -> None: + """Finish a Traveling Mimo mission (task) reward.""" + game_id, version_id = self._get_mimo_data(game) + await self.request( + routes.MIMO_URL.get_url() / "finish-task", + data=dict(task_id=task_id, game_id=game_id, lang=lang or self.lang, version_id=version_id), + method="POST", + ) diff --git a/genshin/client/routes.py b/genshin/client/routes.py index 2595b339..77d4249c 100644 --- a/genshin/client/routes.py +++ b/genshin/client/routes.py @@ -338,3 +338,5 @@ def get_url(self, region: types.Region, game: types.Game) -> yarl.URL: ), chinese=dict(), ) + +MIMO_URL = Route("https://sg-public-api.hoyolab.com/event/e2023mimotravel") diff --git a/genshin/models/hoyolab/__init__.py b/genshin/models/hoyolab/__init__.py index 9a911330..d2d1e849 100644 --- a/genshin/models/hoyolab/__init__.py +++ b/genshin/models/hoyolab/__init__.py @@ -3,3 +3,4 @@ from .announcements import * from .private import * from .record import * +from .mimo import * diff --git a/genshin/models/hoyolab/mimo.py b/genshin/models/hoyolab/mimo.py new file mode 100644 index 00000000..eb09de49 --- /dev/null +++ b/genshin/models/hoyolab/mimo.py @@ -0,0 +1,60 @@ +import typing + +import pydantic +from genshin.models.model import APIModel, Aliased +import enum + +__all__ = ("MimoTask", "MimoTaskStatus") + + +class MimoTaskStatus(enum.IntEnum): + """Mimo task status.""" + + FINISHED = 1 + ONGOING = 2 + CLAIMED = 3 + + +class MimoTaskType(enum.IntEnum): + """Mimo task type.""" + + FINISHABLE = 1 + COMMUNITY = 3 + DAILY_LOGIN = 12 + CONSECUTIVE_LOGIN = 13 + IN_GAME = 16 + + +class MimoTask(APIModel): + """Mimo task.""" + + id: int = Aliased("task_id") + name: str = Aliased("task_name") + time_type: int + point: int + progress: int + total_progress: int + + status: MimoTaskStatus + jump_url: str + window_text: str + type: typing.Union[int, MimoTaskType] = Aliased("task_type") + af_url: str + + @pydantic.field_validator("type", mode="before") + def __transform_task_type(cls, v: int) -> typing.Union[int, MimoTaskType]: + try: + return MimoTaskType(v) + except ValueError: + return v + + +class MimoShopItem(APIModel): + """Mimo shop item.""" + + id: int = Aliased("award_id") + status: int + icon: str + name: str + cost: int + stock: int