diff --git a/simnet/client/account/auth.py b/simnet/client/account/auth.py new file mode 100644 index 0000000..66e44c0 --- /dev/null +++ b/simnet/client/account/auth.py @@ -0,0 +1,173 @@ +from typing import Optional, NoReturn + +from simnet.client.base import BaseClient +from simnet.client.routes import ( + AUTH_URL, + AUTH_KEY_URL, + HK4E_LOGIN_URL, + GET_COOKIES_TOKEN_BY_STOKEN_URL, + GET_LTOKEN_BY_STOKEN_URL, +) +from simnet.utils.enum_ import Region + + +class AuthClient(BaseClient): + """ + The AuthClient class is a client for authentication services. + It is derived from the BaseClient class and provides methods for retrieving + different authentication tokens and keys. + """ + + async def get_stoken_by_login_ticket(self) -> bool: + """ + Retrieves a super ticket (`stoken`) using a login ticket (`login_ticket`) . + + Returns: + bool: `True` if the super ticket successfully retrieved, otherwise `False`. + + Raises: + RuntimeError: This method is only available for the Chinese region. + ValueError: If `login_ticket` is not found in the cookies. + """ + if self.region != Region.CHINESE: + raise RuntimeError("This method is only available for the Chinese region.") + url = AUTH_URL.get_url(Region.CHINESE).join("getMultiTokenByLoginTicket") + login_ticket = self.cookies.get("login_ticket") + if login_ticket is None: + raise ValueError("login_ticket not found in cookies.") + params = { + "login_ticket": login_ticket, + "uid": self.account_id, + "token_types": 3, + } + data = await self.request_lab(url, params=params) + res_data = data["list"] + for i in res_data: + name = i.get("name") + token = i.get("token") + if name and token: + self.cookies[name] = token + stoken = self.cookies.get("stoken") + stuid = self.cookies.get("stuid") + if stoken: + if stuid: + self.cookies["stuid"] = self.account_id + return True + return False + + async def get_cookie_token_by_stoken(self) -> bool: + """ + Retrieves a cookie token (`cookie_token`) using a super ticket (`stoken`). + + Returns: + bool: `True` if the cookie token was successfully retrieved, otherwise `False`. + + Raises: + RuntimeError: This method is only available for the Chinese region. + ValueError: If `stoken` is not found in the cookies. + """ + if self.region != Region.CHINESE: + raise RuntimeError("This method is only available for the Chinese region.") + stoken = self.cookies.get("stoken") + if stoken is None: + raise ValueError("stoken not found in cookies.") + url = GET_COOKIES_TOKEN_BY_STOKEN_URL.get_url(Region.CHINESE) + params = { + "stoken": stoken, + "uid": self.account_id, + } + data = await self.request_lab(url, params=params) + cookie_token = data.get("cookie_token", "") + if cookie_token: + self.cookies["cookie_token"] = cookie_token + self.cookies["account_id"] = self.account_id + return True + return False + + async def get_ltoken_by_stoken(self) -> bool: + """ + Retrieves a login token (`ltoken`) using a super ticket (`stoken`). + + Returns: + bool: `True` if the login token was successfully retrieved, otherwise `False`. + + Raises: + RuntimeError: This method is only available for the Chinese region. + ValueError: If `stoken` is not found in the cookies. + """ + if self.region != Region.CHINESE: + raise RuntimeError("This method is only available for the Chinese region.") + stoken = self.cookies.get("stoken") + if stoken is None: + raise ValueError("stoken not found in cookies.") + url = GET_LTOKEN_BY_STOKEN_URL.get_url(Region.CHINESE) + params = { + "stoken": stoken, + "uid": self.account_id, + } + data = await self.request_lab(url, params=params) + ltoken = data.get("ltoken", "") + if ltoken: + self.cookies["ltoken"] = ltoken + self.cookies["ltuid"] = self.account_id + return True + return False + + async def get_authkey_by_stoken( + self, game_biz: str, region: str, auth_appid: str + ) -> Optional[str]: + """ + Get the auth key (`authkey`) for a game and region using a super ticket (`stoken`). + + Args: + game_biz (str): The name of the game. + region (str): The region in which the game is registered. + auth_appid (str): The type of application for which the authkey is being requested. + For example, to request wish records, use `webview_gacha`. + + Returns: + str or None: The authentication key, or None if not found. + + Raises: + RuntimeError: This method is only available for the Chinese region. + ValueError: If `stoken` is not found in the cookies. + """ + if self.region != Region.CHINESE: + raise RuntimeError("This method is only available for the Chinese region.") + stoken = self.cookies.get("stoken") + if stoken is None: + raise ValueError("stoken not found in cookies.") + url = AUTH_KEY_URL.get_url(self.region) + json = { + "auth_appid": auth_appid, + "game_biz": game_biz, + "game_uid": self.player_id, + "region": region, + } + data = await self.request_lab(url, data=json) + return data.get("authkey") + + async def get_hk4e_token_by_cookie_token( + self, game_biz: str, region: str + ) -> NoReturn: + """ + Get HK4E token (`hk4e_token`) using cookie token (`cookie_token`). + The resulting HK4E token will be automatically saved in self.cookies. + + Args: + game_biz (str): The name of the game. + region (str): The region in which the game is registered. + + Raises: + ValueError: If `cookie_token` is not found in the cookies. + """ + stoken = self.cookies.get("cookie_token") + if stoken is None: + raise ValueError("cookie_token not found in cookies.") + url = HK4E_LOGIN_URL.get_url(self.region) + json = { + "game_biz": game_biz, + "uid": self.player_id, + "region": region, + } + await self.request_lab(url, data=json) diff --git a/simnet/client/routes.py b/simnet/client/routes.py index 68bedd5..7a528fd 100644 --- a/simnet/client/routes.py +++ b/simnet/client/routes.py @@ -156,6 +156,9 @@ class GameRoute(BaseRoute): return self.urls[region][game] +PASSPORT_HOST = "passport-api.mihoyo.com" + + RECORD_URL = InternationalRoute( overseas="https://bbs-api-os.hoyolab.com/game_record", chinese="https://api-takumi-record.mihoyo.com/game_record/app", @@ -171,3 +174,27 @@ GACHA_INFO_URL = GameRoute( hkrpg="https://api-takumi.mihoyo.com/common/gacha_record/api", ), ) + +AUTH_URL = InternationalRoute( + overseas="", + chinese="https://api-takumi.mihoyo.com/auth/api", +) + +GET_COOKIES_TOKEN_BY_STOKEN_URL = InternationalRoute( + overseas="", + chinese=f"https://{PASSPORT_HOST}/account/auth/api/getCookieAccountInfoBySToken", +) + +GET_LTOKEN_BY_STOKEN_URL = InternationalRoute( + overseas="", + chinese=f"https://{PASSPORT_HOST}/account/auth/api/getLTokenBySToken", +) + +AUTH_KEY_URL = InternationalRoute( + overseas="", chinese="https://api-takumi.mihoyo.com/binding/api/genAuthKey" +) + +HK4E_LOGIN_URL = InternationalRoute( + overseas="https://sg-public-api.hoyoverse.com/common/badge/v1/login/account", + chinese="https://api-takumi.mihoyo.com/common/badge/v1/login/account", +) diff --git a/simnet/client/starrail.py b/simnet/client/starrail.py index a6b97a9..cf436e1 100644 --- a/simnet/client/starrail.py +++ b/simnet/client/starrail.py @@ -1,8 +1,9 @@ +from simnet.client.account.auth import AuthClient from simnet.client.chronicle.starrail import StarRailBattleChronicleClient from simnet.client.wish.starrail import WishClient __all__ = ("StarRailClient",) -class StarRailClient(StarRailBattleChronicleClient, WishClient): +class StarRailClient(StarRailBattleChronicleClient, WishClient, AuthClient): """A simple http client for StarRail endpoints.""" diff --git a/simnet/client/starrail.pyi b/simnet/client/starrail.pyi index b14e94d..6ef0557 100644 --- a/simnet/client/starrail.pyi +++ b/simnet/client/starrail.pyi @@ -1,12 +1,12 @@ from typing import Optional +from simnet.client.account.auth import AuthClient from simnet.client.chronicle.starrail import StarRailBattleChronicleClient from simnet.client.wish.starrail import WishClient from simnet.utils.enum_ import Region from simnet.utils.types import CookieTypes, HeaderTypes, TimeoutTypes - -class StarRailClient(StarRailBattleChronicleClient, WishClient): +class StarRailClient(StarRailBattleChronicleClient, WishClient, AuthClient): def __init__( self, cookies: Optional[CookieTypes] = None,