diff --git a/.gitignore b/.gitignore index 7243ae5..d6fc471 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ *.swp +.idea \ No newline at end of file diff --git a/api.py b/api.py index 2f5ea11..437bc30 100644 --- a/api.py +++ b/api.py @@ -1,22 +1,22 @@ -#from objects.api-objects import * import requests from tqdm import tqdm -import json API_VERSION = "v2" HEADERS = { 'User-Agent': 'mc-get-testing' } -def download(file_url:str, file_size:int, path:str): + +def download(file_url: str, file_size: int, path: str): resp = requests.get(file_url, stream=True, headers=HEADERS) - with open(path,'wb') as file: - for data in tqdm(resp.iter_content(), total=file_size,\ - unit_scale=True, unit="byte"): + with open(path, 'wb') as file: + for data in tqdm(resp.iter_content(), total=file_size, unit_scale=True, unit="byte"): file.write(data) -def __method(method:str, api_version:str=API_VERSION): + +def __method(method: str, api_version: str = API_VERSION): api_url = "https://api.modrinth.com" + def request(**args): sub_method = "" if "project" in args: @@ -24,21 +24,19 @@ def __method(method:str, api_version:str=API_VERSION): elif "version" in args: sub_method = "/" + args.pop("version") print(f"{api_url}/{api_version}{method}{sub_method}") - resp = requests.get(f"{api_url}/{api_version}{method}{sub_method}",\ - params=args, headers=HEADERS) - print(resp.headers.get("X-Ratelimit-Remaining",-1)) + resp = requests.get(f"{api_url}/{api_version}{method}{sub_method}", params=args, headers=HEADERS) + print(resp.headers.get("X-Ratelimit-Remaining", -1)) match resp.status_code: case 200: print("200: OK") if type(resp.json()) == list: - return [type(method, (object,), obj)\ - for obj in resp.json()] + return [type(method, (object,), obj) for obj in resp.json()] return type(method, (object,), resp.json()) case 400: - #invalid request + # Invalid request print("400: ERROR") case 401: - # No autorization + # No authorization print("401: ERROR") case 404: ''' @@ -49,9 +47,9 @@ def __method(method:str, api_version:str=API_VERSION): resp.raise_for_status() return request + test = __method("", "") search = __method("/search") project = __method("/project") version = __method("/version") versions = __method("/versions") - diff --git a/mc-get.py b/mc-get.py index b177a41..4bf9685 100755 --- a/mc-get.py +++ b/mc-get.py @@ -6,41 +6,71 @@ import npyscreen import os from colorama import Fore, Style -def validate(): - pass -def version_selector_GUI(vers:list, project:str): +def validate(): + raise NotImplementedError + + +def version_selector_gui(vers: list, project: str): def form(*args): - F = npyscreen.Form(name=f"Select {project} version") - sel = F.add(npyscreen.TitleSelectOne, value=[0,], name="versions:",\ - values=[ver.version_number + " for " +\ - ", ".join(ver.game_versions)\ + f = npyscreen.Form(name=f"Select {project} version") + sel = f.add(npyscreen.TitleSelectOne, value=[0, ], name="versions:", + values=[ver.version_number + " for " + + ", ".join(ver.game_versions) for ver in vers[::-1]], scroll_exit=True) - F.edit() + f.edit() for ver in vers: if ver.version_number == sel.get_selected_objects()[0].split()[0]: return ver return vers[0] + return form -def install(projects:list): + +def download_cache(url: str, filename: str, size: int): + cache_file_path = os.path.join(mcfs.cache_dir, filename) + if not mcfs.is_path_exist(mcfs.cache_dir): + os.mkdir(mcfs.cache_dir) + if not mcfs.is_path_exist(cache_file_path): + api.download(url, size, cache_file_path) + else: + print(f"{filename} is in cache.") + return filename + + +def modpack_install(filename: str): + modpack = mcfs.get_modpack_info(filename) + print(f"{Fore.YELLOW}Installing {modpack.name} modpack...{Style.RESET_ALL}") + for file in modpack.files: + path = file["path"].split("/") + downloads = file["downloads"] + print(file["path"]) + download_cache(downloads[0], path[1], file["fileSize"]) + mcfs.install(path[1], path[0]) + print(f"{Fore.YELLOW}Overriding...{Style.RESET_ALL}") + mcfs.install_modpacks_override(filename) + + +def install(projects: list): to_install = [] for project in projects: project_data = api.project(project=project) to_install.append(project_data) for project in to_install: - versions = api.versions(ids=str(project.versions).replace("'", '"')) #i hate this - version = npyscreen.wrapper_basic(version_selector_GUI(versions,\ - project.slug)) - file = type("mc_file", (object, ), version.files[0]) + versions = api.versions(ids=str(project.versions).replace("'", '"')) # I hate this + version = npyscreen.wrapper_basic(version_selector_gui(versions, + project.slug)) + + file = type("mc_file", (object,), version.files[0]) filename = file.url.split("/")[-1] - cache_file_path = os.path.join(mcfs.cache_dir, filename) - if not mcfs.is_path_exist(mcfs.cache_dir): - os.mkdir(mcfs.cache_dir) - if not mcfs.is_path_exist(cache_file_path): - api.download(file.url, file.size, cache_file_path) - else: - print(f"{filename} is in cache.") + download_cache(file.url, filename, file.size) + # cache_file_path = os.path.join(mcfs.cache_dir, filename) + # if not mcfs.is_path_exist(mcfs.cache_dir): + # os.mkdir(mcfs.cache_dir) + # if not mcfs.is_path_exist(cache_file_path): + # api.download(file.url, file.size, cache_file_path) + # else: + # print(f"{filename} is in cache.") subdir = "" match project.project_type: @@ -50,22 +80,26 @@ def install(projects:list): subdir = "mods" case "shader": subdir = "shaderpacks" + case "modpack": + modpack_install(filename) + continue case _: raise NotImplementedError mcfs.install(filename, subdir) -def search(query:list): - - results = api.search(query=' '.join(query)) +def search(query: list): + results = api.search(query=' '.join(query)) for result in results.hits: print(Fore.GREEN, end="") print(result.get("slug", "error"), end="") print(Style.RESET_ALL, end="") print(f' [{result.get("project_type", "error")}] ', end="") - print(f' : {Fore.GREEN}{result.get("title", "error")}{Style.RESET_ALL} --- {result.get("description", "error")}') + print( + f' : {Fore.GREEN}{result.get("title", "error")}{Style.RESET_ALL} --- {result.get("description", "error")}') + def clean(): if mcfs.is_path_exist(mcfs.cache_dir): @@ -78,11 +112,13 @@ def clean(): print("Nothing to clear.") + if __name__ == "__main__": desc = "Minecraft mods packet manager based on Modrinth API" - parser = argparse.ArgumentParser(description=desc,\ - formatter_class=argparse.RawTextHelpFormatter) - subparsers = parser.add_subparsers(dest="method", required=True) # Переменная, в которую будет записано имя подкоманды + parser = argparse.ArgumentParser(description=desc, + formatter_class=argparse.RawTextHelpFormatter) + subparsers = parser.add_subparsers(dest="method", + required=True) # Переменная, в которую будет записано имя подкоманды parser_install = subparsers.add_parser("install", help="Install one or more mods or resources") parser_install.add_argument("projects", nargs="+") @@ -94,6 +130,7 @@ if __name__ == "__main__": parser_clean = subparsers.add_parser("clean", help="Clean the cache of this program") - kwargs = vars(parser.parse_args()) # Получаем все поля получившегося Namespace и пихаем в словарь - globals()[kwargs.pop("method")](**kwargs) # Из глобального контекста получаем функцию с названием как в method, заодно вытаскивая название метода из списка аргументов, + kwargs = vars(parser.parse_args()) # Получаем все поля получившегося Namespace и пихаем в словарь + globals()[kwargs.pop("method")](**kwargs) # Из глобального контекста получаем функцию с названием как в method, + # заодно вытаскивая название метода из списка аргументов, # затем вызываем функцию с распакованным словарём в качестве аргумента diff --git a/mcfs.py b/mcfs.py index 82ec8e3..ab746a1 100644 --- a/mcfs.py +++ b/mcfs.py @@ -1,6 +1,9 @@ import os from sys import platform import shutil +import zipfile +import json + def __get_mc_dir(): directory = "" @@ -11,17 +14,18 @@ def __get_mc_dir(): appdata = os.getenv('APPDATA') directory = appdata + r"\.minecraft" elif platform == "darwin": - directory = "~/Library/Application Support/minecraft" #unsure + directory = "~/Library/Application Support/minecraft" # unsure directory = os.getenv("MC_DIR", directory) return directory + def __get_cache_dir(): directory = "" if platform == 'win32': appdata_local = os.getenv("LOCALAPPDATA") directory = appdata_local + r"\mc-get" elif platform == 'darwin': - directory = '~/Library/Caches/mc-get' #unsure + directory = '~/Library/Caches/mc-get' # unsure elif platform == 'linux': cache = os.getenv("HOME") + "/.cache" directory = cache + "/mc-get" @@ -30,15 +34,40 @@ def __get_cache_dir(): directory = cache + "/mc-get" return directory -def is_path_exist(path:str): + +def get_modpack_info(filename: str): + with zipfile.ZipFile(os.path.join(cache_dir, filename)) as modpack: + with modpack.open("modrinth.index.json") as index: + return type("modpack_index", (object,), json.load(index)) + + +def install_modpacks_override(filename: str): + with zipfile.ZipFile(os.path.join(cache_dir, filename)) as modpack: + files = filter(lambda x: x.filename.startswith("overrides/"), modpack.infolist()) + for file in files: + if file.is_dir(): + continue + _to_path = os.path.join(mc_dir, file.filename[len("overrides/"):]) + os.makedirs(os.path.dirname(_to_path), exist_ok=True) + print(file.filename[len("overrides/"):]) + with modpack.open(file) as _from, open(_to_path, 'wb') as _to: + shutil.copyfileobj(_from, _to) + + +def is_path_exist(path: str): return os.path.exists(os.path.join(path)) -def is_standart_dir_structure(): - return not os.path.exists(os.path.join(directory, "home")) -def install(filename, subdir:str): - shutil.copy2(os.path.join(cache_dir,filename),\ - os.path.join(mc_dir,subdir,filename)) +def is_standard_dir_structure(): + return not os.path.exists(os.path.join(mc_dir, "home")) + + +def install(filename, subdir: str): + _from = os.path.join(cache_dir, filename) + _to = os.path.join(mc_dir, subdir, filename) + os.makedirs(os.path.dirname(_to), exist_ok=True) + shutil.copy2(_from, _to) + mc_dir = __get_mc_dir() cache_dir = __get_cache_dir()