Skip to content

Commit

Permalink
Fix qrcode login
Browse files Browse the repository at this point in the history
  • Loading branch information
seriaati committed Sep 4, 2024
1 parent af17e53 commit e4ca614
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 88 deletions.
29 changes: 6 additions & 23 deletions genshin/client/components/auth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from genshin.client import routes
from genshin.client.components import base
from genshin.client.manager import managers
from genshin.client.manager.cookie import fetch_cookie_token_with_game_token, fetch_stoken_with_game_token
from genshin.models.auth.cookie import (
AppLoginResult,
CNWebLoginResult,
Expand Down Expand Up @@ -247,35 +246,19 @@ async def login_with_qrcode(self) -> QRLoginResult:

scanned = False
while True:
check_result = await self._check_qrcode(
creation_result.app_id, creation_result.device_id, creation_result.ticket
)
if check_result.status == QRCodeStatus.SCANNED and not scanned:
status, cookies = await self._check_qrcode(creation_result.ticket)
if status is QRCodeStatus.SCANNED and not scanned:
LOGGER_.info("QR code scanned")
scanned = True
elif check_result.status == QRCodeStatus.CONFIRMED:
elif status is QRCodeStatus.CONFIRMED:
LOGGER_.info("QR code login confirmed")
break

await asyncio.sleep(2)

raw_data = check_result.payload.raw
assert raw_data is not None
await asyncio.sleep(1)

cookie_token = await fetch_cookie_token_with_game_token(
game_token=raw_data.game_token, account_id=raw_data.account_id
)
stoken = await fetch_stoken_with_game_token(game_token=raw_data.game_token, account_id=int(raw_data.account_id))

cookies = {
"stoken_v2": stoken.token,
"ltuid": stoken.aid,
"account_id": stoken.aid,
"ltmid": stoken.mid,
"cookie_token": cookie_token,
}
self.set_cookies(cookies)
return QRLoginResult(**cookies)
dict_cookies = {key: morsel.value for key, morsel in cookies.items()}
return QRLoginResult(**dict_cookies)

@managers.no_multi
async def create_mmt(self) -> MMT:
Expand Down
35 changes: 11 additions & 24 deletions genshin/client/components/auth/subclients/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
"""

import json
import random
import typing
from string import ascii_letters, digits
from http.cookies import SimpleCookie

import aiohttp

Expand All @@ -15,7 +14,7 @@
from genshin.client.components import base
from genshin.models.auth.cookie import AppLoginResult
from genshin.models.auth.geetest import SessionMMT, SessionMMTResult
from genshin.models.auth.qrcode import QRCodeCheckResult, QRCodeCreationResult
from genshin.models.auth.qrcode import QRCodeCreationResult, QRCodeStatus
from genshin.models.auth.verification import ActionTicket
from genshin.utility import auth as auth_utility
from genshin.utility import ds as ds_utility
Expand Down Expand Up @@ -180,46 +179,34 @@ async def _verify_email(self, code: str, ticket: ActionTicket) -> None:

async def _create_qrcode(self) -> QRCodeCreationResult:
"""Create a QR code for login."""
if self.default_game is None:
raise RuntimeError("No default game set.")

app_id = constants.APP_IDS[self.default_game][self.region]
device_id = "".join(random.choices(ascii_letters + digits, k=64))

async with aiohttp.ClientSession() as session:
async with session.post(
routes.CREATE_QRCODE_URL.get_url(),
json={"app_id": app_id, "device": device_id},
headers=auth_utility.QRCODE_HEADERS,
) as r:
data = await r.json()

if not data["data"]:
errors.raise_for_retcode(data)

url: str = data["data"]["url"]
return QRCodeCreationResult(
app_id=app_id,
ticket=url.split("ticket=")[1],
device_id=device_id,
url=url,
ticket=data["data"]["ticket"],
url=data["data"]["url"],
)

async def _check_qrcode(self, app_id: str, device_id: str, ticket: str) -> QRCodeCheckResult:
async def _check_qrcode(self, ticket: str) -> tuple[QRCodeStatus, SimpleCookie]:
"""Check the status of a QR code login."""
payload = {
"app_id": app_id,
"device": device_id,
"ticket": ticket,
}
payload = {"ticket": ticket}

async with aiohttp.ClientSession() as session:
async with session.post(
routes.CHECK_QRCODE_URL.get_url(),
json=payload,
headers=auth_utility.QRCODE_HEADERS,
) as r:
data = await r.json()

if not data["data"]:
errors.raise_for_retcode(data)
if not data["data"]:
errors.raise_for_retcode(data)

return QRCodeCheckResult(**data["data"])
return QRCodeStatus(data["data"]["status"]), r.cookies
4 changes: 2 additions & 2 deletions genshin/client/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ def get_url(self, region: types.Region, game: types.Game) -> yarl.URL:
MOBILE_OTP_URL = Route("https://passport-api.miyoushe.com/account/ma-cn-verifier/verifier/createLoginCaptcha")
MOBILE_LOGIN_URL = Route("https://passport-api.miyoushe.com/account/ma-cn-passport/web/loginByMobileCaptcha")

CREATE_QRCODE_URL = Route("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/fetch")
CHECK_QRCODE_URL = Route("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query")
CREATE_QRCODE_URL = Route("https://passport-api.miyoushe.com/account/ma-cn-passport/web/createQRLogin")
CHECK_QRCODE_URL = Route("https://passport-api.miyoushe.com/account/ma-cn-passport/web/queryQRLoginStatus")

CREATE_MMT_URL = InternationalRoute(
overseas="https://sg-public-api.hoyolab.com/event/toolcomsrv/risk/createGeetest?is_high=true",
Expand Down
11 changes: 6 additions & 5 deletions genshin/models/auth/cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ class QRLoginResult(CookieLoginResult):
Returned by `client.login_with_qrcode`.
"""

stoken_v2: str
account_id: str
ltuid: str
ltmid: str
cookie_token: str
cookie_token_v2: str
account_mid_v2: str
account_id_v2: str
ltoken_v2: str
ltmid_v2: str
ltuid_v2: str


class AppLoginResult(CookieLoginResult):
Expand Down
36 changes: 2 additions & 34 deletions genshin/models/auth/qrcode.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Miyoushe QR Code Models"""

import enum
import json
import typing

if typing.TYPE_CHECKING:
Expand All @@ -12,50 +11,19 @@
except ImportError:
import pydantic

__all__ = ["QRCodeCheckResult", "QRCodeCreationResult", "QRCodePayload", "QRCodeRawData", "QRCodeStatus"]
__all__ = ["QRCodeCreationResult", "QRCodeStatus"]


class QRCodeStatus(enum.Enum):
"""QR code check status."""

INIT = "Init"
CREATED = "Created"
SCANNED = "Scanned"
CONFIRMED = "Confirmed"


class QRCodeRawData(pydantic.BaseModel):
"""QR code raw data."""

account_id: str = pydantic.Field(alias="uid")
"""Miyoushe account id."""
game_token: str = pydantic.Field(alias="token")


class QRCodePayload(pydantic.BaseModel):
"""QR code check result payload."""

proto: str
ext: str
raw: typing.Optional[QRCodeRawData] = None

@pydantic.validator("raw", pre=True)
def _convert_raw_data(cls, value: typing.Optional[str] = None) -> typing.Union[QRCodeRawData, None]:
if value:
return QRCodeRawData(**json.loads(value))
return None


class QRCodeCheckResult(pydantic.BaseModel):
"""QR code check result."""

status: QRCodeStatus = pydantic.Field(alias="stat")
payload: QRCodePayload


class QRCodeCreationResult(pydantic.BaseModel):
"""QR code creation result."""

app_id: str
ticket: str
device_id: str
url: str
8 changes: 8 additions & 0 deletions genshin/utility/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@
"x-rpc-client_type": "2",
}

QRCODE_HEADERS = {
"x-rpc-app_id": "bll8iq97cem8",
"x-rpc-client_type": "4",
"x-rpc-game_biz": "bbs_cn",
"x-rpc-device_fp": "38d7fa104e5d7",
"x-rpc-device_id": "586f1440-856a-4243-8076-2b0a12314197",
}

CREATE_MMT_HEADERS = {
types.Region.OVERSEAS: {
"x-rpc-challenge_path": "https://bbs-api-os.hoyolab.com/game_record/app/hkrpg/api/challenge",
Expand Down

0 comments on commit e4ca614

Please sign in to comment.