完成 skd bind

This commit is contained in:
qwerdvd 2023-08-10 22:05:22 +08:00
parent 78c2d97165
commit bc4c1983ec
19 changed files with 605 additions and 0 deletions

View File

@ -0,0 +1,6 @@
from gsuid_core.utils.plugins_config.gs_config import StringConfig
from ..utils.resource.RESOURCE_PATH import CONFIG_PATH
from .config_default import CONIFG_DEFAULT
arkconfig = StringConfig('ArknightsUID', CONFIG_PATH, CONIFG_DEFAULT)

View File

@ -0,0 +1,13 @@
from gsuid_core.utils.plugins_config.models import (
GSC,
GsStrConfig,
)
CONIFG_DEFAULT: dict[str, GSC] = {
'ArknightsPrefix': GsStrConfig(
'插件命令前缀(确认无冲突再修改)',
'用于本插件的前缀设定',
'ark',
),
}

View File

@ -0,0 +1,20 @@
# from gsuid_core.bot import Bot
# from gsuid_core.logger import logger
# from gsuid_core.models import Event
# from gsuid_core.sv import SV
# from ..utils.resource.download_all_resource import download_all_resource
# sv_download_config = SV('下载资源', pm=2)
# @sv_download_config.on_fullmatch(('下载全部资源'))
# async def send_download_resource_msg(bot: Bot, ev: Event):
# await bot.send('正在开始下载~可能需要较久的时间!')
# im = await download_all_resource()
# await bot.send(im)
# async def startup():
# logger.info('[资源文件下载] 正在检查与下载缺失的资源文件,可能需要较长时间,请稍等')
# logger.info(f'[资源文件下载] {await download_all_resource()}')

View File

@ -0,0 +1,13 @@
# from ..arknightsuid_resource import startup
# async def all_start():
# try:
# await startup()
# except Exception as e:
# logger.exception(e)
# threading.Thread(target=lambda: asyncio.run(all_start()), daemon=True).start()

View File

@ -0,0 +1,73 @@
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.sv import SV
from ..utils.ark_prefix import PREFIX
from ..utils.database.models import ArknightsBind
from ..utils.message import send_diff_msg
from .deal_skd_cred import deal_skd_cred
# from .draw_user_card import get_user_card
sv_user_config = SV('ark用户管理', pm=2)
sv_user_add = SV('ark用户添加')
sv_user_info = SV('ark用户信息')
ark_skd_cred_add = SV('森空岛cred绑定')
# sv_user_help = SV('ark绑定帮助')
# @sv_user_info.on_fullmatch((f'{PREFIX}绑定信息'))
# async def send_bind_card(bot: Bot, ev: Event):
# await bot.logger.info('ark开始执行[查询用户绑定状态]')
# uid_list = await get_user_card(ev.bot_id, ev.user_id)
# await bot.logger.info('ark[查询用户绑定状态]完成!等待图片发送中...')
# await bot.send(uid_list)
@sv_user_info.on_command(
(f'{PREFIX}绑定uid', f'{PREFIX}切换uid', f'{PREFIX}删除uid', f'{PREFIX}解绑uid')
)
async def send_link_uid_msg(bot: Bot, ev: Event):
await bot.logger.info("开始执行[绑定/解绑用户信息]")
qid = ev.user_id
await bot.logger.info(f"[绑定/解绑]UserID: {qid}")
ark_uid = ev.text.strip()
if ark_uid and not ark_uid.isdigit():
return await bot.send("你输入了错误的格式!")
if '绑定' in ev.command:
data = await ArknightsBind.insert_uid(qid, ev.bot_id, ark_uid, ev.group_id)
return await send_diff_msg(
bot,
data,
{
0: f'绑定ARK_UID{ark_uid}成功!',
-1: f'ARK_UID{ark_uid}的位数不正确!',
-2: f'ARK_UID{ark_uid}已经绑定过了!',
-3: '你输入了错误的格式!',
},
)
elif '切换' in ev.command:
data = await ArknightsBind.switch_uid_by_game(qid, ev.bot_id, ark_uid)
if isinstance(data, list):
return await bot.send(f'切换ARK_UID{ark_uid}成功!')
else:
return await bot.send(f'尚未绑定该ARK_UID{ark_uid}')
else:
data = await ArknightsBind.delete_uid(qid, ev.bot_id, ark_uid)
return await send_diff_msg(
bot,
data,
{
0: f'删除ARK_UID{ark_uid}成功!',
-1: f'该ARK_UID{ark_uid}不在已绑定列表中!',
},
)
@ark_skd_cred_add.on_prefix(("skd添加cred", "森空岛添加CRED"))
async def send_ark_skd_add_cred_msg(bot: Bot, ev: Event):
im = await deal_skd_cred(ev.bot_id, ev.text, ev.user_id)
await bot.send(im)

View File

@ -0,0 +1,21 @@
from ..utils.ark_api import ark_skd_api
from ..utils.database.models import ArknightsUser
ERROR_HINT = '添加失败,格式为:用户ID - Cred\n \
例如:1810461245 - VropL583Sb1hClS5buQ4nSASkDlL8tMT'
async def deal_skd_cred(bot_id: str, cred: str, user_id: str) -> str:
if '-' not in cred:
return ERROR_HINT
_ck = cred.replace(' ', '').split('-')
if len(_ck) != 2 or not _ck[0] or not _ck[0].isdigit() or not _ck[1]:
return ERROR_HINT
check_cred = await ark_skd_api.check_cred_valid(_ck[1])
if check_cred:
return 'Cred无效!'
uid, cred = _ck[0], _ck[1]
await ArknightsUser.insert_data(user_id, bot_id,
Cred=cred, uid=uid, skd_uid=check_cred)
return '添加成功!'

View File

@ -0,0 +1 @@
ARK_USER_ME = 'https://zonai.skland.com/api/v1/user/me'

View File

@ -0,0 +1,86 @@
from copy import deepcopy
from typing import Any, Literal
from aiohttp import ClientSession, ContentTypeError, TCPConnector
from gsuid_core.logger import logger
from ...database.models import ArknightsUser
from ...models.skland.models import ArknightsUserMeModel
from .api import ARK_USER_ME
class BaseArkApi:
ssl_verify = True
_HEADER = {
"Host": "zonai.skland.com",
"Origin": "https://www.skland.com",
"Referer": "https://www.skland.com/",
"content-type": "application/json; charset=UTF-8",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
}
async def check_cred_valid(self, Cred: str) -> bool | str:
header = deepcopy(self._HEADER)
header['Cred'] = Cred
raw_data = await self._ark_request(ARK_USER_ME, header=header)
unpack_data = self.unpack(raw_data)
if isinstance(unpack_data, int):
return False
else:
return ArknightsUserMeModel(**unpack_data).user.id_
def unpack(self, raw_data: dict | int) -> dict | int:
if isinstance(raw_data, dict):
return raw_data["data"]
else:
return raw_data
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,
) -> dict | int:
if "Cred" not in header:
target_user_id = (
data["friendUserId"]
if data and "friendUserId" in data
else None
)
Cred: str | None = await ArknightsUser.get_random_cookie(
target_user_id if target_user_id else "18888888"
)
if Cred is None:
return -61
arkUser = await ArknightsUser.base_select_data(ArknightsUser, Cred=Cred)
if arkUser is None:
return -61
header["Cred"] = Cred
async with ClientSession(
connector=TCPConnector(verify_ssl=self.ssl_verify)
) as client:
async with client.request(
method,
url=url,
headers=header,
params=params,
json=data,
timeout=300,
) as resp:
try:
raw_data = await resp.json()
except ContentTypeError:
_raw_data = await resp.text()
raw_data = {"code": -999, "data": _raw_data}
if (
raw_data
and 'code' in raw_data
and raw_data['code'] != 0
):
return raw_data['code']
logger.debug(raw_data)
return raw_data

View File

@ -0,0 +1,3 @@
from .api.skd.request import BaseArkApi
ark_skd_api = BaseArkApi()

View File

@ -0,0 +1,3 @@
from ..arknightsuid_config.ark_config import arkconfig
PREFIX = arkconfig.get_config('ArknightsPrefix').data

View File

@ -0,0 +1,34 @@
from gsuid_core.utils.database.base_models import (
Bind,
User,
)
from gsuid_core.webconsole.mount_app import GsAdminModel, PageSchema, site
from sqlmodel import Field
class ArknightsBind(Bind, table=True):
uid: str | None = Field(default=None, title='明日方舟UID')
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凭证')
@site.register_admin
class ArknightsBindadmin(GsAdminModel):
pk_name = 'id'
page_schema = PageSchema(label='方舟绑定管理', icon='fa fa-users') # type: ignore
# 配置管理模型
model = ArknightsBind
@site.register_admin
class ArknightsUseradmin(GsAdminModel):
pk_name = 'id'
page_schema = PageSchema(label='方舟SKD CRED管理', icon='fa fa-database') # type: ignore
# 配置管理模型
model = ArknightsUser

View File

@ -0,0 +1,22 @@
from pathlib import Path
import aiofiles
from aiohttp.client import ClientSession
from aiohttp.client_exceptions import ClientConnectorError
from gsuid_core.logger import logger
async def download_file(
url: str,
path: Path,
name: str,
):
sess = ClientSession()
try:
async with sess.get(url) as res:
content = await res.read()
except ClientConnectorError:
logger.warning(f"[wzry]{name}下载失败")
return url, path, name
async with aiofiles.open(path / name, "wb") as f:
await f.write(content)

View File

@ -0,0 +1,16 @@
from typing import Any
from gsuid_core.bot import Bot
async def send_diff_msg(bot: Bot, code: Any, data: dict | None = None):
if data is None:
data = {
0: '绑定UID成功!',
-1: 'UID的位数不正确!',
-2: 'UID已经绑定过了!',
-3: '你输入了错误的格式!',
}
for retcode in data:
if code == retcode:
return await bot.send(data[retcode])

View File

@ -1,5 +1,100 @@
from msgspec import Struct, field from msgspec import Struct, field
################
# ArknightsUserMeModel Start
################
class UserMeInfoApply(Struct):
nickname: str
profile: str
class UserMeModerator(Struct):
isModerator: bool
class UserGameStatusAp(Struct):
current: int
max_: int = field(name='max')
lastApAddTime: int
completeRecoveryTime: int
class UserGameStatusSecretary(Struct):
charId: str
skinId: str
class UserGameStatusAvatar(Struct):
type_: str = field(name='type')
id_: str = field(name='id')
class UserGameStatus(Struct):
uid: str
name: str
level: int
avatar: UserGameStatusAvatar
registerTs: int
mainStageProgress: str
secretary: UserGameStatusSecretary
resume: str
subscriptionEnd: int
ap: UserGameStatusAp
storeTs: int
lastOnlineTs: int
charCnt: int
furnitureCnt: int
skinCnt: int
class UserMeInfoRts(Struct):
liked: str
collect: str
comment: str
follow: str
fans: str
black: str
pub: str
class UserMeInfo(Struct):
id_: str = field(name='id')
nickname: str
profile: str
avatarCode: int
avatar: str
backgroundCode: int
isCreator: bool
creatorIdentifiers: list[str]
status: int
operationStatus: int
identity: int
kind: int
latestIpLocation: str
moderatorStatus: int
moderatorChangeTime: int
gender: int
birthday: str
class ArknightsUserMeModel(Struct):
user: UserMeInfo
userRts: UserMeInfoRts
userSanctionList: list[str]
gameStatus: UserGameStatus
moderator: UserMeModerator
userInfoApply: UserMeInfoApply
################
# ArknightsUserMeModel End
################
################
# ArknightsPlayerInfoModel Start
################
class PlayerSkinAsset(Struct): class PlayerSkinAsset(Struct):
pass pass
@ -480,3 +575,8 @@ class ArknightsPlayerInfoModel(Struct, omit_defaults=True, gc=False):
manufactureFormulaInfoMap: dict[str, PlayerManufactureFormulaInfo] manufactureFormulaInfoMap: dict[str, PlayerManufactureFormulaInfo]
charAssets: list[PlayerCharAsset] charAssets: list[PlayerCharAsset]
skinAssets: list[PlayerSkinAsset] skinAssets: list[PlayerSkinAsset]
################
# ArknightsPlayerInfoModel End
################

View File

@ -0,0 +1,23 @@
import sys
from gsuid_core.data_store import get_res_path
MAIN_PATH = get_res_path() / 'ArknightsUID'
sys.path.append(str(MAIN_PATH))
CU_BG_PATH = MAIN_PATH / 'bg'
CONFIG_PATH = MAIN_PATH / 'config.json'
PLAYER_PATH = MAIN_PATH / 'players'
RESOURCE_PATH = MAIN_PATH / 'resource'
def init_dir():
for i in [
MAIN_PATH,
CU_BG_PATH,
PLAYER_PATH,
RESOURCE_PATH,
]:
i.mkdir(parents=True, exist_ok=True)
init_dir()

View File

@ -0,0 +1,11 @@
import asyncio
from .download_from_cos import download_all_file_from_cos
async def download_all_resource():
ret = await asyncio.gather(download_all_file_from_cos())
ret = [str(x) for x in ret if x]
if ret:
return '\n'.join(ret)
return 'sr全部资源下载完成!'

View File

@ -0,0 +1,101 @@
import asyncio
import os
from pathlib import Path
from aiohttp.client import ClientSession
from gsuid_core.logger import logger
from msgspec import json as msgjson
from .download_url import download_file
from .RESOURCE_PATH import RESOURCE_PATH
with open(
Path(__file__).parent / 'resource_map.json', encoding='UTF-8'
) as f:
resource_map = msgjson.decode(
f.read(),
type=dict[str, dict[str, dict[str, dict[str, int | str]]]],
)
async def download_all_file_from_cos():
async def _download(tasks: list[asyncio.Task]):
failed_list.extend(
list(filter(lambda x: x is not None, await asyncio.gather(*tasks)))
)
tasks.clear()
logger.info('[cos]下载完成!')
failed_list: list[tuple[str, str, str, str]] = []
TASKS = []
async with ClientSession() as sess:
for res_type in ['resource']:
logger.info('[cos]开始下载资源文件...')
resource_type_list = [
'character',
'character_portrait',
'character_preview',
'consumable',
'element',
'light_cone',
'relic',
'skill',
]
for resource_type in resource_type_list:
file_dict = resource_map[res_type][resource_type]
logger.info(
f'[cos]数据库[{resource_type}]中存在{len(file_dict)}个内容!'
)
temp_num = 0
for file_name, file_info in file_dict.items():
name = file_name
size = file_info['size']
url = file_info['url']
if isinstance(url, int):
continue
path = Path(RESOURCE_PATH / resource_type / name)
if path.exists():
is_diff = size == os.stat(path).st_size
else:
is_diff = True
if (
not path.exists()
or not os.stat(path).st_size
or not is_diff
):
logger.info(f'[cos]开始下载[{resource_type}]_[{name}]...')
temp_num += 1
TASKS.append(
asyncio.wait_for(
download_file(
url, res_type, resource_type, name, sess
),
timeout=60,
)
)
# await download_file(url, FILE_TO_PATH[file], name)
if len(TASKS) >= 10:
await _download(TASKS)
else:
await _download(TASKS)
if temp_num == 0:
im = f'[cos]数据库[{resource_type}]无需下载!'
else:
im = f'[cos]数据库[{resource_type}]已下载{temp_num}个内容!'
temp_num = 0
logger.info(im)
if failed_list:
logger.info(f'[cos]开始重新下载失败的{len(failed_list)}个文件...')
for url, res_type, resource_type, name in failed_list:
TASKS.append(
asyncio.wait_for(
download_file(url, res_type, resource_type, name, sess),
timeout=60,
)
)
if len(TASKS) >= 10:
await _download(TASKS)
else:
await _download(TASKS)
if count := len(failed_list):
logger.error(f'[cos]仍有{count}个文件未下载,请使用命令 `下载全部资源` 重新下载')

View File

@ -0,0 +1,59 @@
import aiofiles
from aiohttp.client import ClientSession
from aiohttp.client_exceptions import ClientConnectorError
from gsuid_core.logger import logger
from .RESOURCE_PATH import RESOURCE_PATH
PATHDICT = {
'resource': RESOURCE_PATH,
}
async def download(
url: str,
res_type: str,
resource_type: str,
name: str,
) -> tuple[str, str, str] | None:
"""
:说明:
下载URL保存入目录
:参数:
* url: `str`
资源下载地址
* res_type: `str`
资源类型`resource``wiki`
* resource_type: `str`
资源文件夹名
* name: `str`
资源保存名称
:返回(失败才会有返回值):
url: `str`
resource_type: `str`
name: `str`
"""
async with ClientSession() as sess:
return await download_file(url, res_type, resource_type, name, sess)
async def download_file(
url: str,
res_type: str,
resource_type: str,
name: str,
sess: ClientSession | None = None,
) -> tuple[str, str, str] | None:
if sess is None:
sess = ClientSession()
try:
async with sess.get(url) as res:
content = await res.read()
except ClientConnectorError:
logger.warning(f"[cos]{name}下载失败")
return url, resource_type, name
async with aiofiles.open(
PATHDICT[res_type] / resource_type / name, "wb"
) as f:
await f.write(content)

0
__nest__.py Normal file
View File