mirror of
https://github.com/PaiGramTeam/SIMNet.git
synced 2024-11-25 15:42:16 +00:00
✨ Add Lab Client
This commit is contained in:
parent
2a86d10640
commit
5f5c222f17
210
simnet/client/components/lab.py
Normal file
210
simnet/client/components/lab.py
Normal 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),
|
||||||
|
),
|
||||||
|
)
|
@ -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."""
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
62
simnet/models/lab/announcement.py
Normal file
62
simnet/models/lab/announcement.py
Normal 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
29
tests/test_lab_client.py
Normal 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
|
Loading…
Reference in New Issue
Block a user