Module genshin.client.components.auth.subclients.app
App sub client for AuthClient.
Covers HoYoLAB and Miyoushe app auth endpoints.
Classes
class AppAuthClient (cookies: ForwardRef('http.cookies.BaseCookie[Any]') | Mapping[Any, Any] | str | Sequence[ForwardRef('http.cookies.BaseCookie[Any]') | Mapping[Any, Any] | str] | None = None,
*,
authkey: str | None = None,
lang: Literal['zh-cn', 'zh-tw', 'de-de', 'en-us', 'es-es', 'fr-fr', 'id-id', 'it-it', 'ja-jp', 'ko-kr', 'pt-pt', 'ru-ru', 'th-th', 'vi-vn', 'tr-tr'] = 'en-us',
region: Region = Region.OVERSEAS,
proxy: str | None = None,
game: Game | None = None,
uid: int | None = None,
hoyolab_id: int | None = None,
device_id: str | None = None,
device_fp: str | None = None,
headers: Mapping[str, str] | Mapping[multidict._multidict.istr, str] | multidict._multidict.CIMultiDict | multidict._multidict.CIMultiDictProxy | Iterable[Tuple[str | multidict._multidict.istr, str]] | None = None,
cache: BaseCache | None = None,
debug: bool = False,
on_cookie_update: Callable[[Mapping[str, str]], Awaitable[None] | None] | None = None)-
Expand source code
class AppAuthClient(base.BaseClient): """App sub client for AuthClient.""" @staticmethod def generate_app_device_id() -> str: """Generate a random device ID for app login.""" return "".join(random.choices(string.ascii_lowercase + string.digits, k=16)) @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTResult, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTv4Result, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: ActionTicket, ) -> AppLoginResult: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: None = ..., ) -> typing.Union[AppLoginResult, SessionMMT, SessionMMTv4, ActionTicket]: ... async def _app_login( self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = None, device_model: typing.Optional[str] = None, encrypted: bool = False, mmt_result: typing.Optional[AppGeetestResult] = None, ticket: typing.Optional[ActionTicket] = None, ) -> typing.Union[AppLoginResult, AppGeetestSession, ActionTicket]: """Login with a password using HoYoLab app endpoint. Returns: - AppLoginResult if login is successful. - AppGeetestSession if captcha is triggered. - ActionTicket if email verification is required. """ headers = { **auth_utility.APP_LOGIN_HEADERS, # Passing "x-rpc-device_id" header will trigger email verification # (unless the device_id is already verified). # For some reason, without this header, email verification is not triggered. # # 2025/07/18: Hoyo found this issue and fixed it, we now have to provide this header. # This value needs to be consistent across all requests, else it will trigger email # verification repeatedly. "x-rpc-device_id": device_id, } if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() if ticket: headers["x-rpc-verify"] = ticket.to_rpc_verify_header() if device_name: headers["x-rpc-device_name"] = device_name if device_model: headers["x-rpc-device_model"] = device_model payload = { "account": account if encrypted else auth_utility.encrypt_credentials(account, 1), "password": password if encrypted else auth_utility.encrypt_credentials(password, 1), } headers["ds"] = ds_utility.generate_app_login_ds(payload) resp = await self.cookie_manager._raw_request( "POST", routes.APP_LOGIN_URL.get_url(), json=payload, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) if isinstance(aigis["data"], str): aigis["data"] = json.loads(aigis["data"]) if aigis["data"].get("use_v4"): return SessionMMTv4( **aigis["data"], session_id=aigis["session_id"], ) return SessionMMT(**aigis) if resp.data["retcode"] == -3239: # Email verification required action_ticket = json.loads(resp.headers["x-rpc-verify"]) return ActionTicket(**action_ticket) if not resp.data["data"]: errors.raise_for_retcode(resp.data) cookies = { "stoken": resp.data["data"]["token"]["token"], "ltuid_v2": resp.data["data"]["user_info"]["aid"], "ltmid_v2": resp.data["data"]["user_info"]["mid"], "account_id_v2": resp.data["data"]["user_info"]["aid"], "account_mid_v2": resp.data["data"]["user_info"]["mid"], } self.set_cookies(cookies) return AppLoginResult(**cookies) async def _send_verification_email( self, ticket: ActionTicket, *, mmt_result: typing.Optional[SessionMMTResult] = None, ) -> typing.Union[None, SessionMMT]: """Send verification email. Returns None if success, SessionMMT data if geetest triggered. """ headers = {**auth_utility.EMAIL_SEND_HEADERS} if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() resp = await self.cookie_manager._raw_request( "POST", routes.SEND_VERIFICATION_CODE_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, }, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) return SessionMMT(**aigis) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _verify_email(self, code: str, ticket: ActionTicket) -> None: """Verify email.""" resp = await self.cookie_manager._raw_request( "POST", routes.VERIFY_EMAIL_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, "email_captcha": code, "verify_method": 2, }, headers=auth_utility.EMAIL_VERIFY_HEADERS, ) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _create_qrcode(self) -> QRCodeCreationResult: """Create a QR code for login.""" resp = await self.cookie_manager._raw_request( "POST", routes.CREATE_QRCODE_URL.get_url(), headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeCreationResult( ticket=resp.data["data"]["ticket"], url=resp.data["data"]["url"], ) async def _check_qrcode(self, ticket: str) -> tuple[QRCodeStatus, SimpleCookie]: """Check the status of a QR code login.""" payload = {"ticket": ticket} resp = await self.cookie_manager._raw_request( "POST", routes.CHECK_QRCODE_URL.get_url(), json=payload, headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeStatus(resp.data["data"]["status"]), resp.cookiesApp sub client for AuthClient.
Ancestors
- BaseClient
- abc.ABC
Subclasses
Class variables
var logger : logging.Logger
Static methods
def generate_app_device_id() ‑> str-
Expand source code
@staticmethod def generate_app_device_id() -> str: """Generate a random device ID for app login.""" return "".join(random.choices(string.ascii_lowercase + string.digits, k=16))Generate a random device ID for app login.
Instance variables
var authkeys : dict[Game, str]-
Expand source code
class AppAuthClient(base.BaseClient): """App sub client for AuthClient.""" @staticmethod def generate_app_device_id() -> str: """Generate a random device ID for app login.""" return "".join(random.choices(string.ascii_lowercase + string.digits, k=16)) @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTResult, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTv4Result, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: ActionTicket, ) -> AppLoginResult: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: None = ..., ) -> typing.Union[AppLoginResult, SessionMMT, SessionMMTv4, ActionTicket]: ... async def _app_login( self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = None, device_model: typing.Optional[str] = None, encrypted: bool = False, mmt_result: typing.Optional[AppGeetestResult] = None, ticket: typing.Optional[ActionTicket] = None, ) -> typing.Union[AppLoginResult, AppGeetestSession, ActionTicket]: """Login with a password using HoYoLab app endpoint. Returns: - AppLoginResult if login is successful. - AppGeetestSession if captcha is triggered. - ActionTicket if email verification is required. """ headers = { **auth_utility.APP_LOGIN_HEADERS, # Passing "x-rpc-device_id" header will trigger email verification # (unless the device_id is already verified). # For some reason, without this header, email verification is not triggered. # # 2025/07/18: Hoyo found this issue and fixed it, we now have to provide this header. # This value needs to be consistent across all requests, else it will trigger email # verification repeatedly. "x-rpc-device_id": device_id, } if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() if ticket: headers["x-rpc-verify"] = ticket.to_rpc_verify_header() if device_name: headers["x-rpc-device_name"] = device_name if device_model: headers["x-rpc-device_model"] = device_model payload = { "account": account if encrypted else auth_utility.encrypt_credentials(account, 1), "password": password if encrypted else auth_utility.encrypt_credentials(password, 1), } headers["ds"] = ds_utility.generate_app_login_ds(payload) resp = await self.cookie_manager._raw_request( "POST", routes.APP_LOGIN_URL.get_url(), json=payload, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) if isinstance(aigis["data"], str): aigis["data"] = json.loads(aigis["data"]) if aigis["data"].get("use_v4"): return SessionMMTv4( **aigis["data"], session_id=aigis["session_id"], ) return SessionMMT(**aigis) if resp.data["retcode"] == -3239: # Email verification required action_ticket = json.loads(resp.headers["x-rpc-verify"]) return ActionTicket(**action_ticket) if not resp.data["data"]: errors.raise_for_retcode(resp.data) cookies = { "stoken": resp.data["data"]["token"]["token"], "ltuid_v2": resp.data["data"]["user_info"]["aid"], "ltmid_v2": resp.data["data"]["user_info"]["mid"], "account_id_v2": resp.data["data"]["user_info"]["aid"], "account_mid_v2": resp.data["data"]["user_info"]["mid"], } self.set_cookies(cookies) return AppLoginResult(**cookies) async def _send_verification_email( self, ticket: ActionTicket, *, mmt_result: typing.Optional[SessionMMTResult] = None, ) -> typing.Union[None, SessionMMT]: """Send verification email. Returns None if success, SessionMMT data if geetest triggered. """ headers = {**auth_utility.EMAIL_SEND_HEADERS} if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() resp = await self.cookie_manager._raw_request( "POST", routes.SEND_VERIFICATION_CODE_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, }, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) return SessionMMT(**aigis) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _verify_email(self, code: str, ticket: ActionTicket) -> None: """Verify email.""" resp = await self.cookie_manager._raw_request( "POST", routes.VERIFY_EMAIL_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, "email_captcha": code, "verify_method": 2, }, headers=auth_utility.EMAIL_VERIFY_HEADERS, ) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _create_qrcode(self) -> QRCodeCreationResult: """Create a QR code for login.""" resp = await self.cookie_manager._raw_request( "POST", routes.CREATE_QRCODE_URL.get_url(), headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeCreationResult( ticket=resp.data["data"]["ticket"], url=resp.data["data"]["url"], ) async def _check_qrcode(self, ticket: str) -> tuple[QRCodeStatus, SimpleCookie]: """Check the status of a QR code login.""" payload = {"ticket": ticket} resp = await self.cookie_manager._raw_request( "POST", routes.CHECK_QRCODE_URL.get_url(), json=payload, headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeStatus(resp.data["data"]["status"]), resp.cookies var cache : BaseCache-
Expand source code
class AppAuthClient(base.BaseClient): """App sub client for AuthClient.""" @staticmethod def generate_app_device_id() -> str: """Generate a random device ID for app login.""" return "".join(random.choices(string.ascii_lowercase + string.digits, k=16)) @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTResult, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTv4Result, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: ActionTicket, ) -> AppLoginResult: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: None = ..., ) -> typing.Union[AppLoginResult, SessionMMT, SessionMMTv4, ActionTicket]: ... async def _app_login( self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = None, device_model: typing.Optional[str] = None, encrypted: bool = False, mmt_result: typing.Optional[AppGeetestResult] = None, ticket: typing.Optional[ActionTicket] = None, ) -> typing.Union[AppLoginResult, AppGeetestSession, ActionTicket]: """Login with a password using HoYoLab app endpoint. Returns: - AppLoginResult if login is successful. - AppGeetestSession if captcha is triggered. - ActionTicket if email verification is required. """ headers = { **auth_utility.APP_LOGIN_HEADERS, # Passing "x-rpc-device_id" header will trigger email verification # (unless the device_id is already verified). # For some reason, without this header, email verification is not triggered. # # 2025/07/18: Hoyo found this issue and fixed it, we now have to provide this header. # This value needs to be consistent across all requests, else it will trigger email # verification repeatedly. "x-rpc-device_id": device_id, } if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() if ticket: headers["x-rpc-verify"] = ticket.to_rpc_verify_header() if device_name: headers["x-rpc-device_name"] = device_name if device_model: headers["x-rpc-device_model"] = device_model payload = { "account": account if encrypted else auth_utility.encrypt_credentials(account, 1), "password": password if encrypted else auth_utility.encrypt_credentials(password, 1), } headers["ds"] = ds_utility.generate_app_login_ds(payload) resp = await self.cookie_manager._raw_request( "POST", routes.APP_LOGIN_URL.get_url(), json=payload, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) if isinstance(aigis["data"], str): aigis["data"] = json.loads(aigis["data"]) if aigis["data"].get("use_v4"): return SessionMMTv4( **aigis["data"], session_id=aigis["session_id"], ) return SessionMMT(**aigis) if resp.data["retcode"] == -3239: # Email verification required action_ticket = json.loads(resp.headers["x-rpc-verify"]) return ActionTicket(**action_ticket) if not resp.data["data"]: errors.raise_for_retcode(resp.data) cookies = { "stoken": resp.data["data"]["token"]["token"], "ltuid_v2": resp.data["data"]["user_info"]["aid"], "ltmid_v2": resp.data["data"]["user_info"]["mid"], "account_id_v2": resp.data["data"]["user_info"]["aid"], "account_mid_v2": resp.data["data"]["user_info"]["mid"], } self.set_cookies(cookies) return AppLoginResult(**cookies) async def _send_verification_email( self, ticket: ActionTicket, *, mmt_result: typing.Optional[SessionMMTResult] = None, ) -> typing.Union[None, SessionMMT]: """Send verification email. Returns None if success, SessionMMT data if geetest triggered. """ headers = {**auth_utility.EMAIL_SEND_HEADERS} if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() resp = await self.cookie_manager._raw_request( "POST", routes.SEND_VERIFICATION_CODE_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, }, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) return SessionMMT(**aigis) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _verify_email(self, code: str, ticket: ActionTicket) -> None: """Verify email.""" resp = await self.cookie_manager._raw_request( "POST", routes.VERIFY_EMAIL_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, "email_captcha": code, "verify_method": 2, }, headers=auth_utility.EMAIL_VERIFY_HEADERS, ) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _create_qrcode(self) -> QRCodeCreationResult: """Create a QR code for login.""" resp = await self.cookie_manager._raw_request( "POST", routes.CREATE_QRCODE_URL.get_url(), headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeCreationResult( ticket=resp.data["data"]["ticket"], url=resp.data["data"]["url"], ) async def _check_qrcode(self, ticket: str) -> tuple[QRCodeStatus, SimpleCookie]: """Check the status of a QR code login.""" payload = {"ticket": ticket} resp = await self.cookie_manager._raw_request( "POST", routes.CHECK_QRCODE_URL.get_url(), json=payload, headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeStatus(resp.data["data"]["status"]), resp.cookies -
Expand source code
class AppAuthClient(base.BaseClient): """App sub client for AuthClient.""" @staticmethod def generate_app_device_id() -> str: """Generate a random device ID for app login.""" return "".join(random.choices(string.ascii_lowercase + string.digits, k=16)) @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTResult, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTv4Result, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: ActionTicket, ) -> AppLoginResult: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: None = ..., ) -> typing.Union[AppLoginResult, SessionMMT, SessionMMTv4, ActionTicket]: ... async def _app_login( self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = None, device_model: typing.Optional[str] = None, encrypted: bool = False, mmt_result: typing.Optional[AppGeetestResult] = None, ticket: typing.Optional[ActionTicket] = None, ) -> typing.Union[AppLoginResult, AppGeetestSession, ActionTicket]: """Login with a password using HoYoLab app endpoint. Returns: - AppLoginResult if login is successful. - AppGeetestSession if captcha is triggered. - ActionTicket if email verification is required. """ headers = { **auth_utility.APP_LOGIN_HEADERS, # Passing "x-rpc-device_id" header will trigger email verification # (unless the device_id is already verified). # For some reason, without this header, email verification is not triggered. # # 2025/07/18: Hoyo found this issue and fixed it, we now have to provide this header. # This value needs to be consistent across all requests, else it will trigger email # verification repeatedly. "x-rpc-device_id": device_id, } if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() if ticket: headers["x-rpc-verify"] = ticket.to_rpc_verify_header() if device_name: headers["x-rpc-device_name"] = device_name if device_model: headers["x-rpc-device_model"] = device_model payload = { "account": account if encrypted else auth_utility.encrypt_credentials(account, 1), "password": password if encrypted else auth_utility.encrypt_credentials(password, 1), } headers["ds"] = ds_utility.generate_app_login_ds(payload) resp = await self.cookie_manager._raw_request( "POST", routes.APP_LOGIN_URL.get_url(), json=payload, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) if isinstance(aigis["data"], str): aigis["data"] = json.loads(aigis["data"]) if aigis["data"].get("use_v4"): return SessionMMTv4( **aigis["data"], session_id=aigis["session_id"], ) return SessionMMT(**aigis) if resp.data["retcode"] == -3239: # Email verification required action_ticket = json.loads(resp.headers["x-rpc-verify"]) return ActionTicket(**action_ticket) if not resp.data["data"]: errors.raise_for_retcode(resp.data) cookies = { "stoken": resp.data["data"]["token"]["token"], "ltuid_v2": resp.data["data"]["user_info"]["aid"], "ltmid_v2": resp.data["data"]["user_info"]["mid"], "account_id_v2": resp.data["data"]["user_info"]["aid"], "account_mid_v2": resp.data["data"]["user_info"]["mid"], } self.set_cookies(cookies) return AppLoginResult(**cookies) async def _send_verification_email( self, ticket: ActionTicket, *, mmt_result: typing.Optional[SessionMMTResult] = None, ) -> typing.Union[None, SessionMMT]: """Send verification email. Returns None if success, SessionMMT data if geetest triggered. """ headers = {**auth_utility.EMAIL_SEND_HEADERS} if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() resp = await self.cookie_manager._raw_request( "POST", routes.SEND_VERIFICATION_CODE_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, }, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) return SessionMMT(**aigis) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _verify_email(self, code: str, ticket: ActionTicket) -> None: """Verify email.""" resp = await self.cookie_manager._raw_request( "POST", routes.VERIFY_EMAIL_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, "email_captcha": code, "verify_method": 2, }, headers=auth_utility.EMAIL_VERIFY_HEADERS, ) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _create_qrcode(self) -> QRCodeCreationResult: """Create a QR code for login.""" resp = await self.cookie_manager._raw_request( "POST", routes.CREATE_QRCODE_URL.get_url(), headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeCreationResult( ticket=resp.data["data"]["ticket"], url=resp.data["data"]["url"], ) async def _check_qrcode(self, ticket: str) -> tuple[QRCodeStatus, SimpleCookie]: """Check the status of a QR code login.""" payload = {"ticket": ticket} resp = await self.cookie_manager._raw_request( "POST", routes.CHECK_QRCODE_URL.get_url(), json=payload, headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeStatus(resp.data["data"]["status"]), resp.cookies var custom_headers : multidict._multidict.CIMultiDict[str]-
Expand source code
class AppAuthClient(base.BaseClient): """App sub client for AuthClient.""" @staticmethod def generate_app_device_id() -> str: """Generate a random device ID for app login.""" return "".join(random.choices(string.ascii_lowercase + string.digits, k=16)) @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTResult, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTv4Result, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: ActionTicket, ) -> AppLoginResult: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: None = ..., ) -> typing.Union[AppLoginResult, SessionMMT, SessionMMTv4, ActionTicket]: ... async def _app_login( self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = None, device_model: typing.Optional[str] = None, encrypted: bool = False, mmt_result: typing.Optional[AppGeetestResult] = None, ticket: typing.Optional[ActionTicket] = None, ) -> typing.Union[AppLoginResult, AppGeetestSession, ActionTicket]: """Login with a password using HoYoLab app endpoint. Returns: - AppLoginResult if login is successful. - AppGeetestSession if captcha is triggered. - ActionTicket if email verification is required. """ headers = { **auth_utility.APP_LOGIN_HEADERS, # Passing "x-rpc-device_id" header will trigger email verification # (unless the device_id is already verified). # For some reason, without this header, email verification is not triggered. # # 2025/07/18: Hoyo found this issue and fixed it, we now have to provide this header. # This value needs to be consistent across all requests, else it will trigger email # verification repeatedly. "x-rpc-device_id": device_id, } if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() if ticket: headers["x-rpc-verify"] = ticket.to_rpc_verify_header() if device_name: headers["x-rpc-device_name"] = device_name if device_model: headers["x-rpc-device_model"] = device_model payload = { "account": account if encrypted else auth_utility.encrypt_credentials(account, 1), "password": password if encrypted else auth_utility.encrypt_credentials(password, 1), } headers["ds"] = ds_utility.generate_app_login_ds(payload) resp = await self.cookie_manager._raw_request( "POST", routes.APP_LOGIN_URL.get_url(), json=payload, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) if isinstance(aigis["data"], str): aigis["data"] = json.loads(aigis["data"]) if aigis["data"].get("use_v4"): return SessionMMTv4( **aigis["data"], session_id=aigis["session_id"], ) return SessionMMT(**aigis) if resp.data["retcode"] == -3239: # Email verification required action_ticket = json.loads(resp.headers["x-rpc-verify"]) return ActionTicket(**action_ticket) if not resp.data["data"]: errors.raise_for_retcode(resp.data) cookies = { "stoken": resp.data["data"]["token"]["token"], "ltuid_v2": resp.data["data"]["user_info"]["aid"], "ltmid_v2": resp.data["data"]["user_info"]["mid"], "account_id_v2": resp.data["data"]["user_info"]["aid"], "account_mid_v2": resp.data["data"]["user_info"]["mid"], } self.set_cookies(cookies) return AppLoginResult(**cookies) async def _send_verification_email( self, ticket: ActionTicket, *, mmt_result: typing.Optional[SessionMMTResult] = None, ) -> typing.Union[None, SessionMMT]: """Send verification email. Returns None if success, SessionMMT data if geetest triggered. """ headers = {**auth_utility.EMAIL_SEND_HEADERS} if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() resp = await self.cookie_manager._raw_request( "POST", routes.SEND_VERIFICATION_CODE_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, }, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) return SessionMMT(**aigis) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _verify_email(self, code: str, ticket: ActionTicket) -> None: """Verify email.""" resp = await self.cookie_manager._raw_request( "POST", routes.VERIFY_EMAIL_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, "email_captcha": code, "verify_method": 2, }, headers=auth_utility.EMAIL_VERIFY_HEADERS, ) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _create_qrcode(self) -> QRCodeCreationResult: """Create a QR code for login.""" resp = await self.cookie_manager._raw_request( "POST", routes.CREATE_QRCODE_URL.get_url(), headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeCreationResult( ticket=resp.data["data"]["ticket"], url=resp.data["data"]["url"], ) async def _check_qrcode(self, ticket: str) -> tuple[QRCodeStatus, SimpleCookie]: """Check the status of a QR code login.""" payload = {"ticket": ticket} resp = await self.cookie_manager._raw_request( "POST", routes.CHECK_QRCODE_URL.get_url(), json=payload, headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeStatus(resp.data["data"]["status"]), resp.cookies var uids : dict[Game, int]-
Expand source code
class AppAuthClient(base.BaseClient): """App sub client for AuthClient.""" @staticmethod def generate_app_device_id() -> str: """Generate a random device ID for app login.""" return "".join(random.choices(string.ascii_lowercase + string.digits, k=16)) @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTResult, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: SessionMMTv4Result, ticket: None = ..., ) -> typing.Union[AppLoginResult, ActionTicket]: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: ActionTicket, ) -> AppLoginResult: ... @typing.overload async def _app_login( # noqa: D102 missing docstring in overload? self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = ..., device_model: typing.Optional[str] = ..., encrypted: bool = ..., mmt_result: None = ..., ticket: None = ..., ) -> typing.Union[AppLoginResult, SessionMMT, SessionMMTv4, ActionTicket]: ... async def _app_login( self, account: str, password: str, *, device_id: str, device_name: typing.Optional[str] = None, device_model: typing.Optional[str] = None, encrypted: bool = False, mmt_result: typing.Optional[AppGeetestResult] = None, ticket: typing.Optional[ActionTicket] = None, ) -> typing.Union[AppLoginResult, AppGeetestSession, ActionTicket]: """Login with a password using HoYoLab app endpoint. Returns: - AppLoginResult if login is successful. - AppGeetestSession if captcha is triggered. - ActionTicket if email verification is required. """ headers = { **auth_utility.APP_LOGIN_HEADERS, # Passing "x-rpc-device_id" header will trigger email verification # (unless the device_id is already verified). # For some reason, without this header, email verification is not triggered. # # 2025/07/18: Hoyo found this issue and fixed it, we now have to provide this header. # This value needs to be consistent across all requests, else it will trigger email # verification repeatedly. "x-rpc-device_id": device_id, } if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() if ticket: headers["x-rpc-verify"] = ticket.to_rpc_verify_header() if device_name: headers["x-rpc-device_name"] = device_name if device_model: headers["x-rpc-device_model"] = device_model payload = { "account": account if encrypted else auth_utility.encrypt_credentials(account, 1), "password": password if encrypted else auth_utility.encrypt_credentials(password, 1), } headers["ds"] = ds_utility.generate_app_login_ds(payload) resp = await self.cookie_manager._raw_request( "POST", routes.APP_LOGIN_URL.get_url(), json=payload, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) if isinstance(aigis["data"], str): aigis["data"] = json.loads(aigis["data"]) if aigis["data"].get("use_v4"): return SessionMMTv4( **aigis["data"], session_id=aigis["session_id"], ) return SessionMMT(**aigis) if resp.data["retcode"] == -3239: # Email verification required action_ticket = json.loads(resp.headers["x-rpc-verify"]) return ActionTicket(**action_ticket) if not resp.data["data"]: errors.raise_for_retcode(resp.data) cookies = { "stoken": resp.data["data"]["token"]["token"], "ltuid_v2": resp.data["data"]["user_info"]["aid"], "ltmid_v2": resp.data["data"]["user_info"]["mid"], "account_id_v2": resp.data["data"]["user_info"]["aid"], "account_mid_v2": resp.data["data"]["user_info"]["mid"], } self.set_cookies(cookies) return AppLoginResult(**cookies) async def _send_verification_email( self, ticket: ActionTicket, *, mmt_result: typing.Optional[SessionMMTResult] = None, ) -> typing.Union[None, SessionMMT]: """Send verification email. Returns None if success, SessionMMT data if geetest triggered. """ headers = {**auth_utility.EMAIL_SEND_HEADERS} if mmt_result: headers["x-rpc-aigis"] = mmt_result.to_aigis_header() resp = await self.cookie_manager._raw_request( "POST", routes.SEND_VERIFICATION_CODE_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, }, headers=headers, ) if resp.data["retcode"] == -3101: # Captcha triggered aigis = json.loads(resp.headers["x-rpc-aigis"]) return SessionMMT(**aigis) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _verify_email(self, code: str, ticket: ActionTicket) -> None: """Verify email.""" resp = await self.cookie_manager._raw_request( "POST", routes.VERIFY_EMAIL_URL.get_url(), json={ "action_type": "verify_for_component", "action_ticket": ticket.verify_str.ticket, "email_captcha": code, "verify_method": 2, }, headers=auth_utility.EMAIL_VERIFY_HEADERS, ) if resp.data["retcode"] != 0: errors.raise_for_retcode(resp.data) return None async def _create_qrcode(self) -> QRCodeCreationResult: """Create a QR code for login.""" resp = await self.cookie_manager._raw_request( "POST", routes.CREATE_QRCODE_URL.get_url(), headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeCreationResult( ticket=resp.data["data"]["ticket"], url=resp.data["data"]["url"], ) async def _check_qrcode(self, ticket: str) -> tuple[QRCodeStatus, SimpleCookie]: """Check the status of a QR code login.""" payload = {"ticket": ticket} resp = await self.cookie_manager._raw_request( "POST", routes.CHECK_QRCODE_URL.get_url(), json=payload, headers=auth_utility.QRCODE_HEADERS, ) if not resp.data["data"]: errors.raise_for_retcode(resp.data) return QRCodeStatus(resp.data["data"]["status"]), resp.cookies
Inherited members