Skip to content

Commit

Permalink
Support Imaginarium Theater (#198)
Browse files Browse the repository at this point in the history
* Add models and method

* Add test

* Continue to use Aliased field cuz it magically works again

* Add medal_num field

Co-authored-by: omg-xtao <100690902+omg-xtao@users.noreply.github.com>

* Rename is_enhanced field

* Improve implementation

* Add need_detail param

---------

Co-authored-by: omg-xtao <100690902+omg-xtao@users.noreply.github.com>
  • Loading branch information
seriaati and omg-xtao committed Jul 1, 2024
1 parent 9d0277b commit dd06c8b
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 0 deletions.
17 changes: 17 additions & 0 deletions genshin/client/components/chronicle/genshin.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,23 @@ async def get_genshin_spiral_abyss(

return models.SpiralAbyss(**data)

async def get_imaginarium_theater(
self,
uid: int,
*,
previous: bool = False,
need_detail: bool = True,
lang: typing.Optional[str] = None,
) -> models.ImgTheater:
"""Get Genshin Impact imaginarium theater runs."""
payload = {
"schedule_type": 2 if previous else 1, # There's 1 season for now but I assume it works like this
"need_detail": str(need_detail).lower(),
}
data = await self._request_genshin_record("role_combat", uid, lang=lang, payload=payload)

return models.ImgTheater(**data)

async def get_genshin_notes(
self,
uid: typing.Optional[int] = None,
Expand Down
1 change: 1 addition & 0 deletions genshin/models/genshin/chronicle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .abyss import *
from .activities import *
from .characters import *
from .img_theater import *
from .notes import *
from .stats import *
from .tcg import *
152 changes: 152 additions & 0 deletions genshin/models/genshin/chronicle/img_theater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import datetime
import enum
import typing

if typing.TYPE_CHECKING:
import pydantic.v1 as pydantic
else:
try:
import pydantic.v1 as pydantic
except ImportError:
import pydantic

from genshin.constants import CN_TIMEZONE
from genshin.models.genshin import character
from genshin.models.model import Aliased, APIModel

__all__ = (
"Act",
"ActCharacter",
"ImgTheater",
"TheaterBuff",
"TheaterCharaType",
"TheaterDifficulty",
"TheaterSchedule",
"TheaterStats",
)


class TheaterCharaType(enum.IntEnum):
"""The type of character in the context of the imaginarium theater gamemode."""

NORMAL = 1
TRIAL = 2
SUPPORT = 3


class TheaterDifficulty(enum.IntEnum):
"""The difficulty of the imaginarium theater data."""

EASY = 1
NORMAL = 2
HARD = 3


class ActCharacter(character.BaseCharacter):
"""A character in an act."""

type: TheaterCharaType = Aliased("avatar_type")
level: int


class TheaterBuff(APIModel):
"""Represents either a 'mystery cache' or a 'wondrous boom'."""

icon: str
name: str
description: str = Aliased("desc")
received_audience_support: bool = Aliased("is_enhanced")
"""Whether external audience support is received."""
id: int


class Act(APIModel):
"""One act in the theater."""

characters: typing.Sequence[ActCharacter] = Aliased("avatars")
mystery_caches: typing.Sequence[TheaterBuff] = Aliased("choice_cards")
wondroud_booms: typing.Sequence[TheaterBuff] = Aliased("buffs")
medal_obtained: bool = Aliased("is_get_medal")
round_id: int
finish_time: int # As timestamp
finish_datetime: datetime.datetime = Aliased("finish_date_time")

@pydantic.validator("finish_datetime", pre=True)
def __parse_datetime(cls, value: typing.Mapping[str, typing.Any]) -> datetime.datetime:
return datetime.datetime(
year=value["year"],
month=value["month"],
day=value["day"],
hour=value["hour"],
minute=value["minute"],
second=value["second"],
tzinfo=CN_TIMEZONE,
)


class TheaterStats(APIModel):
"""Imaginarium theater stats."""

difficulty: TheaterDifficulty = Aliased("difficulty_id")
best_record: int = Aliased("max_round_id")
"""The maximum act the player has reached."""
heraldry: int # Not sure what this is
star_challenge_stellas: typing.Sequence[bool] = Aliased("get_medal_round_list")
"""Whether the player has obtained the medal for each act."""
fantasia_flowers_used: int = Aliased("coin_num")
"""The number of Fantasia Flowers used."""
audience_support_trigger_num: int = Aliased("avatar_bonus_num")
"""The number of external audience support triggers."""
player_assists: int = Aliased("rent_cnt")
"""The number of supporting cast characters assisting other players."""
medal_num: int
"""The number of medals the player has obtained."""


class TheaterSchedule(APIModel):
"""Imaginarium theater schedule."""

start_time: int # As timestamp
end_time: int # As timestamp
schedule_type: int # Not sure what this is
id: int = Aliased("schedule_id")
start_datetime: datetime.datetime = Aliased("start_date_time")
end_datetime: datetime.datetime = Aliased("end_date_time")

@pydantic.validator("start_datetime", "end_datetime", pre=True)
def __parse_datetime(cls, value: typing.Mapping[str, typing.Any]) -> datetime.datetime:
return datetime.datetime(
year=value["year"],
month=value["month"],
day=value["day"],
hour=value["hour"],
minute=value["minute"],
second=value["second"],
tzinfo=CN_TIMEZONE,
)


class ImgTheaterData(APIModel):
"""Imaginarium theater data."""

acts: typing.Sequence[Act] = Aliased(alias="rounds_data")
backup_characters: typing.Sequence[ActCharacter] = Aliased(alias="backup_avatars") # Not sure what this is
stats: TheaterStats = Aliased(alias="stat")
schedule: TheaterSchedule
has_data: bool
has_detail_data: bool

@pydantic.root_validator(pre=True)
def __unnest_detail(cls, values: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
detail: typing.Optional[typing.Dict[str, typing.Any]] = values.get("detail")
has_detail = detail is not None
values["rounds_data"] = detail.get("rounds_data", []) if has_detail else []
values["backup_avatars"] = detail.get("backup_avatars", []) if has_detail else []
return values


class ImgTheater(APIModel):
"""Imaginarium theater."""

datas: typing.Sequence[ImgTheaterData] = Aliased("data")
unlocked: bool = Aliased("is_unlock")
6 changes: 6 additions & 0 deletions tests/client/components/test_genshin_chronicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ async def test_spiral_abyss(client: genshin.Client, genshin_uid: int):
assert data


async def test_imaginarium_theater(client: genshin.Client, genshin_uid: int):
data = await client.get_imaginarium_theater(genshin_uid)

assert data


async def test_notes(lclient: genshin.Client, genshin_uid: int):
data = await lclient.get_notes(genshin_uid)

Expand Down

0 comments on commit dd06c8b

Please sign in to comment.