Add diary client

This commit is contained in:
luoshuijs 2023-06-09 11:48:56 +08:00 committed by GitHub
parent 6f9cee697b
commit 40d55ea0ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 489 additions and 2 deletions

View 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)

View 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)

View 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)

View File

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

View File

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

View File

@ -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/")

View File

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

View File

@ -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
View 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")

View 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")

View 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

View 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

View 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