mc-get/mc-get.py

329 lines
13 KiB
Python
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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,
# заодно вытаскивая название метода из списка аргументов,
# затем вызываем функцию с распакованным словарём в качестве аргумента