完整支持新版 sign

This commit is contained in:
qwerdvd 2023-09-19 23:30:16 +08:00
parent ab9b434bd9
commit 7386758016
4 changed files with 172 additions and 84 deletions

View File

@ -1,3 +1,4 @@
import re
from ..utils.ark_api import ark_skd_api from ..utils.ark_api import ark_skd_api
from ..utils.database.models import ArknightsBind, ArknightsUser from ..utils.database.models import ArknightsBind, ArknightsUser
@ -10,7 +11,15 @@ async def deal_skd_cred(bot_id: str, cred: str, user_id: str) -> str:
if uid_list is None: if uid_list is None:
return UID_HINT return UID_HINT
check_cred = await ark_skd_api.check_cred_valid(cred) match = re.search(r'\S+', cred)
if not match:
return '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): if isinstance(check_cred, bool):
return 'Cred无效!' 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) skd_data = await ArknightsUser.select_data_by_uid(uid)
if not skd_data: if not skd_data:
await ArknightsUser.insert_data(user_id, bot_id, 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: else:
await ArknightsUser.update_data(user_id, bot_id, 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 '添加成功!' return '添加成功!'

View File

@ -1,5 +1,7 @@
ARK_USER_ME = 'https://zonai.skland.com/api/v1/user/me' 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_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' ARK_GEN_CRED_BY_CODE = 'https://zonai.skland.com/api/v1/user/auth/generate_cred_by_code'

View File

@ -3,7 +3,8 @@ import hashlib
import json import json
import time import time
import hmac import hmac
from typing import Any, ClassVar, Literal from typing import Any, ClassVar, Literal, cast
from urllib.parse import urlparse
import msgspec import msgspec
from aiohttp import ClientSession, ContentTypeError, TCPConnector from aiohttp import ClientSession, ContentTypeError, TCPConnector
@ -17,21 +18,31 @@ from ...models.skland.models import (
ArknightsPlayerInfoModel, ArknightsPlayerInfoModel,
ArknightsUserMeModel, 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 proxy_url = core_plugins_config.get_config('proxy').data
ssl_verify = core_plugins_config.get_config('MhySSLVerify').data ssl_verify = core_plugins_config.get_config('MhySSLVerify').data
class TokenExpiredError(Exception):
pass
class TokenRefreshFailed(Exception):
pass
class BaseArkApi: class BaseArkApi:
proxy_url: str | None = proxy_url if proxy_url else None proxy_url: str | None = proxy_url if proxy_url else None
_HEADER: ClassVar[dict[str, str]] = { _HEADER: ClassVar[dict[str, str]] = {
'Host': 'zonai.skland.com', 'Host': 'zonai.skland.com',
'Platform': '1', 'platform': '1',
'Origin': 'https://www.skland.com', 'Origin': 'https://www.skland.com',
'Referer': 'https://www.skland.com/', 'Referer': 'https://www.skland.com/',
'content-type': 'application/json', 'Content-Type': 'application/json',
'user-agent': 'Skland/1.0.1 (com.hypergryph.skland; build:100001014; Android 33; ) Okhttp/4.11.0', '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]: 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) await ArknightsUser.delete_user_data_by_uid(uid)
return -61 return -61
header = deepcopy(self._HEADER) header = deepcopy(self._HEADER)
header['Cred'] = cred header['cred'] = cred
raw_data = await self._ark_request( header = await self.set_sign(ARK_PLAYER_INFO, header=header)
raw_data = await self.ark_request(
url=ARK_PLAYER_INFO, url=ARK_PLAYER_INFO,
params={'uid': uid}, params={'uid': uid},
header=header, header=header,
@ -83,8 +95,9 @@ class BaseArkApi:
await ArknightsUser.delete_user_data_by_uid(uid) await ArknightsUser.delete_user_data_by_uid(uid)
return -61 return -61
header = deepcopy(self._HEADER) header = deepcopy(self._HEADER)
header['Cred'] = cred header['cred'] = cred
raw_data = await self._ark_request( header = await self.set_sign(ARK_SKD_SIGN, header=header)
raw_data = await self.ark_request(
url=ARK_SKD_SIGN, url=ARK_SKD_SIGN,
method='POST', method='POST',
data={ data={
@ -110,8 +123,9 @@ class BaseArkApi:
await ArknightsUser.delete_user_data_by_uid(uid) await ArknightsUser.delete_user_data_by_uid(uid)
return -61 return -61
header = deepcopy(self._HEADER) header = deepcopy(self._HEADER)
header['Cred'] = cred header['cred'] = cred
raw_data = await self._ark_request( header = await self.set_sign(ARK_SKD_SIGN, header=header)
raw_data = await self.ark_request(
url=ARK_SKD_SIGN, url=ARK_SKD_SIGN,
method='GET', method='GET',
params={ params={
@ -128,10 +142,11 @@ class BaseArkApi:
else: else:
return msgspec.convert(unpack_data, ArknightsAttendanceCalendarModel) 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 = deepcopy(self._HEADER)
header['Cred'] = Cred header['cred'] = cred
raw_data = await self._ark_request(ARK_USER_ME, header=header) 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): if isinstance(raw_data, int):
return False return False
if 'code' in raw_data and raw_data['code'] == 10001: if 'code' in raw_data and raw_data['code'] == 10001:
@ -140,56 +155,105 @@ class BaseArkApi:
unpack_data = self.unpack(raw_data) unpack_data = self.unpack(raw_data)
return msgspec.convert(unpack_data, type=ArknightsUserMeModel) return msgspec.convert(unpack_data, type=ArknightsUserMeModel)
async def check_code_valid(self, code: str) -> bool | str: # async def check_code_valid(self, code: str) -> bool | str:
data = { # data = {
'kind': 1, # 'kind': 1,
'code': code # 'code': code
} # }
raw_data = await self._ark_request( # raw_data = await self.ark_request(
ARK_GEN_CRED_BY_CODE, # ARK_GEN_CRED_BY_CODE,
data=data # data=data
) # )
if isinstance(raw_data, int): # if isinstance(raw_data, int):
return False # return False
else: # else:
cred = raw_data['cred'] # cred = raw_data['cred']
return cred # return cred
def unpack(self, raw_data: dict) -> dict: def unpack(self, raw_data: dict) -> dict:
return raw_data['data'] return raw_data['data']
async def refresh_token(self, uid: str) -> int | str: async def refresh_token(self, cred: str, uid: str | None = None) -> str:
pass 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( async def set_sign(
self, self,
url: str, url: str,
headers: dict[str, Any], header: dict[str, Any],
data: dict[str, Any] | None = None, data: dict[str, Any] | None = None,
params: dict[str, Any] | None = None, params: dict[str, Any] | None = None,
token: str | None = None,
) -> dict: ) -> dict:
path = url.split("://")[1].split("/")[1] parsed_url = urlparse(url)
path = parsed_url.path
timestamp = str(int(time.time())) timestamp = str(int(time.time()))
str1=json.dumps( str1=json.dumps(
{ {
"platform":headers.get(['platform'],""), "platform": header.get('platform', ''),
'timestamp': timestamp, 'timestamp': timestamp,
'dId': headers.get(["dId"],""), 'dId': header.get('dId', ''),
"vName":headers.get(["vName"],"") "vName": header.get('vName', '')
} },separators=(',', ':')
,separators=(',', ':')
) )
s2="" s2=""
if params: 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: if data:
s2+=json.dumps(data,separators=(',', ':')) s2 += json.dumps(data,separators=(',', ':'))
str2=path+s2+headers["timestamp"]+str1 print(f'{path} {s2} {timestamp} {str1}')
token: str | None = await ArknightsUser.get_token_by_cred(headers['Cred']) str2 = path + s2 + timestamp + str1
sign=hashlib.md5(hmac.new(token.encode(),str2.encode(), hashlib.sha256).hexdigest().encode()).hexdigest() token_ = await ArknightsUser.get_token_by_cred(header['cred'])
headers["sign"]=sign token = token if token else token_
headers["timestamp"]=timestamp if token is None:
return headers 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( async def _ark_request(
self, self,
@ -204,11 +268,11 @@ class BaseArkApi:
connector=TCPConnector(verify_ssl=ssl_verify) connector=TCPConnector(verify_ssl=ssl_verify)
) as client: ) as client:
raw_data = {} raw_data = {}
if 'Cred' not in header: if 'cred' not in header:
return 10001 return 10001
async with client.request( async with client.request(
method, method=method,
url=url, url=url,
headers=header, headers=header,
params=params, params=params,
@ -224,32 +288,29 @@ class BaseArkApi:
logger.info(raw_data) logger.info(raw_data)
# 判断code # 判断code
if 'code' in raw_data and raw_data['code'] != 0: if 'code' in raw_data and raw_data['code'] == 10000:
logger.info(raw_data)
return raw_data
elif 'code' in raw_data and raw_data['code'] == 10000:
#token失效 #token失效
logger.info(raw_data) logger.info(raw_data)
await self.refresh_token(header['Cred']) raise TokenExpiredError
# 判断status
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 raw_data
return 10001 # 判断status
# 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

View File

@ -1,6 +1,6 @@
from typing import Literal 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 gsuid_core.webconsole.mount_app import GsAdminModel, PageSchema, site
from sqlmodel import Field from sqlmodel import Field
from sqlalchemy.future import select from sqlalchemy.future import select
@ -15,20 +15,17 @@ class ArknightsUser(User, table=True):
uid: str | None = Field(default=None, title='明日方舟UID') uid: str | None = Field(default=None, title='明日方舟UID')
skd_uid: str | None = Field(default=None, title='SKD用户ID') skd_uid: str | None = Field(default=None, title='SKD用户ID')
cred: str | None = Field(default=None, title='SKD凭证') 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 @classmethod
@with_session @with_session
async def select_data_by_cred( async def select_data_by_cred(
cls: type[T_User], cls,
session: AsyncSession, session: AsyncSession,
cred: str cred: str
) -> T_User | None: ) -> BaseModel | None:
result = await session.execute( sql= select(cls).where(cls.cred == cred)
select(cls).where( result = await session.execute(sql)
'cred' == cred,
)
)
data = result.scalars().all() data = result.scalars().all()
return data[0] if data else None return data[0] if data else None
@ -37,6 +34,25 @@ class ArknightsUser(User, table=True):
result = await cls.select_data_by_cred(cred) result = await cls.select_data_by_cred(cred)
return getattr(result, 'token') if result else None 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): class ArknightsPush(Push, table=True):
uid: str | None = Field(default=None, title='明日方舟UID') uid: str | None = Field(default=None, title='明日方舟UID')