Initial modpack installation implementation

This commit is contained in:
Евгений Титаренко 2023-01-10 22:55:42 +03:00
parent 1e923dacb9
commit 983d0f9e21
4 changed files with 117 additions and 52 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
__pycache__/ __pycache__/
*.swp *.swp
.idea

28
api.py
View file

@ -1,22 +1,22 @@
#from objects.api-objects import *
import requests import requests
from tqdm import tqdm from tqdm import tqdm
import json
API_VERSION = "v2" API_VERSION = "v2"
HEADERS = { HEADERS = {
'User-Agent': 'mc-get-testing' '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) resp = requests.get(file_url, stream=True, headers=HEADERS)
with open(path,'wb') as file: with open(path, 'wb') as file:
for data in tqdm(resp.iter_content(), total=file_size,\ for data in tqdm(resp.iter_content(), total=file_size, unit_scale=True, unit="byte"):
unit_scale=True, unit="byte"):
file.write(data) 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" api_url = "https://api.modrinth.com"
def request(**args): def request(**args):
sub_method = "" sub_method = ""
if "project" in args: if "project" in args:
@ -24,21 +24,19 @@ def __method(method:str, api_version:str=API_VERSION):
elif "version" in args: elif "version" in args:
sub_method = "/" + args.pop("version") sub_method = "/" + args.pop("version")
print(f"{api_url}/{api_version}{method}{sub_method}") print(f"{api_url}/{api_version}{method}{sub_method}")
resp = requests.get(f"{api_url}/{api_version}{method}{sub_method}",\ resp = requests.get(f"{api_url}/{api_version}{method}{sub_method}", params=args, headers=HEADERS)
params=args, headers=HEADERS) print(resp.headers.get("X-Ratelimit-Remaining", -1))
print(resp.headers.get("X-Ratelimit-Remaining",-1))
match resp.status_code: match resp.status_code:
case 200: case 200:
print("200: OK") print("200: OK")
if type(resp.json()) == list: if type(resp.json()) == list:
return [type(method, (object,), obj)\ return [type(method, (object,), obj) for obj in resp.json()]
for obj in resp.json()]
return type(method, (object,), resp.json()) return type(method, (object,), resp.json())
case 400: case 400:
#invalid request # Invalid request
print("400: ERROR") print("400: ERROR")
case 401: case 401:
# No autorization # No authorization
print("401: ERROR") print("401: ERROR")
case 404: case 404:
''' '''
@ -49,9 +47,9 @@ def __method(method:str, api_version:str=API_VERSION):
resp.raise_for_status() resp.raise_for_status()
return request return request
test = __method("", "") test = __method("", "")
search = __method("/search") search = __method("/search")
project = __method("/project") project = __method("/project")
version = __method("/version") version = __method("/version")
versions = __method("/versions") versions = __method("/versions")

View file

@ -6,41 +6,71 @@ import npyscreen
import os import os
from colorama import Fore, Style 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): def form(*args):
F = npyscreen.Form(name=f"Select {project} version") f = npyscreen.Form(name=f"Select {project} version")
sel = F.add(npyscreen.TitleSelectOne, value=[0,], name="versions:",\ sel = f.add(npyscreen.TitleSelectOne, value=[0, ], name="versions:",
values=[ver.version_number + " for " +\ values=[ver.version_number + " for " +
", ".join(ver.game_versions)\ ", ".join(ver.game_versions)
for ver in vers[::-1]], scroll_exit=True) for ver in vers[::-1]], scroll_exit=True)
F.edit() f.edit()
for ver in vers: for ver in vers:
if ver.version_number == sel.get_selected_objects()[0].split()[0]: if ver.version_number == sel.get_selected_objects()[0].split()[0]:
return ver return ver
return vers[0] return vers[0]
return form 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 = [] to_install = []
for project in projects: for project in projects:
project_data = api.project(project=project) project_data = api.project(project=project)
to_install.append(project_data) to_install.append(project_data)
for project in to_install: for project in to_install:
versions = api.versions(ids=str(project.versions).replace("'", '"')) #i hate this versions = api.versions(ids=str(project.versions).replace("'", '"')) # I hate this
version = npyscreen.wrapper_basic(version_selector_GUI(versions,\ version = npyscreen.wrapper_basic(version_selector_gui(versions,
project.slug)) project.slug))
file = type("mc_file", (object, ), version.files[0])
file = type("mc_file", (object,), version.files[0])
filename = file.url.split("/")[-1] filename = file.url.split("/")[-1]
cache_file_path = os.path.join(mcfs.cache_dir, filename) download_cache(file.url, filename, file.size)
if not mcfs.is_path_exist(mcfs.cache_dir): # cache_file_path = os.path.join(mcfs.cache_dir, filename)
os.mkdir(mcfs.cache_dir) # if not mcfs.is_path_exist(mcfs.cache_dir):
if not mcfs.is_path_exist(cache_file_path): # os.mkdir(mcfs.cache_dir)
api.download(file.url, file.size, cache_file_path) # if not mcfs.is_path_exist(cache_file_path):
else: # api.download(file.url, file.size, cache_file_path)
print(f"{filename} is in cache.") # else:
# print(f"{filename} is in cache.")
subdir = "" subdir = ""
match project.project_type: match project.project_type:
@ -50,22 +80,26 @@ def install(projects:list):
subdir = "mods" subdir = "mods"
case "shader": case "shader":
subdir = "shaderpacks" subdir = "shaderpacks"
case "modpack":
modpack_install(filename)
continue
case _: case _:
raise NotImplementedError raise NotImplementedError
mcfs.install(filename, subdir) 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: for result in results.hits:
print(Fore.GREEN, end="") print(Fore.GREEN, end="")
print(result.get("slug", "error"), end="") print(result.get("slug", "error"), end="")
print(Style.RESET_ALL, end="") print(Style.RESET_ALL, end="")
print(f' [{result.get("project_type", "error")}] ', 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(): def clean():
if mcfs.is_path_exist(mcfs.cache_dir): if mcfs.is_path_exist(mcfs.cache_dir):
@ -78,11 +112,13 @@ def clean():
print("Nothing to clear.") print("Nothing to clear.")
if __name__ == "__main__": if __name__ == "__main__":
desc = "Minecraft mods packet manager based on Modrinth API" desc = "Minecraft mods packet manager based on Modrinth API"
parser = argparse.ArgumentParser(description=desc,\ parser = argparse.ArgumentParser(description=desc,
formatter_class=argparse.RawTextHelpFormatter) formatter_class=argparse.RawTextHelpFormatter)
subparsers = parser.add_subparsers(dest="method", required=True) # Переменная, в которую будет записано имя подкоманды subparsers = parser.add_subparsers(dest="method",
required=True) # Переменная, в которую будет записано имя подкоманды
parser_install = subparsers.add_parser("install", help="Install one or more mods or resources") parser_install = subparsers.add_parser("install", help="Install one or more mods or resources")
parser_install.add_argument("projects", nargs="+") 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") parser_clean = subparsers.add_parser("clean", help="Clean the cache of this program")
kwargs = vars(parser.parse_args()) # Получаем все поля получившегося Namespace и пихаем в словарь kwargs = vars(parser.parse_args()) # Получаем все поля получившегося Namespace и пихаем в словарь
globals()[kwargs.pop("method")](**kwargs) # Из глобального контекста получаем функцию с названием как в method, заодно вытаскивая название метода из списка аргументов, globals()[kwargs.pop("method")](**kwargs) # Из глобального контекста получаем функцию с названием как в method,
# заодно вытаскивая название метода из списка аргументов,
# затем вызываем функцию с распакованным словарём в качестве аргумента # затем вызываем функцию с распакованным словарём в качестве аргумента

45
mcfs.py
View file

@ -1,6 +1,9 @@
import os import os
from sys import platform from sys import platform
import shutil import shutil
import zipfile
import json
def __get_mc_dir(): def __get_mc_dir():
directory = "" directory = ""
@ -11,17 +14,18 @@ def __get_mc_dir():
appdata = os.getenv('APPDATA') appdata = os.getenv('APPDATA')
directory = appdata + r"\.minecraft" directory = appdata + r"\.minecraft"
elif platform == "darwin": elif platform == "darwin":
directory = "~/Library/Application Support/minecraft" #unsure directory = "~/Library/Application Support/minecraft" # unsure
directory = os.getenv("MC_DIR", directory) directory = os.getenv("MC_DIR", directory)
return directory return directory
def __get_cache_dir(): def __get_cache_dir():
directory = "" directory = ""
if platform == 'win32': if platform == 'win32':
appdata_local = os.getenv("LOCALAPPDATA") appdata_local = os.getenv("LOCALAPPDATA")
directory = appdata_local + r"\mc-get" directory = appdata_local + r"\mc-get"
elif platform == 'darwin': elif platform == 'darwin':
directory = '~/Library/Caches/mc-get' #unsure directory = '~/Library/Caches/mc-get' # unsure
elif platform == 'linux': elif platform == 'linux':
cache = os.getenv("HOME") + "/.cache" cache = os.getenv("HOME") + "/.cache"
directory = cache + "/mc-get" directory = cache + "/mc-get"
@ -30,15 +34,40 @@ def __get_cache_dir():
directory = cache + "/mc-get" directory = cache + "/mc-get"
return directory 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)) 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): def is_standard_dir_structure():
shutil.copy2(os.path.join(cache_dir,filename),\ return not os.path.exists(os.path.join(mc_dir, "home"))
os.path.join(mc_dir,subdir,filename))
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() mc_dir = __get_mc_dir()
cache_dir = __get_cache_dir() cache_dir = __get_cache_dir()