mirror of
https://github.com/PaiGramTeam/SIMNet.git
synced 2024-11-21 21:58:05 +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.chronicle.genshin import GenshinBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
from simnet.client.components.lab import LabClient
|
||||
from simnet.client.components.wish.genshin import GenshinWishClient
|
||||
from simnet.utils.enum_ import Game
|
||||
|
||||
@ -10,7 +11,11 @@ __all__ = ("GenshinClient",)
|
||||
|
||||
|
||||
class GenshinClient(
|
||||
GenshinBattleChronicleClient, GenshinWishClient, AuthClient, DailyRewardClient
|
||||
GenshinBattleChronicleClient,
|
||||
GenshinWishClient,
|
||||
AuthClient,
|
||||
DailyRewardClient,
|
||||
LabClient,
|
||||
):
|
||||
"""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.chronicle.genshin import GenshinBattleChronicleClient
|
||||
from simnet.client.components.daily import DailyRewardClient
|
||||
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):
|
||||
class GenshinClient(GenshinBattleChronicleClient, GenshinWishClient, AuthClient, DailyRewardClient, LabClient):
|
||||
def __init__(
|
||||
self,
|
||||
cookies: Optional[CookieTypes] = None,
|
||||
|
@ -21,6 +21,10 @@ __all__ = (
|
||||
"AUTH_KEY_URL",
|
||||
"HK4E_LOGIN_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",
|
||||
),
|
||||
)
|
||||
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.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.utils.enum_ import Game
|
||||
|
||||
__all__ = ("StarRailClient",)
|
||||
|
||||
|
||||
class StarRailClient(StarRailBattleChronicleClient, StarRailWishClient, AuthClient):
|
||||
class StarRailClient(
|
||||
StarRailBattleChronicleClient,
|
||||
StarRailWishClient,
|
||||
DailyRewardClient,
|
||||
AuthClient,
|
||||
LabClient,
|
||||
):
|
||||
"""A simple http client for StarRail endpoints."""
|
||||
|
||||
game: Optional[Game] = Game.STARRAIL
|
||||
|
@ -2,11 +2,14 @@ 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.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, AuthClient):
|
||||
|
||||
class StarRailClient(StarRailBattleChronicleClient, StarRailWishClient,DailyRewardClient, AuthClient, LabClient):
|
||||
def __init__(
|
||||
self,
|
||||
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