from datetime import timedelta, datetime from typing import Union, Literal, Tuple, List, Optional, Dict, Any from pydantic import Field, root_validator from simnet.models.base import APIModel __all__ = ("Expedition", "Notes", "ExpeditionWidget", "NotesWidget") def _process_timedelta(time: Union[int, timedelta, datetime]) -> datetime: """A helper function that processes time inputs. Args: time (Union[int, timedelta, datetime]): The time input to process. Returns: datetime: The processed datetime. Raises: ValueError: If the input time is less than January 1, 2000. """ if isinstance(time, int): time = datetime.fromtimestamp(time).astimezone() if isinstance(time, timedelta): time = datetime.now().astimezone() + time if time < datetime(2000, 1, 1).astimezone(): delta = timedelta(seconds=int(time.timestamp())) time = datetime.now().astimezone() + delta time = time.replace(second=0, microsecond=0) return time class Expedition(APIModel): """The model for a real-time expedition. Attributes: character (str): The expedition character icon url. status (Literal["Ongoing", "Finished"]): The status of the expedition. remaining_time (timedelta): The remaining time of the expedition. """ character: str = Field(alias="avatar_side_icon") status: Literal["Ongoing", "Finished"] remaining_time: timedelta = Field(alias="remained_time") @property def finished(self) -> bool: """A property that returns whether the expedition has finished.""" return self.remaining_time <= timedelta(0) @property def completion_time(self) -> datetime: """A property that returns the completion time of the expedition.""" return datetime.now().astimezone() + self.remaining_time class TransformerTimedelta(timedelta): """The model for a transformer recovery time.""" @property def timedata(self) -> Tuple[int, int, int, int]: """A property that returns the transformer recovery time in days, hours, minutes, and seconds.""" seconds: int = super().seconds days: int = super().days hour, second = divmod(seconds, 3600) minute, second = divmod(second, 60) return days, hour, minute, second @property def hours(self) -> int: """A property that returns the transformer recovery time in hours.""" return self.timedata[1] @property def minutes(self) -> int: """A property that returns the transformer recovery time in minutes.""" return self.timedata[2] @property def seconds(self) -> int: """A property that returns the transformer recovery time in seconds.""" return self.timedata[3] class Notes(APIModel): """The model for real-time notes. Attributes: current_resin (int): The current amount of resin. max_resin (int): The maximum amount of resin. remaining_resin_recovery_time (timedelta): The remaining time until resin recovery. current_realm_currency (int): The current amount of realm currency. max_realm_currency (int): The maximum amount of realm currency. remaining_realm_currency_recovery_time (timedelta): The remaining time until realm currency recovery. completed_commissions (int): The number of completed commissions. max_commissions (int): The maximum number of commissions. claimed_commission_reward (bool): Whether the commission reward has been claimed. remaining_resin_discounts (int): The number of remaining resin discounts. max_resin_discounts (int): The maximum number of resin discounts. remaining_transformer_recovery_time (Optional[TransformerTimedelta]): The remaining time until transformer recovery. expeditions (List[Expedition]): The list of expeditions. max_expeditions (int): The maximum number of expeditions. Raises: ValueError: If the remaining resin recovery time is less than 0 or greater than 8 hours, or if the remaining realm currency recovery time is less than 0 or greater than 24 hours. """ current_resin: int max_resin: int remaining_resin_recovery_time: timedelta = Field(alias="resin_recovery_time") current_realm_currency: int = Field(alias="current_home_coin") max_realm_currency: int = Field(alias="max_home_coin") remaining_realm_currency_recovery_time: timedelta = Field(alias="home_coin_recovery_time") completed_commissions: int = Field(alias="finished_task_num") max_commissions: int = Field(alias="total_task_num") claimed_commission_reward: bool = Field(alias="is_extra_task_reward_received") remaining_resin_discounts: int = Field(alias="remain_resin_discount_num") max_resin_discounts: int = Field(alias="resin_discount_num_limit") remaining_transformer_recovery_time: Optional[TransformerTimedelta] expeditions: List[Expedition] max_expeditions: int = Field(alias="max_expedition_num") @property def resin_recovery_time(self) -> datetime: """A property that returns the time when resin will be fully recovered.""" return datetime.now().astimezone() + self.remaining_resin_recovery_time @property def realm_currency_recovery_time(self) -> datetime: """A property that returns the time when realm currency will be fully recovered.""" return datetime.now().astimezone() + self.remaining_realm_currency_recovery_time @property def transformer_recovery_time(self) -> Optional[datetime]: """A property that returns the time when the transformer will be fully recovered.""" if self.remaining_transformer_recovery_time is None: return None remaining = datetime.now().astimezone() + self.remaining_transformer_recovery_time return remaining @root_validator(pre=True) def flatten_transformer(cls, values: Dict[str, Any]) -> Dict[str, Any]: """A validator that flattens the transformer recovery time. This method flattens the transformer recovery time from a dictionary format to a TransformerTimedelta format. Args: values (Dict[str, Any]): The input values. Returns: Dict[str, Any]: The values with the transformer recovery time flattened. Raises: ValueError: If the transformer recovery time is less than 0 or greater than 7 days. """ if "transformer_recovery_time" in values: return values if values.get("transformer") and values["transformer"]["obtained"]: t = values["transformer"]["recovery_time"] delta = TransformerTimedelta(days=t["Day"], hours=t["Hour"], minutes=t["Minute"], seconds=t["Second"]) values["remaining_transformer_recovery_time"] = delta else: values["remaining_transformer_recovery_time"] = None if ( values["remaining_transformer_recovery_time"] is not None and values["remaining_transformer_recovery_time"].days > 7 ): raise ValueError("Transformer recovery time cannot exceed 7 days.") return values class ExpeditionWidget(APIModel): """The model for a real-time expedition. Attributes: character (str): The expedition character icon url. status (Literal["Ongoing", "Finished"]): The status of the expedition. """ character: str = Field(alias="avatar_side_icon") status: Literal["Ongoing", "Finished"] class NotesWidget(APIModel): """The model for real-time notes. Attributes: current_resin (int): The current amount of resin. max_resin (int): The maximum amount of resin. remaining_resin_recovery_time (timedelta): The remaining time until resin recovery. current_realm_currency (int): The current amount of realm currency. max_realm_currency (int): The maximum amount of realm currency. completed_commissions (int): The number of completed commissions. max_commissions (int): The maximum number of commissions. claimed_commission_reward (bool): Whether the commission reward has been claimed. expeditions (List[Expedition]): The list of expeditions. max_expeditions (int): The maximum number of expeditions. Raises: ValueError: If the remaining resin recovery time is less than 0 or greater than 8 hours, or if the remaining realm currency recovery time is less than 0 or greater than 24 hours. """ current_resin: int max_resin: int remaining_resin_recovery_time: timedelta = Field(alias="resin_recovery_time") current_realm_currency: int = Field(alias="current_home_coin") max_realm_currency: int = Field(alias="max_home_coin") completed_commissions: int = Field(alias="finished_task_num") max_commissions: int = Field(alias="total_task_num") claimed_commission_reward: bool = Field(alias="is_extra_task_reward_received") expeditions: List[ExpeditionWidget] max_expeditions: int = Field(alias="max_expedition_num") @property def resin_recovery_time(self) -> datetime: """A property that returns the time when resin will be fully recovered.""" return datetime.now().astimezone() + self.remaining_resin_recovery_time