mirror of
https://github.com/PaiGramTeam/SIMNet.git
synced 2024-11-22 06:17:57 +00:00
✨ Add diary client
This commit is contained in:
parent
6f9cee697b
commit
40d55ea0ee
0
simnet/client/components/diary/__init__.py
Normal file
0
simnet/client/components/diary/__init__.py
Normal file
99
simnet/client/components/diary/base.py
Normal file
99
simnet/client/components/diary/base.py
Normal file
@ -0,0 +1,99 @@
|
||||
from datetime import timedelta, timezone, datetime
|
||||
from typing import Optional, Any, Dict
|
||||
|
||||
from simnet.client.base import BaseClient
|
||||
from simnet.client.routes import DETAIL_LEDGER_URL, INFO_LEDGER_URL
|
||||
from simnet.models.diary import DiaryType
|
||||
from simnet.models.genshin.diary import DiaryPage
|
||||
from simnet.utils.enum_ import Region, Game
|
||||
from simnet.utils.player import recognize_server
|
||||
|
||||
__all__ = ("BaseDiaryClient",)
|
||||
|
||||
CN_TIMEZONE = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
class BaseDiaryClient(BaseClient):
|
||||
"""Base diary component."""
|
||||
|
||||
async def request_ledger(
|
||||
self,
|
||||
player_id: Optional[int] = None,
|
||||
*,
|
||||
game: Optional[Game] = None,
|
||||
detail: bool = False,
|
||||
month: Optional[int] = None,
|
||||
lang: Optional[str] = None,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Make a request towards the ys ledger endpoint.
|
||||
|
||||
Args:
|
||||
player_id (Optional[int], optional): The player ID to get the ledger for.
|
||||
game (Optional[Game], optional): The game to get the ledger for.
|
||||
detail (bool, optional): Whether to get the detailed ledger.
|
||||
month (Optional[int], optional): The month to get the ledger for.
|
||||
lang (Optional[str], optional): The language code to use for the request.
|
||||
params (Optional[Dict[str, Any]], optional): The query parameters to use for the request.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: The response data.
|
||||
"""
|
||||
game = game or self.game
|
||||
player_id = player_id or self.player_id
|
||||
params = params or {}
|
||||
|
||||
url = (
|
||||
DETAIL_LEDGER_URL.get_url(self.region)
|
||||
if detail
|
||||
else INFO_LEDGER_URL.get_url(
|
||||
self.region,
|
||||
game,
|
||||
)
|
||||
)
|
||||
|
||||
if self.region == Region.OVERSEAS or game == Game.STARRAIL:
|
||||
params["uid"] = player_id
|
||||
params["region"] = recognize_server(player_id, game)
|
||||
elif self.region == Region.CHINESE:
|
||||
params["bind_uid"] = player_id
|
||||
params["bind_region"] = recognize_server(player_id, game)
|
||||
else:
|
||||
raise TypeError(f"{self.region!r} is not a valid region.")
|
||||
params["month"] = month or (datetime.now().strftime("%Y%m") if game == Game.STARRAIL else datetime.now().month)
|
||||
params["lang"] = lang or self.lang
|
||||
|
||||
return await self.request_lab(url, params=params)
|
||||
|
||||
async def _get_diary_page(
|
||||
self,
|
||||
page: int,
|
||||
*,
|
||||
game: Optional[Game] = None,
|
||||
player_id: Optional[int] = None,
|
||||
diary_type: int = DiaryType.PRIMOGEMS,
|
||||
month: Optional[int] = None,
|
||||
lang: Optional[str] = None,
|
||||
) -> DiaryPage:
|
||||
"""Get a diary page.
|
||||
|
||||
Args:
|
||||
page (int): The page number to get.
|
||||
game (Optional[Game], optional): The game to get the diary page for.
|
||||
player_id (Optional[int], optional): The player ID to get the diary page for.
|
||||
diary_type (int, optional): The diary type to get the diary page for.
|
||||
month (Optional[int], optional): The month to get the diary page for.
|
||||
lang (Optional[str], optional): The language code to use for the request.
|
||||
|
||||
Returns:
|
||||
DiaryPage: The diary page.
|
||||
"""
|
||||
data = await self.request_ledger(
|
||||
player_id,
|
||||
game=game,
|
||||
detail=True,
|
||||
month=month,
|
||||
lang=lang,
|
||||
params=dict(type=diary_type, current_page=page, page_size=100),
|
||||
)
|
||||
return DiaryPage(**data)
|
29
simnet/client/components/diary/genshin.py
Normal file
29
simnet/client/components/diary/genshin.py
Normal file
@ -0,0 +1,29 @@
|
||||
from typing import Optional
|
||||
|
||||
from simnet.client.components.diary.base import BaseDiaryClient
|
||||
from simnet.models.genshin.diary import Diary
|
||||
from simnet.utils.enum_ import Game
|
||||
|
||||
|
||||
class GenshinDiaryClient(BaseDiaryClient):
|
||||
"""Genshin diary component."""
|
||||
|
||||
async def get_genshin_diary(
|
||||
self,
|
||||
player_id: Optional[int] = None,
|
||||
*,
|
||||
month: Optional[int] = None,
|
||||
lang: Optional[str] = None,
|
||||
) -> Diary:
|
||||
"""Get a traveler's diary with earning details for the month.
|
||||
|
||||
Args:
|
||||
player_id (int, optional): The player's ID. Defaults to None.
|
||||
month (int, optional): The month to get the diary for. Defaults to None.
|
||||
lang (str, optional): The language to get the diary in. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Diary: The diary for the month.
|
||||
"""
|
||||
data = await self.request_ledger(player_id, game=Game.GENSHIN, month=month, lang=lang)
|
||||
return Diary(**data)
|
29
simnet/client/components/diary/starrail.py
Normal file
29
simnet/client/components/diary/starrail.py
Normal file
@ -0,0 +1,29 @@
|
||||
from typing import Optional
|
||||
|
||||
from simnet.client.components.diary.base import BaseDiaryClient
|
||||
from simnet.models.starrail.diary import StarRailDiary
|
||||
from simnet.utils.enum_ import Game
|
||||
|
||||
|
||||
class StarrailDiaryClient(BaseDiaryClient):
|
||||
"""Starrail diary component."""
|
||||
|
||||
async def get_starrail_diary(
|
||||
self,
|
||||
player_id: Optional[int] = None,
|
||||
*,
|
||||
month: Optional[int] = None,
|
||||
lang: Optional[str] = None,
|
||||
) -> StarRailDiary:
|
||||
"""Get a traveler's diary with earning details for the month.
|
||||
|
||||
Args:
|
||||
player_id (int, optional): The player's ID. Defaults to None.
|
||||
month (int, optional): The month to get the diary for. Defaults to None.
|
||||
lang (str, optional): The language to get the diary in. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Diary: The diary for the month.
|
||||
"""
|
||||
data = await self.request_ledger(player_id, game=Game.STARRAIL, month=month, lang=lang)
|
||||
return StarRailDiary(**data)
|
@ -3,6 +3,7 @@ from typing import Optional
|
||||
from simnet.client.components.auth import AuthClient
|
||||
from simnet.client.components.chronicle.genshin import GenshinBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
from simnet.client.components.diary.genshin import GenshinDiaryClient
|
||||
from simnet.client.components.lab import LabClient
|
||||
from simnet.client.components.wish.genshin import GenshinWishClient
|
||||
from simnet.utils.enum_ import Game
|
||||
@ -13,6 +14,7 @@ __all__ = ("GenshinClient",)
|
||||
class GenshinClient(
|
||||
GenshinBattleChronicleClient,
|
||||
GenshinWishClient,
|
||||
GenshinDiaryClient,
|
||||
AuthClient,
|
||||
DailyRewardClient,
|
||||
LabClient,
|
||||
|
@ -3,13 +3,16 @@ from typing import Optional
|
||||
from simnet.client.components.auth import AuthClient
|
||||
from simnet.client.components.chronicle.genshin import GenshinBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
from simnet.client.components.diary.genshin import GenshinDiaryClient
|
||||
from simnet.client.components.lab import LabClient
|
||||
from simnet.client.components.wish.genshin import GenshinWishClient
|
||||
from simnet.utils.enum_ import Region
|
||||
from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes
|
||||
|
||||
|
||||
class GenshinClient(GenshinBattleChronicleClient, GenshinWishClient, AuthClient, DailyRewardClient, LabClient):
|
||||
class GenshinClient(
|
||||
GenshinBattleChronicleClient, GenshinWishClient, GenshinDiaryClient, AuthClient, DailyRewardClient, LabClient
|
||||
):
|
||||
def __init__(
|
||||
self,
|
||||
cookies: Optional[CookieTypes] = None,
|
||||
|
@ -23,6 +23,8 @@ __all__ = (
|
||||
"REWARD_URL",
|
||||
"TAKUMI_URL",
|
||||
"CALCULATOR_URL",
|
||||
"DETAIL_LEDGER_URL",
|
||||
"INFO_LEDGER_URL",
|
||||
"HK4E_URL",
|
||||
"CODE_URL",
|
||||
)
|
||||
@ -237,6 +239,22 @@ CALCULATOR_URL = InternationalRoute(
|
||||
chinese="https://api-takumi.mihoyo.com/event/e20200928calculate/v1/",
|
||||
)
|
||||
|
||||
DETAIL_LEDGER_URL = InternationalRoute(
|
||||
overseas="https://sg-hk4e-api.hoyolab.com/event/ysledgeros/month_detail",
|
||||
chinese="https://hk4e-api.mihoyo.com/event/ys_ledger/monthDetail",
|
||||
)
|
||||
|
||||
INFO_LEDGER_URL = GameRoute(
|
||||
overseas=dict(
|
||||
genshin="https://sg-hk4e-api.hoyolab.com/event/ysledgeros/month_info",
|
||||
hkrpg="",
|
||||
),
|
||||
chinese=dict(
|
||||
genshin="https://hk4e-api.mihoyo.com/event/ys_ledger/monthInfo",
|
||||
hkrpg="https://api-takumi.mihoyo.com/event/srledger/month_info",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
HK4E_URL = Route("https://sg-hk4e-api.hoyoverse.com/common/hk4e_global/")
|
||||
|
||||
|
@ -3,6 +3,7 @@ from typing import Optional
|
||||
from simnet.client.components.auth import AuthClient
|
||||
from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
from simnet.client.components.diary.starrail import StarrailDiaryClient
|
||||
from simnet.client.components.lab import LabClient
|
||||
from simnet.client.components.wish.starrail import StarRailWishClient
|
||||
from simnet.utils.enum_ import Game
|
||||
@ -13,6 +14,7 @@ __all__ = ("StarRailClient",)
|
||||
class StarRailClient(
|
||||
StarRailBattleChronicleClient,
|
||||
StarRailWishClient,
|
||||
StarrailDiaryClient,
|
||||
DailyRewardClient,
|
||||
AuthClient,
|
||||
LabClient,
|
||||
|
@ -3,13 +3,16 @@ from typing import Optional
|
||||
from simnet.client.components.auth import AuthClient
|
||||
from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
from simnet.client.components.diary.starrail import StarrailDiaryClient
|
||||
from simnet.client.components.lab import LabClient
|
||||
from simnet.client.components.wish.starrail import StarRailWishClient
|
||||
from simnet.utils.enum_ import Region
|
||||
from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes
|
||||
|
||||
|
||||
class StarRailClient(StarRailBattleChronicleClient, StarRailWishClient,DailyRewardClient, AuthClient, LabClient):
|
||||
class StarRailClient(
|
||||
StarRailBattleChronicleClient, StarRailWishClient, StarrailDiaryClient, DailyRewardClient, AuthClient, LabClient
|
||||
):
|
||||
def __init__(
|
||||
self,
|
||||
cookies: Optional[CookieTypes] = None,
|
||||
|
43
simnet/models/diary.py
Normal file
43
simnet/models/diary.py
Normal file
@ -0,0 +1,43 @@
|
||||
from enum import IntEnum
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from simnet.models.base import APIModel
|
||||
|
||||
__all__ = (
|
||||
"DiaryType",
|
||||
"BaseDiary",
|
||||
)
|
||||
|
||||
|
||||
class DiaryType(IntEnum):
|
||||
"""Type of diary pages.
|
||||
|
||||
0: Unknown
|
||||
1: Primogems
|
||||
2: Mora
|
||||
"""
|
||||
|
||||
UNKNOWN = 0
|
||||
|
||||
PRIMOGEMS = 1
|
||||
"""Primogems."""
|
||||
|
||||
MORA = 2
|
||||
"""Mora."""
|
||||
|
||||
|
||||
class BaseDiary(APIModel):
|
||||
"""Base model for diary and diary page.
|
||||
|
||||
Attributes:
|
||||
uid: User ID.
|
||||
server: Server name.
|
||||
nickname: User nickname.
|
||||
month: Month of the diary page.
|
||||
"""
|
||||
|
||||
uid: int
|
||||
server: str = Field(alias="region")
|
||||
nickname: str = ""
|
||||
month: int = Field(alias="data_month")
|
113
simnet/models/genshin/diary.py
Normal file
113
simnet/models/genshin/diary.py
Normal file
@ -0,0 +1,113 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from simnet.models.base import APIModel
|
||||
from simnet.models.diary import BaseDiary
|
||||
|
||||
__all__ = (
|
||||
"DayDiaryData",
|
||||
"Diary",
|
||||
"DiaryAction",
|
||||
"DiaryActionCategory",
|
||||
"DiaryPage",
|
||||
"MonthDiaryData",
|
||||
)
|
||||
|
||||
|
||||
class DiaryActionCategory(APIModel):
|
||||
"""Diary category for primogems.
|
||||
|
||||
Attributes:
|
||||
id: Category ID.
|
||||
name: Category name.
|
||||
amount: Amount of primogems.
|
||||
percentage: Percentage of primogems.
|
||||
"""
|
||||
|
||||
id: int = Field(alias="action_id")
|
||||
name: str = Field(alias="action")
|
||||
amount: int = Field(alias="num")
|
||||
percentage: int = Field(alias="percent")
|
||||
|
||||
|
||||
class MonthDiaryData(APIModel):
|
||||
"""Diary data for a month.
|
||||
|
||||
Attributes:
|
||||
current_primogems: Current amount of primogems.
|
||||
current_mora: Current amount of mora.
|
||||
last_primogems: Last amount of primogems.
|
||||
last_mora: Last amount of mora.
|
||||
primogems_rate: Primogems rate.
|
||||
mora_rate: Mora rate.
|
||||
categories: List of diary categories.
|
||||
"""
|
||||
|
||||
def __init__(self, **data):
|
||||
if data.get("primogem_rate"):
|
||||
data["primogems_rate"] = data.pop("primogem_rate")
|
||||
super().__init__(**data)
|
||||
|
||||
current_primogems: int
|
||||
current_mora: int
|
||||
last_primogems: int
|
||||
last_mora: int
|
||||
primogems_rate: int
|
||||
mora_rate: int
|
||||
categories: List[DiaryActionCategory] = Field(alias="group_by")
|
||||
|
||||
|
||||
class DayDiaryData(APIModel):
|
||||
"""Diary data for a day.
|
||||
|
||||
Attributes:
|
||||
current_primogems: Current amount of primogems.
|
||||
current_mora: Current amount of mora.
|
||||
"""
|
||||
|
||||
current_primogems: int
|
||||
current_mora: int
|
||||
|
||||
|
||||
class Diary(BaseDiary):
|
||||
"""Traveler's diary.
|
||||
|
||||
Attributes:
|
||||
data: Diary data for a month.
|
||||
day_data: Diary data for a day.
|
||||
"""
|
||||
|
||||
data: MonthDiaryData = Field(alias="month_data")
|
||||
day_data: DayDiaryData
|
||||
|
||||
@property
|
||||
def month_data(self) -> MonthDiaryData:
|
||||
return self.data
|
||||
|
||||
|
||||
class DiaryAction(APIModel):
|
||||
"""Action which earned currency.
|
||||
|
||||
Attributes:
|
||||
action_id: Action ID.
|
||||
action: Action name.
|
||||
time: Time of the action.
|
||||
amount: Amount of the action.
|
||||
"""
|
||||
|
||||
action_id: int
|
||||
action: str
|
||||
time: datetime = Field(timezone=8)
|
||||
amount: int = Field(alias="num")
|
||||
|
||||
|
||||
class DiaryPage(BaseDiary):
|
||||
"""Page of a diary.
|
||||
|
||||
Attributes:
|
||||
actions: List of diary actions.
|
||||
"""
|
||||
|
||||
actions: List[DiaryAction] = Field(alias="list")
|
84
simnet/models/starrail/diary.py
Normal file
84
simnet/models/starrail/diary.py
Normal file
@ -0,0 +1,84 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from simnet.models.base import APIModel
|
||||
from simnet.models.diary import BaseDiary
|
||||
|
||||
__all__ = (
|
||||
"DiaryActionCategory",
|
||||
"MonthDiaryData",
|
||||
"DayDiaryData",
|
||||
"StarRailDiary",
|
||||
)
|
||||
|
||||
|
||||
class DiaryActionCategory(APIModel):
|
||||
"""Diary category for rails_pass .
|
||||
|
||||
Attributes:
|
||||
id: Category ID.
|
||||
name: Category name.
|
||||
amount: Amount of rails_pass.
|
||||
percentage: Percentage of rails_pass.
|
||||
"""
|
||||
|
||||
id: str = Field(alias="action")
|
||||
name: str = Field(alias="action_name")
|
||||
amount: int = Field(alias="num")
|
||||
percentage: int = Field(alias="percent")
|
||||
|
||||
|
||||
class MonthDiaryData(APIModel):
|
||||
"""Diary data for a month.
|
||||
|
||||
Attributes:
|
||||
current_hcoin: Current amount of hcoin.
|
||||
current_rails_pass: Current amount of rails_pass.
|
||||
last_hcoin: Last amount of hcoin.
|
||||
last_rails_pass: Last amount of rails_pass.
|
||||
hcoin_rate: hcoin rate.
|
||||
rails_rate: rails_pass rate.
|
||||
categories: List of diary categories.
|
||||
"""
|
||||
|
||||
current_hcoin: int
|
||||
current_rails_pass: int
|
||||
last_hcoin: int
|
||||
last_rails_pass: int
|
||||
hcoin_rate: int
|
||||
rails_rate: int
|
||||
categories: List[DiaryActionCategory] = Field(alias="group_by")
|
||||
|
||||
|
||||
class DayDiaryData(APIModel):
|
||||
"""Diary data for a day.
|
||||
|
||||
Attributes:
|
||||
current_hcoin: Current amount of hcoin.
|
||||
current_rails_pass: Current amount of rails_pass.
|
||||
last_hcoin: Last amount of hcoin.
|
||||
last_rails_pass: Last amount of rails_pass.
|
||||
"""
|
||||
|
||||
current_hcoin: int
|
||||
current_rails_pass: int
|
||||
last_hcoin: int
|
||||
last_rails_pass: int
|
||||
|
||||
|
||||
class StarRailDiary(BaseDiary):
|
||||
"""Traveler's diary.
|
||||
|
||||
Attributes:
|
||||
data: Diary data for a month.
|
||||
day_data: Diary data for a day.
|
||||
"""
|
||||
|
||||
data: MonthDiaryData = Field(alias="month_data")
|
||||
day_data: DayDiaryData
|
||||
|
||||
@property
|
||||
def month_data(self) -> MonthDiaryData:
|
||||
"""Diary data for a month."""
|
||||
return self.data
|
31
tests/test_genshin_diary_client.py
Normal file
31
tests/test_genshin_diary_client.py
Normal file
@ -0,0 +1,31 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from simnet.client.components.diary.genshin import GenshinDiaryClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from simnet.client.cookies import Cookies
|
||||
from simnet.utils.enum_ import Region
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def diary_client(genshin_player_id: int, account_id: int, region: "Region", cookies: "Cookies"):
|
||||
if genshin_player_id is None:
|
||||
pytest.skip("Test case test_genshin_diary_client skipped: No genshin player id set.")
|
||||
async with GenshinDiaryClient(
|
||||
player_id=genshin_player_id,
|
||||
cookies=cookies,
|
||||
account_id=account_id,
|
||||
region=region,
|
||||
) as client_instance:
|
||||
yield client_instance
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestGenshinDiaryClient:
|
||||
@staticmethod
|
||||
async def test_get_genshin_diary(diary_client: "GenshinDiaryClient"):
|
||||
genshin_diary = await diary_client.get_genshin_diary()
|
||||
assert genshin_diary is not None
|
31
tests/test_starrail_diary_client.py
Normal file
31
tests/test_starrail_diary_client.py
Normal file
@ -0,0 +1,31 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from simnet.client.components.diary.starrail import StarrailDiaryClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from simnet.client.cookies import Cookies
|
||||
from simnet.utils.enum_ import Region
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def diary_client(starrail_player_id: int, account_id: int, region: "Region", cookies: "Cookies"):
|
||||
if starrail_player_id is None:
|
||||
pytest.skip("Test case test_starrail_diary_client skipped: No starrail player id set.")
|
||||
async with StarrailDiaryClient(
|
||||
player_id=starrail_player_id,
|
||||
cookies=cookies,
|
||||
account_id=account_id,
|
||||
region=region,
|
||||
) as client_instance:
|
||||
yield client_instance
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestStarrailDiaryClient:
|
||||
@staticmethod
|
||||
async def test_get_starrail_diary(diary_client: "StarrailDiaryClient"):
|
||||
genshin_diary = await diary_client.get_starrail_diary()
|
||||
assert genshin_diary is not None
|
Loading…
Reference in New Issue
Block a user