Add Lab Client

This commit is contained in:
洛水居室 2023-05-08 10:00:01 +08:00 committed by GitHub
parent 2a86d10640
commit 5f5c222f17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 343 additions and 4 deletions

View File

@ -0,0 +1,210 @@
import asyncio
from typing import Optional, List, Dict, Any
from simnet.client.base import BaseClient
from simnet.client.headers import Headers
from simnet.client.routes import TAKUMI_URL, HK4E_URL, CODE_URL
from simnet.models.lab.announcement import Announcement
from simnet.models.lab.record import PartialUser, FullUser
from simnet.utils.enum_ import Region
from simnet.utils.lang import create_short_lang_code
from simnet.utils.player import recognize_genshin_server
from simnet.utils.types import HeaderTypes
__all__ = ("LabClient",)
class LabClient(BaseClient):
"""LabClient component."""
async def request_bbs(
self,
endpoint: str,
*,
lang: Optional[str] = None,
region: Optional[Region] = None,
method: Optional[str] = None,
params: Optional[Dict[str, Any]] = None,
data: Any = None,
headers: Optional[HeaderTypes] = None,
) -> Dict[str, Any]:
"""Makes a request to a bbs endpoint.
Args:
endpoint (str): The URL of the endpoint to make the request to.
lang (str, optional): The language code used for the request. Defaults to None.
region (Region, optional): The server region used for the request. Defaults to None.
method (str, optional): The HTTP method used for the request. Defaults to None.
params (dict, optional): The parameters to include in the request. Defaults to None.
data (any, optional): The data to include in the request. Defaults to None.
headers (dict, optional): The headers to include in the request. Defaults to None.
Returns:
dict: The response data from the request.
"""
headers = Headers(headers)
lang = lang or self.lang
region = region or self.region
url = TAKUMI_URL.get_url(region) / endpoint
if self.region == Region.CHINESE:
headers["Referer"] = "https://www.miyoushe.com/"
data = await self.request_lab(
url,
method=method,
params=params,
data=data,
headers=headers,
lang=lang,
new_ds=self.region == Region.CHINESE,
)
return data
async def search_users(
self,
keyword: str,
*,
lang: Optional[str] = None,
) -> List[PartialUser]:
"""Searches for users by keyword.
Args:
keyword (str): The keyword to search for.
lang (str, optional): The language code used for the request. Defaults to None.
Returns:
list of PartialUser: A list of partial user objects that match the search criteria.
"""
data = await self.request_bbs(
"community/search/wapi/search/user",
lang=lang,
params=dict(keyword=keyword, page_size=20),
)
return [PartialUser(**i["user"]) for i in data["list"]]
async def get_user_info(
self,
accident: Optional[int] = None,
*,
lang: Optional[str] = None,
) -> FullUser:
"""Gets user information for the current or specified user.
Args:
accident (int, optional): The user ID to get information for. Defaults to None.
lang (str, optional): The language code used for the request. Defaults to None.
Returns:
FullUser: A full user object for the specified user.
"""
if self.region == Region.OVERSEAS:
url = "/community/painter/wapi/user/full"
elif self.region == Region.CHINESE:
url = "/user/wapi/getUserFullInfo"
else:
raise TypeError(f"{self.region!r} is not a valid region.")
data = await self.request_bbs(
endpoint=url,
lang=lang,
params=dict(uid=accident) if accident else None,
)
return FullUser(**data["user_info"])
async def get_recommended_users(self, *, limit: int = 200) -> List[PartialUser]:
"""Gets a list of recommended active users.
Args:
limit (int, optional): The maximum number of users to retrieve. Defaults to 200.
Returns:
list of PartialUser: A list of partial user objects for recommended active users.
"""
data = await self.request_bbs(
"community/user/wapi/recommendActive",
params=dict(page_size=limit),
)
return [PartialUser(**i["user"]) for i in data["list"]]
async def get_genshin_announcements(
self,
player_id: Optional[str] = None,
*,
lang: Optional[str] = None,
) -> List[Announcement]:
"""Gets a list of Genshin Impact game announcements.
Args:
player_id (str, optional): The player ID to get announcements for. Defaults to None.
lang (str, optional): The language code used for the request. Defaults to None.
Returns:
list of Announcement: A list of announcement objects for the specified player.
"""
player_id = self.player_id or player_id
if player_id is None:
player_id = 900000005
params = dict(
game="hk4e",
game_biz="hk4e_global",
bundle_id="hk4e_global",
platform="pc",
region=recognize_genshin_server(player_id),
uid=player_id,
level=8,
lang=lang or self.lang,
)
info, details = await asyncio.gather(
self.request_bbs(
HK4E_URL.get_url() / "announcement/api/getAnnList",
lang=lang,
params=params,
),
self.request_bbs(
HK4E_URL.get_url() / "announcement/api/getAnnContent",
lang=lang,
params=params,
),
)
announcements: List[Dict[str, Any]] = []
for sublist in info["list"]:
for info in sublist["list"]:
detail = next(
(i for i in details["list"] if i["ann_id"] == info["ann_id"]), None
)
announcements.append({**info, **(detail or {})})
return [Announcement(**i) for i in announcements]
async def redeem_code(
self,
code: str,
player_id: Optional[int] = None,
*,
lang: Optional[str] = None,
) -> None:
"""Redeems a gift code for the current or specified user.
Args:
code (str): The gift code to redeem.
player_id (int, optional): The player ID to redeem the code for. Defaults to None.
lang (str, optional): The language code used for the request. Defaults to None.
"""
player_id = self.player_id or player_id
await self.request_bbs(
CODE_URL.get_url(),
params=dict(
uid=player_id,
region=recognize_genshin_server(player_id),
cdkey=code,
game_biz="hk4e_global",
lang=create_short_lang_code(lang or self.lang),
),
)

View File

@ -3,6 +3,7 @@ from typing import Optional
from simnet.client.components.auth import AuthClient from simnet.client.components.auth import AuthClient
from simnet.client.components.chronicle.genshin import GenshinBattleChronicleClient from simnet.client.components.chronicle.genshin import GenshinBattleChronicleClient
from simnet.client.components.daily import DailyRewardClient from simnet.client.components.daily import DailyRewardClient
from simnet.client.components.lab import LabClient
from simnet.client.components.wish.genshin import GenshinWishClient from simnet.client.components.wish.genshin import GenshinWishClient
from simnet.utils.enum_ import Game from simnet.utils.enum_ import Game
@ -10,7 +11,11 @@ __all__ = ("GenshinClient",)
class GenshinClient( class GenshinClient(
GenshinBattleChronicleClient, GenshinWishClient, AuthClient, DailyRewardClient GenshinBattleChronicleClient,
GenshinWishClient,
AuthClient,
DailyRewardClient,
LabClient,
): ):
"""A simple http client for StarRail endpoints.""" """A simple http client for StarRail endpoints."""

View File

@ -3,12 +3,13 @@ from typing import Optional
from simnet.client.components.auth import AuthClient from simnet.client.components.auth import AuthClient
from simnet.client.components.chronicle.genshin import GenshinBattleChronicleClient from simnet.client.components.chronicle.genshin import GenshinBattleChronicleClient
from simnet.client.components.daily import DailyRewardClient from simnet.client.components.daily import DailyRewardClient
from simnet.client.components.lab import LabClient
from simnet.client.components.wish.genshin import GenshinWishClient from simnet.client.components.wish.genshin import GenshinWishClient
from simnet.utils.enum_ import Region from simnet.utils.enum_ import Region
from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes
class GenshinClient(GenshinBattleChronicleClient, GenshinWishClient, AuthClient, DailyRewardClient): class GenshinClient(GenshinBattleChronicleClient, GenshinWishClient, AuthClient, DailyRewardClient, LabClient):
def __init__( def __init__(
self, self,
cookies: Optional[CookieTypes] = None, cookies: Optional[CookieTypes] = None,

View File

@ -21,6 +21,10 @@ __all__ = (
"AUTH_KEY_URL", "AUTH_KEY_URL",
"HK4E_LOGIN_URL", "HK4E_LOGIN_URL",
"REWARD_URL", "REWARD_URL",
"TAKUMI_URL",
"CALCULATOR_URL",
"HK4E_URL",
"CODE_URL",
) )
@ -227,3 +231,20 @@ REWARD_URL = GameRoute(
hkrpg="https://api-takumi.mihoyo.com/event/luna/?act_id=e202304121516551", hkrpg="https://api-takumi.mihoyo.com/event/luna/?act_id=e202304121516551",
), ),
) )
TAKUMI_URL = InternationalRoute(
overseas="https://api-os-takumi.mihoyo.com/",
chinese="https://api-takumi.mihoyo.com/",
)
CALCULATOR_URL = InternationalRoute(
overseas="https://sg-public-api.hoyoverse.com/event/calculateos/",
chinese="https://api-takumi.mihoyo.com/event/e20200928calculate/v1/",
)
HK4E_URL = Route("https://sg-hk4e-api.hoyoverse.com/common/hk4e_global/")
CODE_URL = Route(
"https://sg-hk4e-api.hoyoverse.com/common/apicdkey/api/webExchangeCdkey"
)

View File

@ -2,13 +2,21 @@ from typing import Optional
from simnet.client.components.auth import AuthClient from simnet.client.components.auth import AuthClient
from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient
from simnet.client.components.daily import DailyRewardClient
from simnet.client.components.lab import LabClient
from simnet.client.components.wish.starrail import StarRailWishClient from simnet.client.components.wish.starrail import StarRailWishClient
from simnet.utils.enum_ import Game from simnet.utils.enum_ import Game
__all__ = ("StarRailClient",) __all__ = ("StarRailClient",)
class StarRailClient(StarRailBattleChronicleClient, StarRailWishClient, AuthClient): class StarRailClient(
StarRailBattleChronicleClient,
StarRailWishClient,
DailyRewardClient,
AuthClient,
LabClient,
):
"""A simple http client for StarRail endpoints.""" """A simple http client for StarRail endpoints."""
game: Optional[Game] = Game.STARRAIL game: Optional[Game] = Game.STARRAIL

View File

@ -2,11 +2,14 @@ from typing import Optional
from simnet.client.components.auth import AuthClient from simnet.client.components.auth import AuthClient
from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient from simnet.client.components.chronicle.starrail import StarRailBattleChronicleClient
from simnet.client.components.daily import DailyRewardClient
from simnet.client.components.lab import LabClient
from simnet.client.components.wish.starrail import StarRailWishClient from simnet.client.components.wish.starrail import StarRailWishClient
from simnet.utils.enum_ import Region from simnet.utils.enum_ import Region
from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes
class StarRailClient(StarRailBattleChronicleClient, StarRailWishClient, AuthClient):
class StarRailClient(StarRailBattleChronicleClient, StarRailWishClient,DailyRewardClient, AuthClient, LabClient):
def __init__( def __init__(
self, self,
cookies: Optional[CookieTypes] = None, cookies: Optional[CookieTypes] = None,

View File

@ -0,0 +1,62 @@
from datetime import datetime
from pydantic import Field
from simnet.models.base import APIModel
__all__ = ("Announcement",)
class Announcement(APIModel):
"""
Represents an announcement.
Attributes:
id (int): The ID of the announcement.
title (str): The title of the announcement.
subtitle (str): The subtitle of the announcement.
banner (str): The URL of the banner image for the announcement.
content (str): The content of the announcement.
type_label (str): The label of the announcement type.
type (int): The type of the announcement.
tag_icon (str): The URL of the tag icon for the announcement.
login_alert (bool): Indicates whether the announcement is shown to logged-in users only.
remind (bool): Indicates whether to send reminder notifications for the announcement.
alert (bool): Indicates whether to send alert notifications for the announcement.
remind_ver (int): The version of the reminder notification.
extra_remind (bool): Indicates whether to send additional reminder notifications for the announcement.
start_time (datetime): The start time of the announcement.
end_time (datetime): The end time of the announcement.
tag_start_time (datetime): The start time of the tag for the announcement.
tag_end_time (datetime): The end time of the tag for the announcement.
lang (str): The language of the announcement.
has_content (bool): Indicates whether the announcement has content.
"""
id: int = Field(alias="ann_id")
title: str
subtitle: str
banner: str
content: str
type_label: str
type: int
tag_icon: str
login_alert: bool
remind: bool
alert: bool
remind_ver: int
extra_remind: bool
start_time: datetime
end_time: datetime
tag_start_time: datetime
tag_end_time: datetime
lang: str
has_content: bool

29
tests/test_lab_client.py Normal file
View File

@ -0,0 +1,29 @@
from typing import TYPE_CHECKING
import pytest
import pytest_asyncio
from simnet.client.components.lab import LabClient
from simnet.utils.enum_ import Region
if TYPE_CHECKING:
from simnet.client.cookies import Cookies
@pytest_asyncio.fixture
async def client_instance(account_id: int, cookies: "Cookies"):
async with LabClient(
cookies=cookies,
account_id=account_id,
region=Region.CHINESE,
) as client_instance:
yield client_instance
@pytest.mark.asyncio
class TestStarRailClient:
@staticmethod
async def test_get_user_info(account_id: int, client_instance: "LabClient"):
user_info = await client_instance.get_user_info()
assert user_info.nickname
assert user_info.accident_id == account_id