diff --git a/simnet/client/base.py b/simnet/client/base.py index e72f152..1515ee9 100644 --- a/simnet/client/base.py +++ b/simnet/client/base.py @@ -318,11 +318,10 @@ class BaseClient(AsyncContextManager["BaseClient"]): params=params, headers=headers, ) - # if "application/json" in response.headers.get("Content-Type", ""): if not response.is_error: data = response.json() - ret_code = data.get("retcode") - if response.is_error or ret_code != 0: + ret_code = data.get("retcode", 0) + if ret_code != 0: raise_for_ret_code(data) return data["data"] if response.status_code == 404: diff --git a/simnet/client/components/auth.py b/simnet/client/components/auth.py index a31bb7e..efbaadc 100644 --- a/simnet/client/components/auth.py +++ b/simnet/client/components/auth.py @@ -5,10 +5,9 @@ from simnet.client.routes import ( AUTH_URL, AUTH_KEY_URL, HK4E_LOGIN_URL, - GET_COOKIES_TOKEN_BY_STOKEN_URL, - GET_LTOKEN_BY_STOKEN_URL, + PASSPORT_URL, + WEB_ACCOUNT_URL, ) -from simnet.errors import RegionNotSupported from simnet.utils.enum_ import Region __all__ = ("AuthClient",) @@ -37,12 +36,9 @@ class AuthClient(BaseClient): Optional[str]: The retrieved super ticket (`stoken`). Raises: - RegionNotSupported: This method is only available for the Chinese region. ValueError: If the `login_ticket` argument is `None`, or if the `account_id` argument is `None`. """ - if self.region != Region.CHINESE: - raise RegionNotSupported("This method is only available for the Chinese region.") - url = AUTH_URL.get_url(Region.CHINESE) / "getMultiTokenByLoginTicket" + url = AUTH_URL.get_url(self.region) / "getMultiTokenByLoginTicket" login_ticket = login_ticket or self.cookies.get("login_ticket") account_id = account_id or self.account_id if login_ticket is None: @@ -64,9 +60,41 @@ class AuthClient(BaseClient): stoken = self.cookies.get("stoken") stuid = self.cookies.get("stuid") if stoken and stuid: - self.cookies["stuid"] = self.account_id + self.cookies["stuid"] = str(self.account_id) return stoken + async def get_cookie_token_by_login_ticket(self, login_ticket: Optional[str] = None) -> Optional[str]: + """ + Retrieves a cookie token (`cookie_token`) using a login ticket (`login_ticket`). + + Args: + login_ticket (Optional[str]): The login ticket to use to retrieve the cookie token. If not provided, the + `login_ticket` cookie value will be used. + + Returns: + Optional[str]: The retrieved cookie token (`cookie_token`). + + Raises: + ValueError: If the `login_ticket` argument is `None`. + """ + url = WEB_ACCOUNT_URL.get_url(self.region) / "cookie_accountinfo_by_loginticket" + login_ticket = login_ticket or self.cookies.get("login_ticket") + if login_ticket is None: + raise ValueError("The 'login_ticket' argument cannot be None.") + params = {"login_ticket": login_ticket} + data = await self.request_lab(url, params=params) + cookie_info = data.get("cookie_info") + if not cookie_info: + raise ValueError("The 'login_ticket' is expired.") + account_id = cookie_info.get("account_id") + cookie_token = cookie_info.get("cookie_token") + if account_id: + self.account_id = account_id + self.cookies["account_id"] = str(account_id) + if cookie_token: + self.cookies["cookie_token"] = cookie_token + return cookie_token + async def get_cookie_token_by_stoken( self, stoken: Optional[str] = None, account_id: Optional[int] = None ) -> Optional[str]: @@ -83,27 +111,25 @@ class AuthClient(BaseClient): Optional[str]: The retrieved cookie token (`cookie_token`). Raises: - RegionNotSupported: This method is only available for the Chinese region. ValueError: If the `login_ticket` argument is `None`, or if the `account_id` argument is `None`. """ - if self.region != Region.CHINESE: - raise RegionNotSupported("This method is only available for the Chinese region.") stoken = stoken or self.cookies.get("stoken") account_id = account_id or self.account_id if stoken is None: raise ValueError("The 'stoken' argument cannot be None.") if account_id is None: raise ValueError("The 'account_id' argument cannot be None.") - url = GET_COOKIES_TOKEN_BY_STOKEN_URL.get_url(Region.CHINESE) + url = PASSPORT_URL.get_url(self.region) / "getCookieAccountInfoBySToken" + method = "GET" if self.region == Region.CHINESE else "POST" params = { "stoken": stoken, "uid": account_id, } - data = await self.request_lab(url, params=params) + data = await self.request_lab(url, method=method, params=params) cookie_token = data.get("cookie_token") if cookie_token: self.cookies["cookie_token"] = cookie_token - self.cookies["account_id"] = self.account_id + self.cookies["account_id"] = str(self.account_id) return cookie_token async def get_ltoken_by_stoken( @@ -122,27 +148,25 @@ class AuthClient(BaseClient): Optional[str]: The retrieved cookie token (`cookie_token`). Raises: - RegionNotSupported: This method is only available for the Chinese region. ValueError: If the `login_ticket` argument is `None`, or if the `account_id` argument is `None`. """ - if self.region != Region.CHINESE: - raise RegionNotSupported("This method is only available for the Chinese region.") stoken = stoken or self.cookies.get("stoken") account_id = account_id or self.account_id if stoken is None: raise ValueError("The 'stoken' argument cannot be None.") if account_id is None: raise ValueError("The 'account_id' argument cannot be None.") - url = GET_LTOKEN_BY_STOKEN_URL.get_url(Region.CHINESE) + url = PASSPORT_URL.get_url(self.region) / "getLTokenBySToken" + method = "GET" if self.region == Region.CHINESE else "POST" params = { "stoken": stoken, "uid": account_id, } - data = await self.request_lab(url, params=params) + data = await self.request_lab(url, method=method, params=params) ltoken = data.get("ltoken", "") if ltoken: self.cookies["ltoken"] = ltoken - self.cookies["ltuid"] = self.account_id + self.cookies["ltuid"] = str(self.account_id) return ltoken async def get_authkey_by_stoken(self, game_biz: str, region: str, auth_appid: str) -> Optional[str]: @@ -159,11 +183,8 @@ class AuthClient(BaseClient): Optional[str]: The authentication key, or None if not found. Raises: - RegionNotSupported: This method is only available for the Chinese region. ValueError: If `stoken` is not found in the cookies or `player_id` not found. """ - if self.region != Region.CHINESE: - raise RegionNotSupported("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.") diff --git a/simnet/client/cookies.py b/simnet/client/cookies.py index cccf3fc..b4dffef 100644 --- a/simnet/client/cookies.py +++ b/simnet/client/cookies.py @@ -12,9 +12,11 @@ __all__ = ("Cookies",) class Cookies(_Cookies): """A wrapper around `httpx.Cookies` that provides additional functionality.""" + jar: CookieJar + def __init__(self, cookies: Optional[CookieTypes] = None): # skipcq: PYL-W0231 + self.jar = CookieJar() if cookies is None or isinstance(cookies, dict): - self.jar = CookieJar() if isinstance(cookies, dict): for key, value in cookies.items(): if isinstance(value, str): @@ -22,20 +24,17 @@ class Cookies(_Cookies): else: self.set(key, str(value)) elif isinstance(cookies, list): - self.jar = CookieJar() for key, value in cookies: self.set(key, value) elif isinstance(cookies, Cookies): - self.jar = CookieJar() for cookie in cookies.jar: self.jar.set_cookie(cookie) elif isinstance(cookies, str): - self.jar = CookieJar() cookie = SimpleCookie(cookies) for key, value in cookie.items(): self.set(key, value.value) else: - self.jar = cookies + self.jar = cookies # type: ignore COOKIE_USER_ID_NAMES = ("ltuid", "account_id", "stuid", "ltuid_v2", "account_id_v2") @@ -54,3 +53,29 @@ class Cookies(_Cookies): if value is not None: return int(value) return None + + def get( + self, + name: str, + default: Optional[str] = None, + domain: Optional[str] = None, + path: Optional[str] = None, + ) -> Optional[str]: + """ + Get a cookie by name. May optionally include domain and path + in order to specify exactly which cookie to retrieve. + """ + value = None + for cookie in self.jar: + if ( + cookie.name == name + and domain is None + or cookie.domain == domain + and path is None + or cookie.path == path + and cookie.value + ): + value = cookie.value + if value is None: + return default + return value diff --git a/simnet/client/routes.py b/simnet/client/routes.py index 39c17dc..4701b13 100644 --- a/simnet/client/routes.py +++ b/simnet/client/routes.py @@ -17,8 +17,8 @@ __all__ = ( "RECORD_URL", "GACHA_INFO_URL", "AUTH_URL", - "GET_COOKIES_TOKEN_BY_STOKEN_URL", - "GET_LTOKEN_BY_STOKEN_URL", + "PASSPORT_URL", + "WEB_ACCOUNT_URL", "AUTH_KEY_URL", "HK4E_LOGIN_URL", "REWARD_URL", @@ -188,8 +188,6 @@ 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", @@ -207,18 +205,16 @@ GACHA_INFO_URL = GameRoute( ) AUTH_URL = InternationalRoute( - overseas="", + overseas="https://api-os-takumi.mihoyo.com/auth/api", 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", +PASSPORT_URL = InternationalRoute( + overseas="https://api-account-os.hoyoverse.com/account/auth/api/", + chinese="https://passport-api.mihoyo.com/account/auth/api/", ) - -GET_LTOKEN_BY_STOKEN_URL = InternationalRoute( - overseas="", - chinese=f"https://{PASSPORT_HOST}/account/auth/api/getLTokenBySToken", +WEB_ACCOUNT_URL = InternationalRoute( + overseas="https://webapi-os.account.hoyoverse.com/Api/", + chinese="https://webapi.account.mihoyo.com/Api/", ) AUTH_KEY_URL = InternationalRoute(overseas="", chinese="https://api-takumi.mihoyo.com/binding/api/genAuthKey")