Доработка взаимодействия с API, новая процедура установки, определение установленных версий игры и начальная реализация БД

This commit is contained in:
Евгений Титаренко 2023-07-21 18:05:44 +03:00
parent 983d0f9e21
commit 5beb835d1e
4 changed files with 217 additions and 51 deletions

26
api.py
View file

@ -14,21 +14,22 @@ def download(file_url: str, file_size: int, path: str):
file.write(data)
def __method(method: str, api_version: str = API_VERSION):
def __method(method: str, sub_method = "", api_version: str = API_VERSION):
api_url = "https://api.modrinth.com"
def request(**args):
sub_method = ""
slug = ""
if "project" in args:
sub_method = "/" + args.pop("project")
slug = "/" + args.pop("project")
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))
slug = "/" + args.pop("version")
# print(f"{api_url}/{api_version}{method}{slug}{sub_method}")
#print(args)
resp = requests.get(f"{api_url}/{api_version}{method}{slug}{sub_method}", params=args, headers=HEADERS)
# print(resp.headers.get("X-Ratelimit-Remaining", -1))
match resp.status_code:
case 200:
print("200: OK")
# print("200: OK")
if type(resp.json()) == list:
return [type(method, (object,), obj) for obj in resp.json()]
return type(method, (object,), resp.json())
@ -40,10 +41,10 @@ def __method(method: str, api_version: str = API_VERSION):
print("401: ERROR")
case 404:
'''
The requested project was not found or
no authorization to see this project
The requested project was not found or no authorization to see this project
'''
print("404: ERROR")
# print("404: ERROR")
return None
resp.raise_for_status()
return request
@ -51,5 +52,6 @@ def __method(method: str, api_version: str = API_VERSION):
test = __method("", "")
search = __method("/search")
project = __method("/project")
check_project = __method("/project", sub_method="/check")
version = __method("/version")
versions = __method("/versions")
get_versions = __method("/project", sub_method="/version")

130
mc-get.py
View file

@ -2,31 +2,16 @@
import argparse
import api
import mcfs
import npyscreen
import os
import mcgetdb
from colorama import Fore, Style
db = mcgetdb.McGetDB(mcfs.mc_dir)
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)
for ver in vers[::-1]], scroll_exit=True)
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 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):
@ -51,28 +36,72 @@ def modpack_install(filename: str):
mcfs.install_modpacks_override(filename)
def install(projects: list):
def install(projects: list, version, loader):
to_install = []
dependencies_to_install = []
not_found = []
unavailable = []
projects_ids = []
def check_project(slug):
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)
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))
versions = api.get_versions(project=project, loaders=f'["{loader}"]',
game_versions=f'["{mc_ver}"]')
if versions:
to_install.append((project_data, versions[0]))
dependency_solver(versions[0])
else:
unavailable.append(project_data)
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")
choose = input("Continue? [y/n]")
if choose.strip().lower() in ["n", "no"]:
print("Canceled.")
return
for project, version in to_install + dependencies_to_install:
file = type("mc_file", (object,), version.files[0])
filename = file.url.split("/")[-1]
filename = project.slug + ".jar"
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:
case "resourcepack":
subdir = "resourcepacks"
@ -114,6 +143,39 @@ def clean():
if __name__ == "__main__":
def __select_version(versions):
if len(versions) > 0:
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.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()
print(properties)
_, mc_ver, loader, _ = properties
desc = "Minecraft mods packet manager based on Modrinth API"
parser = argparse.ArgumentParser(description=desc,
formatter_class=argparse.RawTextHelpFormatter)
@ -122,6 +184,8 @@ if __name__ == "__main__":
parser_install = subparsers.add_parser("install", help="Install one or more mods or resources")
parser_install.add_argument("projects", nargs="+")
parser_install.add_argument("--version", 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="+")

59
mcfs.py
View file

@ -5,6 +5,23 @@ import zipfile
import json
class MCVersion:
def __init__(self, version_number, is_modified=False, modloader=None):
self.version_number = version_number
self.is_modified = is_modified
self.modloader = modloader
def __lt__(self, other):
own_nums = map(int, self.version_number.split("."))
other_nums = map(int, other.version_number.split("."))
for own_num, other_num in zip(own_nums, other_nums):
if own_num < other_num:
return True
if own_num > other_num:
return False
return False
def __get_mc_dir():
directory = ""
if platform == 'linux':
@ -35,6 +52,14 @@ def __get_cache_dir():
return directory
def __version_sort(mc_ver: MCVersion) -> str:
return mc_ver.version_number
mc_dir = __get_mc_dir()
cache_dir = __get_cache_dir()
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:
@ -58,10 +83,6 @@ def is_path_exist(path: str):
return os.path.exists(os.path.join(path))
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)
@ -69,5 +90,31 @@ def install(filename, subdir: str):
shutil.copy2(_from, _to)
mc_dir = __get_mc_dir()
cache_dir = __get_cache_dir()
def get_installed_mc_versions():
mc_vers_dir = os.path.join(mc_dir, "versions")
if not is_path_exist(mc_vers_dir):
return # TODO: Выброс ошибки
versions_dirs = next(os.walk(mc_vers_dir))[1]
versions = []
for version_dir in versions_dirs:
version_json_file = os.path.join(mc_vers_dir, version_dir, version_dir + ".json")
if not is_path_exist(version_json_file):
continue
with open(version_json_file) as json_file:
version_json = json.load(json_file)
mc_ver = None
match version_json.get("type", None):
case "modified":
version_number = version_json["jar"] # TODO: ИСПОЛЬЗОВАТЬ get ВМЕСТО []
is_modified = True
modloader = version_json["id"].split()[0].lower()
mc_ver = MCVersion(version_number, is_modified, modloader)
case "release": # TODO: Добавить поддержку других значений
version_number = version_json["id"]
mc_ver = MCVersion(version_number)
case None:
pass
case _: # TODO: Throw some errors
pass
versions.append(mc_ver)
return sorted(versions, reverse=True)

53
mcgetdb.py Normal file
View file

@ -0,0 +1,53 @@
import sqlite3
import os
DB_VERSION = 1
class McGetDB:
def __init_db(self):
with self.db as db:
db.execute('''
CREATE TABLE IF NOT EXISTS mods (
slug TEXT PRIMARY KEY NOT NULL,
install_path TEXT NOT NULL,
version TEXT NOT NULL
);''')
db.execute('''
CREATE TABLE IF NOT EXISTS properties (
db_version TEXT PRIMARY KEY NOT NULL,
version TEXT NOT NULL,
modloader TEXT,
dir_hierarchy TEXT
);''')
def __init__(self, mc_path):
self.db_path = os.path.join(mc_path, "mcget.db")
self.db = sqlite3.connect(self.db_path)
self.__init_db()
def get_properties(self):
properties = None
with self.db as db:
properties = db.execute('''
SELECT * FROM properties;
''').fetchone()
return properties
def set_properties(self, mc_ver, modloader = "NULL", dir_hierarhy = "default"):
with self.db as db:
db.execute('''
DELETE FROM properties;
''')
db.execute('''
INSERT INTO properties VALUES (?, ?, ?, ?)
''', (DB_VERSION, mc_ver, modloader, dir_hierarhy))
def add_mod(self):
pass
def remove_mod(self):
pass
def update_mod(self):
pass