329 lines
13 KiB
Python
Executable file
329 lines
13 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
import argparse
|
||
import api
|
||
import mcfs
|
||
import os
|
||
import mcgetdb
|
||
from colorama import Fore, Style
|
||
|
||
|
||
class HashError(ValueError):
|
||
"""Incorrect hash"""
|
||
|
||
|
||
def validate():
|
||
raise NotImplementedError
|
||
|
||
|
||
def download_cache(url: str, filename: str, size: int, hash: str):
|
||
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.")
|
||
if not mcfs.check_file_hash(cache_file_path, hash):
|
||
os.remove(cache_file_path)
|
||
raise HashError(f"Incorrect hash for {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"], file["hashes"]["sha512"])
|
||
mcfs.install(path[1], path[0])
|
||
print(f"{Fore.YELLOW}Overriding...{Style.RESET_ALL}")
|
||
mcfs.install_modpacks_override(filename)
|
||
|
||
|
||
def install(projects: list, mc_ver, loader):
|
||
to_install = []
|
||
dependencies_to_install = []
|
||
not_found = []
|
||
unavailable = []
|
||
projects_ids = []
|
||
already_installed = []
|
||
|
||
def get_project_info_from_db(slug):
|
||
_db = mcgetdb.McGetDB(mcfs.mc_dir)
|
||
return _db.select_mod(slug) + _db.select_shader(slug) + _db.select_resourcepack(slug) + _db.select_modpack(slug)
|
||
|
||
def check_project(slug):
|
||
if __name__ == "__main__":
|
||
mod_info = get_project_info_from_db(slug)
|
||
if mod_info:
|
||
already_installed.append(mod_info[0])
|
||
return
|
||
id = api.check_project(project=slug)
|
||
if id:
|
||
projects_ids.append(id.id)
|
||
else:
|
||
not_found.append(slug)
|
||
|
||
def dependency_solver(ver):
|
||
for dependency in ver.dependencies:
|
||
if dependency["dependency_type"] in ["optional", "embedded"]:
|
||
continue
|
||
if dependency["version_id"]:
|
||
dep_ver = api.version(version=dependency["version_id"])
|
||
else:
|
||
dep_ver = api.get_versions(project=dependency["project_id"], loaders=f'["{loader}"]',
|
||
game_versions=f'["{mc_ver}"]')[0]
|
||
if dependency["project_id"]:
|
||
proj_id = dependency["project_id"]
|
||
else:
|
||
proj_id = dep_ver.project_id
|
||
if proj_id in projects_ids or proj_id in [dep_proj.id for dep_proj, _ in dependencies_to_install]:
|
||
continue
|
||
project_data = api.project(project=proj_id)
|
||
dependencies_to_install.append((project_data, dep_ver))
|
||
dependency_solver(dep_ver)
|
||
|
||
for project in projects:
|
||
check_project(project)
|
||
|
||
for project in projects_ids:
|
||
project_data = api.project(project=project)
|
||
match project_data.project_type:
|
||
case "resourcepack":
|
||
versions = api.get_versions(project=project, game_versions=f'["{mc_ver}"]')
|
||
case "mod" | "modpack":
|
||
versions = api.get_versions(project=project, loaders=f'["{loader}"]', game_versions=f'["{mc_ver}"]')
|
||
case "shader": # TODO: Реализовать поддержку загрузчиков шейдеров
|
||
versions = api.get_versions(project=project, game_versions=f'["{mc_ver}"]')
|
||
case _:
|
||
raise NotImplementedError
|
||
|
||
if versions:
|
||
to_install.append((project_data, versions[0]))
|
||
dependency_solver(versions[0])
|
||
else:
|
||
unavailable.append(project_data)
|
||
if to_install:
|
||
print("To install:", *[project.title + " " + version.version_number for project, version in to_install],
|
||
sep="\n\t")
|
||
if dependencies_to_install:
|
||
print("With dependencies:",
|
||
*[project.title + " " + version.version_number for project, version in dependencies_to_install],
|
||
sep="\n\t")
|
||
if not_found:
|
||
print("Not found: ", end="")
|
||
print(*[project for project in not_found], sep=", ")
|
||
if unavailable:
|
||
print("Cannot be installed:", *[project.title for project in unavailable], sep="\n\t")
|
||
|
||
if already_installed:
|
||
print("Already installed:", *[name + " " + version for _, name, _, version, _ in already_installed], sep="\n\t")
|
||
|
||
all_to_install = to_install + dependencies_to_install
|
||
|
||
if not all_to_install:
|
||
return
|
||
|
||
choose = input("Continue? [y/n] ")
|
||
if choose.strip().lower() in ["n", "no"]:
|
||
print("Canceled.")
|
||
return
|
||
|
||
failed_to_install = []
|
||
|
||
for project, version in all_to_install:
|
||
file = type("mc_file", (object,), version.files[0])
|
||
filename = file.filename
|
||
hash = file.hashes["sha512"]
|
||
try:
|
||
download_cache(file.url, filename, file.size, hash)
|
||
except HashError:
|
||
print(f"Failed to install {project.title} ({project.slug}) [{version.version_number}] due to an incorrect "
|
||
f"hash")
|
||
failed_to_install.append((project, version))
|
||
continue
|
||
|
||
match project.project_type:
|
||
case "resourcepack":
|
||
subdir = "resourcepacks"
|
||
case "mod":
|
||
subdir = "mods"
|
||
case "shader":
|
||
subdir = "shaderpacks"
|
||
case "modpack":
|
||
modpack_install(filename)
|
||
subdir = "modpacks"
|
||
case _:
|
||
raise NotImplementedError
|
||
|
||
if __name__ == "__main__":
|
||
db = mcgetdb.McGetDB(mcfs.mc_dir)
|
||
match project.project_type:
|
||
case "resourcepack":
|
||
db.add_resourcepack(slug=project.slug, proj_name=project.title, filename=filename,
|
||
version=version.version_number, hash=hash)
|
||
case "mod":
|
||
db.add_mod(slug=project.slug, proj_name=project.title, filename=filename,
|
||
version=version.version_number, hash=hash)
|
||
case "shader":
|
||
db.add_shader(slug=project.slug, proj_name=project.title, filename=filename,
|
||
version=version.version_number, hash=hash)
|
||
case "modpack":
|
||
db.add_modpack(slug=project.slug, proj_name=project.title, filename=filename,
|
||
version=version.version_number, hash=hash)
|
||
case _:
|
||
raise NotImplementedError
|
||
mcfs.install(filename, subdir)
|
||
if failed_to_install:
|
||
print("Failed to install:",
|
||
*[project.title + " " + version.version_number for project, version in failed_to_install], sep="\n\t")
|
||
|
||
|
||
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")}')
|
||
|
||
|
||
def clean():
|
||
if mcfs.is_path_exist(mcfs.cache_dir):
|
||
files = os.listdir(mcfs.cache_dir)
|
||
if len(files) > 0:
|
||
for file in files:
|
||
os.remove(os.path.join(mcfs.cache_dir, file))
|
||
print("Cache cleared successfully.")
|
||
return
|
||
|
||
print("Nothing to clear.")
|
||
|
||
|
||
def remove(projects): # TODO: 1. Проверка зависимостей? 2. Модпаки
|
||
if __name__ == "__main__":
|
||
db = mcgetdb.McGetDB(mcfs.mc_dir)
|
||
|
||
mods_to_remove = []
|
||
resources_to_remove = []
|
||
shaders_to_remove = []
|
||
modpacks_to_remove = []
|
||
|
||
for project in projects:
|
||
for res in db.select_mod(project):
|
||
mods_to_remove.append(res)
|
||
for res in db.select_resourcepack(project):
|
||
resources_to_remove.append(res)
|
||
for res in db.select_shader(project):
|
||
shaders_to_remove.append(res)
|
||
for res in db.select_modpack(project):
|
||
modpacks_to_remove.append(res)
|
||
|
||
for slug, title, filename, version, _ in mods_to_remove:
|
||
local_path = os.path.join("mods", filename)
|
||
mcfs.remove(local_path)
|
||
db.remove_mod(slug)
|
||
print(f"Mod {title} v.{version} was removed.")
|
||
for _, title, filename, version, _ in resources_to_remove:
|
||
local_path = os.path.join("resourcepacks", filename)
|
||
mcfs.remove(local_path)
|
||
db.remove_resourcepack(slug)
|
||
print(f"Resource pack {title} v.{version} was removed.")
|
||
for _, title, filename, version, _ in shaders_to_remove:
|
||
local_path = os.path.join("shaderpacks", filename)
|
||
mcfs.remove(local_path)
|
||
db.remove_shader(slug)
|
||
print(f"Shader pack {title} v.{version} was removed.")
|
||
for _, title, filename, version, _ in modpacks_to_remove:
|
||
raise NotImplementedError
|
||
# local_path = os.path.join("modpacks", filename)
|
||
# mcfs.remove(local_path)
|
||
# db.remove_modpack(slug)
|
||
# print(f"Mod pack {title} v.{version} was removed.")
|
||
else:
|
||
raise NotImplementedError("Not available in module mode")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
|
||
def exit():
|
||
import sys
|
||
sys.exit("MC installation not found. If the program is not installed in the default location, "
|
||
"then specify the path to the installation through the MC_DIR environment variable.")
|
||
|
||
if not mcfs.is_path_exist(mcfs.mc_dir):
|
||
exit()
|
||
|
||
db = mcgetdb.McGetDB(mcfs.mc_dir)
|
||
|
||
|
||
def __select_version(versions):
|
||
if not versions:
|
||
return None
|
||
print("Installed MC versions: ")
|
||
for id, version in enumerate(versions):
|
||
print(id + 1,
|
||
version.version_number + (f" with {version.modloader}" if version.is_modified else ""),
|
||
sep=": ")
|
||
if len(versions) > 1:
|
||
id_to_use = -1
|
||
while id_to_use not in range(1, len(versions)):
|
||
id_input = input(f"Select MC version to use [1-{len(versions)}]: ")
|
||
if not id_input.isnumeric():
|
||
continue
|
||
id_to_use = int(id_input)
|
||
else:
|
||
id_to_use = 1
|
||
selected_version = versions[id_to_use - 1]
|
||
print("Selected MC version:", selected_version.version_number +
|
||
(f" with {selected_version.modloader}" if selected_version.is_modified else ""))
|
||
return selected_version
|
||
|
||
|
||
properties = db.get_properties()
|
||
if not properties:
|
||
version_to_use = __select_version(mcfs.get_installed_mc_versions())
|
||
if version_to_use:
|
||
if version_to_use.is_modified: # TODO: Добавить иерархию каталогов
|
||
db.set_properties(version_to_use.version_number, version_to_use.modloader)
|
||
else:
|
||
db.set_properties(version_to_use.version_number)
|
||
properties = db.get_properties()
|
||
else:
|
||
properties = None
|
||
if properties:
|
||
_, mc_ver, loader, _ = properties
|
||
else:
|
||
mc_ver, loader = None, None
|
||
|
||
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_install = subparsers.add_parser("install", help="Install one or more mods or resources")
|
||
parser_install.add_argument("projects", nargs="+")
|
||
parser_install.add_argument("--mc_ver", default=mc_ver)
|
||
parser_install.add_argument("--loader", default=loader)
|
||
|
||
parser_search = subparsers.add_parser("search", help="Find a mod or a resource")
|
||
parser_search.add_argument("query", nargs="+")
|
||
|
||
parser_validate = subparsers.add_parser("validate", help="Validate the installation")
|
||
|
||
parser_clean = subparsers.add_parser("clean", help="Clean the cache of this program")
|
||
|
||
parser_remove = subparsers.add_parser("remove", help="Remove installed packages")
|
||
parser_remove.add_argument("projects", nargs="+")
|
||
|
||
kwargs = vars(parser.parse_args()) # Получаем все поля получившегося Namespace и пихаем в словарь
|
||
if not properties:
|
||
exit()
|
||
globals()[kwargs.pop("method")](**kwargs) # Из глобального контекста получаем функцию с названием как в method,
|
||
# заодно вытаскивая название метода из списка аргументов,
|
||
# затем вызываем функцию с распакованным словарём в качестве аргумента
|