Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,11 @@
- 서버 메모리 크기는 `ansible/roles/fabric_server/defaults/main.yml`의 `server_xms`, `server_xmx` 변수로 조정할 수 있습니다.
- 자세한 과정은 [Notion 문서](https://www.notion.so/MC-2241afe72e6980da8b2ac86e0bcf270e)를 참고하실 수 있습니다.

### Mods 관리 GUI
`scripts/mod_manager.py`를 실행하면 간단한 창이 열리며 Modrinth에서 모드를 검색하여 목록에 추가하거나 삭제할 수 있습니다. 실행 예시는 다음과 같습니다.
```bash
python3 scripts/mod_manager.py
```
새 모드를 추가할 때는 슬러그를 입력하면 현재 Minecraft 버전에 맞는 최신 파일을 자동으로 다운로드해 `ansible/vars/mods.yml`에 등록합니다.


125 changes: 125 additions & 0 deletions scripts/mod_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Simple GUI to manage Minecraft mods list using Modrinth API."""

from __future__ import annotations

import hashlib
import os
import tkinter as tk
from tkinter import messagebox, simpledialog
from typing import Dict

import requests
import yaml

# File paths relative to this script
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MODS_FILE = os.path.join(REPO_ROOT, "ansible", "vars", "mods.yml")
VERSIONS_FILE = os.path.join(REPO_ROOT, "ansible", "vars", "versions.yml")


def load_yaml(path: str) -> Dict:
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}


def save_yaml(path: str, data: Dict) -> None:
with open(path, "w", encoding="utf-8") as f:
yaml.safe_dump(data, f, allow_unicode=True, sort_keys=False)


def get_mc_version() -> str:
versions = load_yaml(VERSIONS_FILE)
return versions.get("afabric_mc_version", "")


def fetch_modrinth_latest(slug: str, mc_version: str) -> Dict[str, str]:
params = {
"loaders": ["fabric"],
"game_versions": [mc_version],
"limit": 1,
}
resp = requests.get(
f"https://api.modrinth.com/v2/project/{slug}/version",
params=params,
timeout=10,
)
resp.raise_for_status()
versions = resp.json()
if not versions:
raise RuntimeError("No compatible version found")
file_info = versions[0]["files"][0]
url = file_info["url"]
filename = file_info["filename"]
data = requests.get(url, timeout=10).content
checksum = hashlib.sha256(data).hexdigest()
return {"name": filename, "url": url, "checksum": f"sha256:{checksum}"}


def refresh_list(listbox: tk.Listbox, mods: Dict) -> None:
listbox.delete(0, tk.END)
for mod in mods.get("fabric_mods", []):
listbox.insert(tk.END, mod["name"])


def add_mod(root: tk.Tk, listbox: tk.Listbox, mods: Dict) -> None:
slug = simpledialog.askstring(
"Add Mod",
"Modrinth slug or project ID:",
parent=root,
)
if not slug:
return
try:
mc_ver = get_mc_version()
entry = fetch_modrinth_latest(slug, mc_ver)
except Exception as exc: # noqa: BLE001
messagebox.showerror("Error", str(exc), parent=root)
return
mods.setdefault("fabric_mods", []).append(entry)
save_yaml(MODS_FILE, mods)
refresh_list(listbox, mods)


def remove_mod(root: tk.Tk, listbox: tk.Listbox, mods: Dict) -> None:
sel = listbox.curselection()
if not sel:
return
idx = sel[0]
confirm = messagebox.askyesno(
"Remove",
f"Delete {mods['fabric_mods'][idx]['name']}?",
parent=root,
)
if confirm:
mods["fabric_mods"].pop(idx)
save_yaml(MODS_FILE, mods)
refresh_list(listbox, mods)


def main() -> None:
mods = load_yaml(MODS_FILE)
root = tk.Tk()
root.title("Minecraft Mod Manager")
listbox = tk.Listbox(root, width=80)
listbox.pack(fill=tk.BOTH, expand=True)
refresh_list(listbox, mods)

btn_frame = tk.Frame(root)
btn_frame.pack(fill=tk.X)
tk.Button(
btn_frame,
text="Add",
command=lambda: add_mod(root, listbox, mods),
).pack(side=tk.LEFT)
tk.Button(
btn_frame,
text="Remove",
command=lambda: remove_mod(root, listbox, mods),
).pack(side=tk.LEFT)

root.mainloop()


if __name__ == "__main__":
main()