diff --git a/ArknightsUID/arknightsuid_user/deal_skd_cred.py b/ArknightsUID/arknightsuid_user/deal_skd_cred.py index 3c5449b..2d0f5a7 100644 --- a/ArknightsUID/arknightsuid_user/deal_skd_cred.py +++ b/ArknightsUID/arknightsuid_user/deal_skd_cred.py @@ -1,3 +1,4 @@ +import re from ..utils.ark_api import ark_skd_api from ..utils.database.models import ArknightsBind, ArknightsUser @@ -9,8 +10,16 @@ async def deal_skd_cred(bot_id: str, cred: str, user_id: str) -> str: uid_list = await ArknightsBind.get_uid_list_by_game(user_id, bot_id) if uid_list is None: return UID_HINT + + match = re.search(r'\S+', cred) + if not match: + return 'Cred无效!' - check_cred = await ark_skd_api.check_cred_valid(cred) + # refresh token + token = await ark_skd_api.refresh_token(match.group()) + print(token) + + check_cred = await ark_skd_api.check_cred_valid(match.group(), token) if isinstance(check_cred, bool): return 'Cred无效!' @@ -24,8 +33,8 @@ async def deal_skd_cred(bot_id: str, cred: str, user_id: str) -> str: skd_data = await ArknightsUser.select_data_by_uid(uid) if not skd_data: await ArknightsUser.insert_data(user_id, bot_id, - cred=cred, uid=uid, skd_uid=skd_uid) + cred=match.group(), uid=uid, skd_uid=skd_uid, token=token) else: await ArknightsUser.update_data(user_id, bot_id, - cred=cred, uid=uid, skd_uid=skd_uid) + cred=match.group(), uid=uid, skd_uid=skd_uid, token=token) return '添加成功!' diff --git a/ArknightsUID/utils/api/skd/api.py b/ArknightsUID/utils/api/skd/api.py index 20d351c..e3275f9 100644 --- a/ArknightsUID/utils/api/skd/api.py +++ b/ArknightsUID/utils/api/skd/api.py @@ -1,5 +1,7 @@ ARK_USER_ME = 'https://zonai.skland.com/api/v1/user/me' +ARK_REFRESH_TOKEN = 'https://zonai.skland.com/api/v1/auth/refresh' + ARK_PLAYER_INFO = 'https://zonai.skland.com/api/v1/game/player/info' ARK_GEN_CRED_BY_CODE = 'https://zonai.skland.com/api/v1/user/auth/generate_cred_by_code' diff --git a/ArknightsUID/utils/api/skd/request.py b/ArknightsUID/utils/api/skd/request.py index d050090..299b569 100644 --- a/ArknightsUID/utils/api/skd/request.py +++ b/ArknightsUID/utils/api/skd/request.py @@ -3,7 +3,8 @@ import hashlib import json import time import hmac -from typing import Any, ClassVar, Literal +from typing import Any, ClassVar, Literal, cast +from urllib.parse import urlparse import msgspec from aiohttp import ClientSession, ContentTypeError, TCPConnector @@ -17,21 +18,31 @@ from ...models.skland.models import ( ArknightsPlayerInfoModel, ArknightsUserMeModel, ) -from .api import ARK_GEN_CRED_BY_CODE, ARK_PLAYER_INFO, ARK_SKD_SIGN, ARK_USER_ME +from .api import ARK_PLAYER_INFO, ARK_REFRESH_TOKEN, ARK_SKD_SIGN, ARK_USER_ME proxy_url = core_plugins_config.get_config('proxy').data ssl_verify = core_plugins_config.get_config('MhySSLVerify').data +class TokenExpiredError(Exception): + pass + + +class TokenRefreshFailed(Exception): + pass + + class BaseArkApi: proxy_url: str | None = proxy_url if proxy_url else None _HEADER: ClassVar[dict[str, str]] = { 'Host': 'zonai.skland.com', - 'Platform': '1', + 'platform': '1', 'Origin': 'https://www.skland.com', 'Referer': 'https://www.skland.com/', - 'content-type': 'application/json', - 'user-agent': 'Skland/1.0.1 (com.hypergryph.skland; build:100001014; Android 33; ) Okhttp/4.11.0', + 'Content-Type': 'application/json', + 'User-Agent': 'Skland/1.1.0 (com.hypergryph.skland; build:100100047; Android 33; ) Okhttp/4.11.0', + 'vName': '1.1.0', + 'vCode': '100100047', } async def _pass(self, gt: str, ch: str) -> tuple[str | None, str | None]: @@ -60,8 +71,9 @@ class BaseArkApi: await ArknightsUser.delete_user_data_by_uid(uid) return -61 header = deepcopy(self._HEADER) - header['Cred'] = cred - raw_data = await self._ark_request( + header['cred'] = cred + header = await self.set_sign(ARK_PLAYER_INFO, header=header) + raw_data = await self.ark_request( url=ARK_PLAYER_INFO, params={'uid': uid}, header=header, @@ -83,8 +95,9 @@ class BaseArkApi: await ArknightsUser.delete_user_data_by_uid(uid) return -61 header = deepcopy(self._HEADER) - header['Cred'] = cred - raw_data = await self._ark_request( + header['cred'] = cred + header = await self.set_sign(ARK_SKD_SIGN, header=header) + raw_data = await self.ark_request( url=ARK_SKD_SIGN, method='POST', data={ @@ -110,8 +123,9 @@ class BaseArkApi: await ArknightsUser.delete_user_data_by_uid(uid) return -61 header = deepcopy(self._HEADER) - header['Cred'] = cred - raw_data = await self._ark_request( + header['cred'] = cred + header = await self.set_sign(ARK_SKD_SIGN, header=header) + raw_data = await self.ark_request( url=ARK_SKD_SIGN, method='GET', params={ @@ -128,10 +142,11 @@ class BaseArkApi: else: return msgspec.convert(unpack_data, ArknightsAttendanceCalendarModel) - async def check_cred_valid(self, Cred: str) -> bool | ArknightsUserMeModel: + async def check_cred_valid(self, cred: str, token: str | None = None) -> bool | ArknightsUserMeModel: header = deepcopy(self._HEADER) - header['Cred'] = Cred - raw_data = await self._ark_request(ARK_USER_ME, header=header) + header['cred'] = cred + header = await self.set_sign(ARK_USER_ME, header=header, token=token) + raw_data = await self.ark_request(ARK_USER_ME, header=header) if isinstance(raw_data, int): return False if 'code' in raw_data and raw_data['code'] == 10001: @@ -140,56 +155,105 @@ class BaseArkApi: unpack_data = self.unpack(raw_data) return msgspec.convert(unpack_data, type=ArknightsUserMeModel) - async def check_code_valid(self, code: str) -> bool | str: - data = { - 'kind': 1, - 'code': code - } - raw_data = await self._ark_request( - ARK_GEN_CRED_BY_CODE, - data=data - ) - if isinstance(raw_data, int): - return False - else: - cred = raw_data['cred'] - return cred + # async def check_code_valid(self, code: str) -> bool | str: + # data = { + # 'kind': 1, + # 'code': code + # } + # raw_data = await self.ark_request( + # ARK_GEN_CRED_BY_CODE, + # data=data + # ) + # if isinstance(raw_data, int): + # return False + # else: + # cred = raw_data['cred'] + # return cred def unpack(self, raw_data: dict) -> dict: return raw_data['data'] - async def refresh_token(self, uid: str) -> int | str: - pass + async def refresh_token(self, cred: str, uid: str | None = None) -> str: + header = deepcopy(self._HEADER) + header['cred'] = cred + header['sign_enable'] = 'false' + raw_data = await self.ark_request(url=ARK_REFRESH_TOKEN, header=header) + if isinstance(raw_data, int): + raise TokenRefreshFailed + else: + token = cast(str, self.unpack(raw_data)['token']) + uid = await ArknightsUser.get_uid_by_cred(cred) + if uid is not None: + await ArknightsUser.update_user_attr_by_uid(uid=uid, attr='token', value=token) + return token async def set_sign( self, url: str, - headers: dict[str, Any], + header: dict[str, Any], data: dict[str, Any] | None = None, params: dict[str, Any] | None = None, + token: str | None = None, ) -> dict: - path = url.split("://")[1].split("/")[1] + parsed_url = urlparse(url) + path = parsed_url.path timestamp = str(int(time.time())) str1=json.dumps( { - "platform":headers.get(['platform'],""), + "platform": header.get('platform', ''), 'timestamp': timestamp, - 'dId': headers.get(["dId"],""), - "vName":headers.get(["vName"],"") - } - ,separators=(',', ':') + 'dId': header.get('dId', ''), + "vName": header.get('vName', '') + },separators=(',', ':') ) s2="" if params: - s2+="&".join(["=".join(x) for x in params]) + print(f'params {params}') + s2 += "&".join([str(x) + "=" + str(params[x]) for x in params]) if data: - s2+=json.dumps(data,separators=(',', ':')) - str2=path+s2+headers["timestamp"]+str1 - token: str | None = await ArknightsUser.get_token_by_cred(headers['Cred']) - sign=hashlib.md5(hmac.new(token.encode(),str2.encode(), hashlib.sha256).hexdigest().encode()).hexdigest() - headers["sign"]=sign - headers["timestamp"]=timestamp - return headers + s2 += json.dumps(data,separators=(',', ':')) + print(f'{path} {s2} {timestamp} {str1}') + str2 = path + s2 + timestamp + str1 + token_ = await ArknightsUser.get_token_by_cred(header['cred']) + token = token if token else token_ + if token is None: + raise Exception("token is None") + sign = hashlib.md5(hmac.new(token.encode(),str2.encode(), hashlib.sha256).hexdigest().encode()).hexdigest() + header["sign"] = sign + header["timestamp"] = timestamp + print(header) + return header + + async def ark_request( + self, + url: str, + method: Literal['GET', 'POST'] = 'GET', + header: dict[str, Any] = _HEADER, + params: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, + use_proxy: bool | None = False, + ) -> dict | int: + try: + raw_data = await self._ark_request( + url=url, + method=method, + header=header, + params=params, + data=data, + use_proxy=use_proxy, + ) + except TokenExpiredError: + await self.refresh_token(header['cred']) + header = await self.set_sign(url, header, data, params) + raw_data = await self._ark_request( + url=url, + method=method, + header=header, + params=params, + data=data, + use_proxy=use_proxy, + ) + return raw_data async def _ark_request( self, @@ -204,11 +268,11 @@ class BaseArkApi: connector=TCPConnector(verify_ssl=ssl_verify) ) as client: raw_data = {} - if 'Cred' not in header: + if 'cred' not in header: return 10001 - + async with client.request( - method, + method=method, url=url, headers=header, params=params, @@ -224,32 +288,29 @@ class BaseArkApi: logger.info(raw_data) # 判断code - if 'code' in raw_data and raw_data['code'] != 0: - logger.info(raw_data) - return raw_data - elif 'code' in raw_data and raw_data['code'] == 10000: + if 'code' in raw_data and raw_data['code'] == 10000: #token失效 logger.info(raw_data) - await self.refresh_token(header['Cred']) - + raise TokenExpiredError + return raw_data # 判断status - if 'status' in raw_data and 'msg' in raw_data: - retcode = 1 - else: - retcode = 0 + # if 'status' in raw_data and 'msg' in raw_data: + # retcode = 1 + # else: + # retcode = 0 - if retcode == 1 and data: - vl, ch = await self._pass( - gt=raw_data['data']['captcha']['gt'], - ch=raw_data['data']['captcha']['challenge'] - ) - data['captcha'] = {} - data['captcha']['geetest_challenge'] = ch - data['captcha']['geetest_validate'] = vl - data['captcha']['geetest_seccode'] = f'{vl}|jordan' - elif retcode != 0: - return retcode - else: - return raw_data - return 10001 + # if retcode == 1 and data: + # vl, ch = await self._pass( + # gt=raw_data['data']['captcha']['gt'], + # ch=raw_data['data']['captcha']['challenge'] + # ) + # data['captcha'] = {} + # data['captcha']['geetest_challenge'] = ch + # data['captcha']['geetest_validate'] = vl + # data['captcha']['geetest_seccode'] = f'{vl}|jordan' + # elif retcode != 0: + # return retcode + # else: + # return raw_data + # return 10001 diff --git a/ArknightsUID/utils/database/models.py b/ArknightsUID/utils/database/models.py index 59ef640..d2a4c41 100644 --- a/ArknightsUID/utils/database/models.py +++ b/ArknightsUID/utils/database/models.py @@ -1,6 +1,6 @@ from typing import Literal -from gsuid_core.utils.database.base_models import Bind, Push, T_BaseIDModel, T_User, User, with_session +from gsuid_core.utils.database.base_models import Bind, Push, T_BaseIDModel, T_User, User, with_session, BaseModel from gsuid_core.webconsole.mount_app import GsAdminModel, PageSchema, site from sqlmodel import Field from sqlalchemy.future import select @@ -15,20 +15,17 @@ class ArknightsUser(User, table=True): uid: str | None = Field(default=None, title='明日方舟UID') skd_uid: str | None = Field(default=None, title='SKD用户ID') cred: str | None = Field(default=None, title='SKD凭证') - toekn: str | None = Field(default=None, title='SKD Token') + token: str | None = Field(default=None, title='SKD Token') @classmethod @with_session async def select_data_by_cred( - cls: type[T_User], + cls, session: AsyncSession, cred: str - ) -> T_User | None: - result = await session.execute( - select(cls).where( - 'cred' == cred, - ) - ) + ) -> BaseModel | None: + sql= select(cls).where(cls.cred == cred) + result = await session.execute(sql) data = result.scalars().all() return data[0] if data else None @@ -37,6 +34,25 @@ class ArknightsUser(User, table=True): result = await cls.select_data_by_cred(cred) return getattr(result, 'token') if result else None + @classmethod + async def get_uid_by_cred(cls, cred: str) -> str | None: + result = await cls.select_data_by_cred(cred) + return getattr(result, 'uid') if result else None + + @classmethod + async def update_user_attr_by_uid( + cls, + uid: str, + attr: str, + value: str, + ) -> bool: + retcode = -1 + if await cls.data_exist(uid=uid): + retcode = await cls.update_data_by_uid( + uid, cls.bot_id, None, **{attr: value} + ) + return not bool(retcode) + class ArknightsPush(Push, table=True): uid: str | None = Field(default=None, title='明日方舟UID')