From bc4c1983eca18754105cdef75d260f5f7d5663c3 Mon Sep 17 00:00:00 2001 From: qwerdvd <2450899274@qq.com> Date: Thu, 10 Aug 2023 22:05:22 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E5=AE=8C=E6=88=90=20skd=20bind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../arknightsuid_config/ark_config.py | 6 ++ .../arknightsuid_config/config_default.py | 13 +++ .../arknightsuid_resource/__init__.py | 20 ++++ ArknightsUID/arknightsuid_start/__init__.py | 13 +++ ArknightsUID/arknightsuid_user/__init__.py | 73 +++++++++++++ .../arknightsuid_user/deal_skd_cred.py | 21 ++++ ArknightsUID/utils/api/skd/api.py | 1 + ArknightsUID/utils/api/skd/request.py | 86 +++++++++++++++ ArknightsUID/utils/ark_api.py | 3 + ArknightsUID/utils/ark_prefix.py | 3 + ArknightsUID/utils/database/models.py | 34 ++++++ ArknightsUID/utils/download.py | 22 ++++ ArknightsUID/utils/message.py | 16 +++ ArknightsUID/utils/models/skland/models.py | 100 +++++++++++++++++ ArknightsUID/utils/resource/RESOURCE_PATH.py | 23 ++++ .../utils/resource/download_all_resource.py | 11 ++ .../utils/resource/download_from_cos.py | 101 ++++++++++++++++++ ArknightsUID/utils/resource/download_url.py | 59 ++++++++++ __nest__.py | 0 19 files changed, 605 insertions(+) create mode 100644 ArknightsUID/arknightsuid_config/ark_config.py create mode 100644 ArknightsUID/arknightsuid_config/config_default.py create mode 100644 ArknightsUID/arknightsuid_resource/__init__.py create mode 100644 ArknightsUID/arknightsuid_start/__init__.py create mode 100644 ArknightsUID/arknightsuid_user/__init__.py create mode 100644 ArknightsUID/arknightsuid_user/deal_skd_cred.py create mode 100644 ArknightsUID/utils/api/skd/request.py create mode 100644 ArknightsUID/utils/ark_api.py create mode 100644 ArknightsUID/utils/ark_prefix.py create mode 100644 ArknightsUID/utils/database/models.py create mode 100644 ArknightsUID/utils/download.py create mode 100644 ArknightsUID/utils/message.py create mode 100644 ArknightsUID/utils/resource/RESOURCE_PATH.py create mode 100644 ArknightsUID/utils/resource/download_all_resource.py create mode 100644 ArknightsUID/utils/resource/download_from_cos.py create mode 100644 ArknightsUID/utils/resource/download_url.py create mode 100644 __nest__.py diff --git a/ArknightsUID/arknightsuid_config/ark_config.py b/ArknightsUID/arknightsuid_config/ark_config.py new file mode 100644 index 0000000..664e245 --- /dev/null +++ b/ArknightsUID/arknightsuid_config/ark_config.py @@ -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) diff --git a/ArknightsUID/arknightsuid_config/config_default.py b/ArknightsUID/arknightsuid_config/config_default.py new file mode 100644 index 0000000..5fbba64 --- /dev/null +++ b/ArknightsUID/arknightsuid_config/config_default.py @@ -0,0 +1,13 @@ + +from gsuid_core.utils.plugins_config.models import ( + GSC, + GsStrConfig, +) + +CONIFG_DEFAULT: dict[str, GSC] = { + 'ArknightsPrefix': GsStrConfig( + '插件命令前缀(确认无冲突再修改)', + '用于本插件的前缀设定', + 'ark', + ), +} diff --git a/ArknightsUID/arknightsuid_resource/__init__.py b/ArknightsUID/arknightsuid_resource/__init__.py new file mode 100644 index 0000000..c25b6a3 --- /dev/null +++ b/ArknightsUID/arknightsuid_resource/__init__.py @@ -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()}') diff --git a/ArknightsUID/arknightsuid_start/__init__.py b/ArknightsUID/arknightsuid_start/__init__.py new file mode 100644 index 0000000..f697d10 --- /dev/null +++ b/ArknightsUID/arknightsuid_start/__init__.py @@ -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() diff --git a/ArknightsUID/arknightsuid_user/__init__.py b/ArknightsUID/arknightsuid_user/__init__.py new file mode 100644 index 0000000..1122ecd --- /dev/null +++ b/ArknightsUID/arknightsuid_user/__init__.py @@ -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) diff --git a/ArknightsUID/arknightsuid_user/deal_skd_cred.py b/ArknightsUID/arknightsuid_user/deal_skd_cred.py new file mode 100644 index 0000000..3e768d2 --- /dev/null +++ b/ArknightsUID/arknightsuid_user/deal_skd_cred.py @@ -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 '添加成功!' diff --git a/ArknightsUID/utils/api/skd/api.py b/ArknightsUID/utils/api/skd/api.py index e69de29..43c7a4e 100644 --- a/ArknightsUID/utils/api/skd/api.py +++ b/ArknightsUID/utils/api/skd/api.py @@ -0,0 +1 @@ +ARK_USER_ME = 'https://zonai.skland.com/api/v1/user/me' diff --git a/ArknightsUID/utils/api/skd/request.py b/ArknightsUID/utils/api/skd/request.py new file mode 100644 index 0000000..1a3d965 --- /dev/null +++ b/ArknightsUID/utils/api/skd/request.py @@ -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 diff --git a/ArknightsUID/utils/ark_api.py b/ArknightsUID/utils/ark_api.py new file mode 100644 index 0000000..e9cffb8 --- /dev/null +++ b/ArknightsUID/utils/ark_api.py @@ -0,0 +1,3 @@ +from .api.skd.request import BaseArkApi + +ark_skd_api = BaseArkApi() diff --git a/ArknightsUID/utils/ark_prefix.py b/ArknightsUID/utils/ark_prefix.py new file mode 100644 index 0000000..5df1018 --- /dev/null +++ b/ArknightsUID/utils/ark_prefix.py @@ -0,0 +1,3 @@ +from ..arknightsuid_config.ark_config import arkconfig + +PREFIX = arkconfig.get_config('ArknightsPrefix').data diff --git a/ArknightsUID/utils/database/models.py b/ArknightsUID/utils/database/models.py new file mode 100644 index 0000000..35ab9f7 --- /dev/null +++ b/ArknightsUID/utils/database/models.py @@ -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 diff --git a/ArknightsUID/utils/download.py b/ArknightsUID/utils/download.py new file mode 100644 index 0000000..2b493f0 --- /dev/null +++ b/ArknightsUID/utils/download.py @@ -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) diff --git a/ArknightsUID/utils/message.py b/ArknightsUID/utils/message.py new file mode 100644 index 0000000..96ba4b0 --- /dev/null +++ b/ArknightsUID/utils/message.py @@ -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]) diff --git a/ArknightsUID/utils/models/skland/models.py b/ArknightsUID/utils/models/skland/models.py index 682ee28..01731f5 100644 --- a/ArknightsUID/utils/models/skland/models.py +++ b/ArknightsUID/utils/models/skland/models.py @@ -1,5 +1,100 @@ 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): pass @@ -480,3 +575,8 @@ class ArknightsPlayerInfoModel(Struct, omit_defaults=True, gc=False): manufactureFormulaInfoMap: dict[str, PlayerManufactureFormulaInfo] charAssets: list[PlayerCharAsset] skinAssets: list[PlayerSkinAsset] + + +################ +# ArknightsPlayerInfoModel End +################ diff --git a/ArknightsUID/utils/resource/RESOURCE_PATH.py b/ArknightsUID/utils/resource/RESOURCE_PATH.py new file mode 100644 index 0000000..99ac0e8 --- /dev/null +++ b/ArknightsUID/utils/resource/RESOURCE_PATH.py @@ -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() diff --git a/ArknightsUID/utils/resource/download_all_resource.py b/ArknightsUID/utils/resource/download_all_resource.py new file mode 100644 index 0000000..a51357f --- /dev/null +++ b/ArknightsUID/utils/resource/download_all_resource.py @@ -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全部资源下载完成!' diff --git a/ArknightsUID/utils/resource/download_from_cos.py b/ArknightsUID/utils/resource/download_from_cos.py new file mode 100644 index 0000000..927bd19 --- /dev/null +++ b/ArknightsUID/utils/resource/download_from_cos.py @@ -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}个文件未下载,请使用命令 `下载全部资源` 重新下载') diff --git a/ArknightsUID/utils/resource/download_url.py b/ArknightsUID/utils/resource/download_url.py new file mode 100644 index 0000000..88b49b9 --- /dev/null +++ b/ArknightsUID/utils/resource/download_url.py @@ -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) diff --git a/__nest__.py b/__nest__.py new file mode 100644 index 0000000..e69de29