hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2025-12-09 21:55:02 +0100
committerhachem <im@hachem.wtf>2025-12-09 21:55:02 +0100
commit50e62e246fef7975866c244e08ead7c681d50349 (patch)
treeb863e4b818d5bfa4fbbdcb774351b094d323e3d1
parentee977e942c8deabde89f7979fc00850922062926 (diff)
[add]: prompt+banner+refactor
-rw-r--r--README.md89
-rw-r--r--bot/app.js63
-rw-r--r--config/config.json2
-rw-r--r--lib/config.js62
-rw-r--r--lib/modrinth.js69
-rw-r--r--package.json10
-rw-r--r--updater/cli.js393
-rw-r--r--updater/updater.py171
8 files changed, 611 insertions, 248 deletions
diff --git a/README.md b/README.md
index bdeebbd..95b68ed 100644
--- a/README.md
+++ b/README.md
@@ -1,43 +1,92 @@
# Benson
Mod Manager from hell, for Willy World
-## Running the Discord Bot
-1. DM me to get the Discord bot token.
-2. Place the token in `config/api.json` like this:
+## Setup
-```json
-{
- "token": "your-bot-token-here"
-}
+```bash
+npm install
```
-3. **Set the Discord channel ID**
+## Configuration
-Open `config/config.json` and update the `channel_id` field to the channel where the bot should post updates:
+### `config/config.json`
```json
{
"channel_id": "YOUR_CHANNEL_ID_HERE",
+ "check_interval_minutes": 60,
+ "minecraft_version": "1.21.10",
+ "fabric_loader_version": "0.17.3",
+ "loader_meta_version": "1.1.0",
"modrinth_mods": [
- "example-mod",
- "another-mod"
- ],
- // ...
+ "fabric-api",
+ "lithium",
+ "simple-voice-chat"
+ ]
+}
+```
+
+### `config/api.json` (for Discord bot)
+
+```json
+{
+ "token": "your-bot-token-here"
}
```
-If you don't set this, the bot **will not find the channel** and won't send any messages.
+## Discord Bot
-4. Start the bot:
+Monitors Modrinth for mod updates and posts compatibility reports to a Discord channel.
```bash
-npm i
-node .
+npm run bot
```
-## Running the Updater
-> Make sure to run this from the **project root**, **not** inside the `updater` folder.
+## Server Manager CLI
+
+Interactive CLI for managing mods and server files.
```bash
-python updater/updater.py
+npm run benson
+```
+
+```
+ ╔══════════════════════════════════════╗
+ ║ BENSON CLI v1.0 ║
+ ║ Fabric Mod Manager for MC 1.21.10 ║
+ ╚══════════════════════════════════════╝
+
+ Type 'help' for available commands.
+
+benson> _
+```
+
+### Commands
+
+| Command | Description |
+|---------|-------------|
+| `list-mods` | List configured mods |
+| `generate` | Download mods + server, run to generate files |
+| `merge` | (coming soon) |
+| `clean generated` | Remove generated server files |
+| `clean all` | (coming soon) |
+| `help` | Show available commands |
+| `exit` | Exit the CLI |
+
+## Project Structure
+
+```
+Benson/
+├── lib/
+│ ├── modrinth.js # Shared Modrinth API functions
+│ └── config.js # Shared config/cache utilities
+├── bot/
+│ └── app.js # Discord bot
+├── updater/
+│ └── cli.js # Interactive CLI
+├── config/
+│ ├── config.json # Main configuration
+│ └── api.json # Discord token (not in repo)
+├── cache/ # Version cache (auto-generated)
+└── server/ # Server files (auto-generated)
```
diff --git a/bot/app.js b/bot/app.js
index 3fc9134..59219eb 100644
--- a/bot/app.js
+++ b/bot/app.js
@@ -5,26 +5,22 @@ const
GatewayIntentBits
} = require("discord.js")
-const axios = require("axios")
-const fs = require("fs-extra")
-const path = require("path")
+const { LoadConfig, LoadApi, LoadCache, SaveCache } = require("../lib/config")
+const { GetModVersions, GetLatestVersion, GetCompatibleVersion } = require("../lib/modrinth")
-const config = require("../config/config.json")
-const api = require("../config/api.json")
+const config = LoadConfig()
+const api = LoadApi()
const
{
channel_id,
check_interval_minutes,
minecraft_version,
- fabric_loader_version,
- loader_meta_version,
modrinth_mods
} = config
const { token } = api
-const cacheFile = path.join(__dirname, "../cache/mod_versions.json")
const client = new Client
({
intents:
@@ -34,47 +30,6 @@ const client = new Client
]
})
-const LoadCache = async () =>
-{
- try
- {
- if (await fs.pathExists(cacheFile))
- return JSON.parse(await fs.readFile(cacheFile, "utf8"))
- }
- catch (err)
- {
- console.error("[Cache] Failed to load:", err)
- }
- return {}
-}
-
-const SaveCache = async cache =>
-{
- try
- {
- await fs.ensureDir(path.dirname(cacheFile))
- await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2))
- }
- catch (err)
- {
- console.error("[Cache] Failed to save:", err)
- }
-}
-
-const GetModVersions = async slug =>
-{
- try
- {
- const response = await axios.get(`https://api.modrinth.com/v2/project/${slug}/version`)
- return response.data
- }
- catch (err)
- {
- console.error(`[Modrinth] Failed for ${slug}:`, err.message)
- return null
- }
-}
-
const CheckMods = async () =>
{
const cache = await LoadCache()
@@ -90,8 +45,8 @@ const CheckMods = async () =>
if (!versions)
continue
- const latest = versions.sort((a, b) => new Date(b.date_published) - new Date(a.date_published))[0]
- const compatible = versions.find(v => v.game_versions.includes(minecraft_version)) || null
+ const latest = GetLatestVersion(versions)
+ const compatible = GetCompatibleVersion(versions, minecraft_version)
const cached = cache[slug] ||
{
@@ -102,7 +57,7 @@ const CheckMods = async () =>
newCache[slug] =
{
compatible: compatible ? compatible.id : null,
- latest: latest.id
+ latest: latest ? latest.id : null
}
if (!compatible)
@@ -117,7 +72,7 @@ const CheckMods = async () =>
continue
}
- if (latest.id !== cached.latest)
+ if (latest && latest.id !== cached.latest)
stillCompatibleUpdate.push(slug)
}
@@ -204,4 +159,4 @@ client.once(Events.ClientReady, async c =>
}, check_interval_minutes * 60 * 1000)
})
-client.login(token) \ No newline at end of file
+client.login(token)
diff --git a/config/config.json b/config/config.json
index 6db2f86..67011f9 100644
--- a/config/config.json
+++ b/config/config.json
@@ -4,6 +4,8 @@
"minecraft_version": "1.21.10",
"fabric_loader_version": "0.17.3",
"loader_meta_version": "1.1.0",
+ "java_path": "C:/Program Files/Java/jdk-25/bin/java.exe",
+
"modrinth_mods": [
"audioplayer",
"c2me-fabric",
diff --git a/lib/config.js b/lib/config.js
new file mode 100644
index 0000000..9d2ed39
--- /dev/null
+++ b/lib/config.js
@@ -0,0 +1,62 @@
+const path = require("path")
+const fs = require("fs-extra")
+
+const configPath = path.join(__dirname, "../config/config.json")
+const apiPath = path.join(__dirname, "../config/api.json")
+const cachePath = path.join(__dirname, "../cache/mod_versions.json")
+
+const LoadConfig = () =>
+{
+ return require(configPath)
+}
+
+const LoadApi = () =>
+{
+ try
+ {
+ return require(apiPath)
+ }
+ catch
+ {
+ return {}
+ }
+}
+
+const LoadCache = async () =>
+{
+ try
+ {
+ if (await fs.pathExists(cachePath))
+ return JSON.parse(await fs.readFile(cachePath, "utf8"))
+ }
+ catch (err)
+ {
+ console.error("[Cache] Failed to load:", err)
+ }
+ return {}
+}
+
+const SaveCache = async cache =>
+{
+ try
+ {
+ await fs.ensureDir(path.dirname(cachePath))
+ await fs.writeFile(cachePath, JSON.stringify(cache, null, 2))
+ }
+ catch (err)
+ {
+ console.error("[Cache] Failed to save:", err)
+ }
+}
+
+module.exports =
+{
+ configPath,
+ apiPath,
+ cachePath,
+ LoadConfig,
+ LoadApi,
+ LoadCache,
+ SaveCache
+}
+
diff --git a/lib/modrinth.js b/lib/modrinth.js
new file mode 100644
index 0000000..cf0f697
--- /dev/null
+++ b/lib/modrinth.js
@@ -0,0 +1,69 @@
+const axios = require("axios")
+
+const MODRINTH_API = "https://api.modrinth.com/v2"
+
+const GetModVersions = async slug =>
+{
+ try
+ {
+ const response = await axios.get(`${MODRINTH_API}/project/${slug}/version`)
+ return response.data
+ }
+ catch (err)
+ {
+ console.error(`[Modrinth] Failed for ${slug}:`, err.message)
+ return null
+ }
+}
+
+const GetLatestVersion = versions =>
+{
+ if (!versions || !versions.length)
+ return null
+ return versions.sort((a, b) => new Date(b.date_published) - new Date(a.date_published))[0]
+}
+
+const GetCompatibleVersion = (versions, minecraftVersion, loader = null) =>
+{
+ if (!versions || !versions.length)
+ return null
+
+ const compatible = versions.filter(v =>
+ {
+ const hasVersion = v.game_versions.includes(minecraftVersion)
+ const hasLoader = loader ? v.loaders.includes(loader) : true
+ return hasVersion && hasLoader
+ })
+
+ if (!compatible.length)
+ return null
+
+ return compatible.sort((a, b) => new Date(b.date_published) - new Date(a.date_published))[0]
+}
+
+const DownloadFile = async (url, destPath) =>
+{
+ const fs = require("fs-extra")
+
+ try
+ {
+ const response = await axios.get(url, { responseType: "arraybuffer" })
+ await fs.writeFile(destPath, response.data)
+ return true
+ }
+ catch (err)
+ {
+ console.error(`[Download] Failed:`, err.message)
+ return false
+ }
+}
+
+module.exports =
+{
+ MODRINTH_API,
+ GetModVersions,
+ GetLatestVersion,
+ GetCompatibleVersion,
+ DownloadFile
+}
+
diff --git a/package.json b/package.json
index f2a0b0c..560bb6a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "benson",
- "version": "1.0.0",
- "description": " Mod manager for Willy World",
+ "version": "1.1.0",
+ "description": "A server mod manager from hell.",
"homepage": "https://github.com/le-hachem/Benson#readme",
"bugs": {
"url": "https://github.com/le-hachem/Benson/issues"
@@ -15,11 +15,15 @@
"type": "commonjs",
"main": "bot/app.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "bot": "node bot/app.js",
+ "benson": "node updater/cli.js"
},
"dependencies": {
"axios": "^1.12.2",
"discord.js": "^14.23.2",
"fs-extra": "^11.3.2"
+ },
+ "bin": {
+ "benson": "./updater/cli.js"
}
}
diff --git a/updater/cli.js b/updater/cli.js
new file mode 100644
index 0000000..8731ab6
--- /dev/null
+++ b/updater/cli.js
@@ -0,0 +1,393 @@
+#!/usr/bin/env node
+
+const readline = require("readline")
+const fs = require("fs-extra")
+const path = require("path")
+const { spawn } = require("child_process")
+
+const { LoadConfig } = require("../lib/config")
+const { GetModVersions, GetCompatibleVersion, DownloadFile } = require("../lib/modrinth")
+
+const pkg = require("../package.json")
+const config = LoadConfig()
+
+const
+{
+ minecraft_version,
+ fabric_loader_version,
+ loader_meta_version,
+ java_path,
+ modrinth_mods
+} = config
+
+const javaCmd = java_path || "java"
+
+const serverDir = path.join(__dirname, "../server")
+const modsDir = path.join(serverDir, "mods")
+
+const deleteList =
+[
+ "audioplayer_uploads",
+ "config",
+ "cache",
+ "world",
+ "logs",
+ "banned-players.json",
+ "server.properties",
+ "banned-ips.json",
+ "usercache.json",
+ "whitelist.json",
+ "ops.json",
+ "eula.txt"
+]
+
+
+const EnsureDirs = async () =>
+{
+ await fs.ensureDir(serverDir)
+ await fs.ensureDir(modsDir)
+}
+
+const DownloadModrinthMods = async (modList, mcVersion) =>
+{
+ const downloadedFiles = []
+ const failedMods = []
+
+ for (const slug of modList)
+ {
+ try
+ {
+ const versions = await GetModVersions(slug)
+ if (!versions)
+ {
+ console.log(` [Modrinth] Could not get versions for ${slug}`)
+ failedMods.push(slug)
+ continue
+ }
+
+ const compatible = GetCompatibleVersion(versions, mcVersion, "fabric")
+ if (!compatible)
+ {
+ console.log(` [Modrinth] No Fabric version for ${slug} on MC ${mcVersion}`)
+ failedMods.push(slug)
+ continue
+ }
+
+ const fileInfo = compatible.files[0]
+ const jarUrl = fileInfo.url
+ const filename = fileInfo.filename
+ const destPath = path.join(modsDir, filename)
+
+ console.log(` [Modrinth] Downloading ${slug} -> ${filename} ...`)
+
+ const success = await DownloadFile(jarUrl, destPath)
+ if (!success)
+ {
+ console.log(` [Modrinth] Couldn't download jar for ${slug}`)
+ failedMods.push(slug)
+ continue
+ }
+
+ downloadedFiles.push(destPath)
+ console.log(` [Modrinth] Downloaded: ${filename}`)
+ }
+ catch (err)
+ {
+ console.error(` [Modrinth] Error downloading ${slug}:`, err.message)
+ failedMods.push(slug)
+ }
+ }
+
+ return { downloadedFiles, failedMods }
+}
+
+const DownloadFabricServer = async (mcVersion, loaderVersion, metaVersion) =>
+{
+ const url = `https://meta.fabricmc.net/v2/versions/loader/${mcVersion}/${loaderVersion}/${metaVersion}/server/jar`
+ const filename = "server.jar"
+ const destPath = path.join(serverDir, filename)
+
+ console.log(` [Fabric] Downloading Fabric server for MC ${mcVersion} ...`)
+
+ const success = await DownloadFile(url, destPath)
+ if (!success)
+ {
+ console.log(` [Fabric] Failed to download Fabric server JAR`)
+ return null
+ }
+
+ console.log(` [Fabric] Downloaded: ${filename}`)
+ return destPath
+}
+
+const RunServerAutoStop = (rl) =>
+{
+ return new Promise((resolve) =>
+ {
+ const jarPath = path.join(serverDir, "server.jar")
+
+ if (!fs.existsSync(jarPath))
+ {
+ console.log(" Server JAR not found.")
+ resolve(false)
+ return
+ }
+
+ const eulaPath = path.join(serverDir, "eula.txt")
+ if (!fs.existsSync(eulaPath))
+ {
+ console.log(" Accepting EULA...")
+ fs.writeFileSync(eulaPath, "eula=true\n")
+ }
+
+ console.log(" Starting server (will auto-stop after loading)...\n")
+
+ rl.pause()
+
+ let hadError = false
+
+ const serverProcess = spawn(javaCmd, ["-jar", "server.jar", "nogui"],
+ {
+ cwd: serverDir,
+ stdio: ["pipe", "pipe", "pipe"]
+ })
+
+ serverProcess.stdout.on("data", (data) =>
+ {
+ const line = data.toString()
+ process.stdout.write(line)
+
+ if (line.includes("Done"))
+ {
+ console.log("\n Server loaded, stopping...")
+ serverProcess.stdin.write("stop\n")
+ }
+ })
+
+ serverProcess.stderr.on("data", (data) =>
+ {
+ process.stderr.write(data.toString())
+ })
+
+ serverProcess.on("error", err =>
+ {
+ hadError = true
+ console.error(" Failed to start server:", err.message)
+ rl.resume()
+ resolve(false)
+ })
+
+ serverProcess.on("close", () =>
+ {
+ if (!hadError)
+ {
+ console.log("\n Server stopped.")
+ rl.resume()
+ resolve(true)
+ }
+ })
+ })
+}
+
+const DeletePostGenerate = async () =>
+{
+ console.log(" Cleaning up server-generated files...")
+
+ let removed = 0
+ for (const item of deleteList)
+ {
+ const itemPath = path.join(serverDir, item)
+
+ try
+ {
+ if (await fs.pathExists(itemPath))
+ {
+ await fs.remove(itemPath)
+ removed++
+ }
+ }
+ catch (err)
+ {
+ console.error(` Failed to remove ${item}:`, err.message)
+ }
+ }
+
+ console.log(` Removed ${removed} items.`)
+}
+
+const CleanGenerated = async () =>
+{
+ try
+ {
+ if (await fs.pathExists(serverDir))
+ {
+ await fs.remove(serverDir)
+ console.log(" Removed server directory.")
+ }
+ else
+ {
+ console.log(" Server directory doesn't exist.")
+ }
+ }
+ catch (err)
+ {
+ console.error(" Failed to remove server directory:", err.message)
+ }
+}
+
+const CleanAll = async () =>
+{
+ // Placeholder - will be implemented later
+ console.log(" clean all - not implemented yet")
+}
+
+const ListMods = () =>
+{
+ console.log(`\n Mods for MC ${minecraft_version}:\n`)
+ for (const slug of modrinth_mods)
+ console.log(` - ${slug}`)
+ console.log(`\n Total: ${modrinth_mods.length} mods\n`)
+}
+
+const Generate = async (rl) =>
+{
+ await EnsureDirs()
+
+ console.log(`\n Generating server for MC ${minecraft_version}...\n`)
+
+ const { downloadedFiles, failedMods } = await DownloadModrinthMods(modrinth_mods, minecraft_version)
+ console.log(`\n ✅ Downloaded ${downloadedFiles.length} mods`)
+ if (failedMods.length)
+ console.log(` ❌ Failed: ${failedMods.join(", ")}`)
+
+ const serverFile = await DownloadFabricServer(minecraft_version, fabric_loader_version, loader_meta_version)
+
+ if (serverFile)
+ {
+ console.log("")
+ await RunServerAutoStop(rl)
+ await DeletePostGenerate()
+ }
+
+ console.log("")
+}
+
+const Merge = async () =>
+{
+ // Placeholder - will be implemented later
+ console.log(" merge - not implemented yet")
+}
+
+const ShowHelp = () =>
+{
+ console.log(`
+ Commands:
+
+ list-mods List configured mods
+ generate Download mods + server, run to generate files
+ merge (coming soon)
+ clean generated Remove generated server files
+ clean all (coming soon)
+ help Show this help
+ exit Exit
+`)
+}
+
+const ProcessCommand = async (input, rl) =>
+{
+ const parts = input.trim().toLowerCase().split(/\s+/)
+ const cmd = parts[0]
+ const arg = parts[1]
+
+ switch (cmd)
+ {
+ case "list-mods":
+ ListMods()
+ break
+
+ case "generate":
+ await Generate(rl)
+ break
+
+ case "merge":
+ await Merge()
+ break
+
+ case "clean":
+ if (arg === "generated")
+ await CleanGenerated()
+ else if (arg === "all")
+ await CleanAll()
+ else
+ console.log(" Usage: clean generated | clean all")
+ break
+
+ case "help":
+ case "?":
+ ShowHelp()
+ break
+
+ case "exit":
+ case "quit":
+ case "q":
+ console.log(" Goodbye!\n")
+ process.exit(0)
+
+ case "":
+ break
+
+ default:
+ console.log(` Unknown command: ${cmd}. Type 'help' for commands.`)
+ }
+}
+
+const DrawBox = (lines) =>
+{
+ const maxLen = Math.max(...lines.map(l => l.length))
+ const pad = (str) => str + " ".repeat(maxLen - str.length)
+
+ const top = "╔" + "═".repeat(maxLen + 2) + "╗"
+ const bot = "╚" + "═".repeat(maxLen + 2) + "╝"
+ const mid = lines.map(l => "║ " + pad(l) + " ║").join("\n ")
+
+ return ` ${top}\n ${mid}\n ${bot}`
+}
+
+const Main = () =>
+{
+ const title = `BENSON CLI v${pkg.version}`
+ const subtitle = `Fabric Server for MC ${minecraft_version}`
+
+ const box = DrawBox([title, subtitle])
+
+ console.log(`
+${box}
+
+ Type 'help' for commands.
+`)
+
+ const rl = readline.createInterface
+ ({
+ input: process.stdin,
+ output: process.stdout
+ })
+
+ const Prompt = () =>
+ {
+ rl.question("benson> ", async (input) =>
+ {
+ await ProcessCommand(input, rl)
+ Prompt()
+ })
+ }
+
+ rl.on("close", () =>
+ {
+ console.log("\n Goodbye!\n")
+ process.exit(0)
+ })
+
+ Prompt()
+}
+
+Main()
diff --git a/updater/updater.py b/updater/updater.py
deleted file mode 100644
index 7f065eb..0000000
--- a/updater/updater.py
+++ /dev/null
@@ -1,171 +0,0 @@
-from typing import List
-import os
-import httpx
-import shutil
-import asyncio
-import subprocess
-import json
-
-server_dir = "server"
-mods_dir = f"{server_dir}/mods"
-os.makedirs(server_dir, exist_ok=True)
-os.makedirs(mods_dir, exist_ok=True)
-
-delete_list = [
- "audioplayer_uploads/",
- "config/",
- "cache/",
- "world/",
- "logs/",
-
- "banned-players.json",
- "server.properties",
- "banned-ips.json",
- "usercache.json",
- "whitelist.json",
- "ops.json",
- "eula.txt",
-]
-
-with open("config/config.json", "r") as f:
- config = json.load(f)
-
-mc_version = config.get("minecraft_version", "1.21.10")
-fabric_loader_version = config.get("fabric_loader_version", "0.17.3")
-loader_meta_version = config.get("loader_meta_version", "1.1.0")
-modrinth_mods = config.get("modrinth_mods", [])
-
-async def download_modrinth(mod_list: List[str], mc_ver: str):
- downloaded_files = []
- failed_mods = []
- async with httpx.AsyncClient(timeout=60) as client:
- for slug in mod_list:
- try:
- versions_url = f"https://api.modrinth.com/v2/project/{slug}/version"
- resp = await client.get(versions_url)
- if resp.status_code != 200:
- print(f"[Modrinth] Could not get versions for {slug}")
- failed_mods.append(slug)
- continue
-
- versions = resp.json()
- compatible_versions = [
- v for v in versions
- if "fabric" in v.get("loaders", []) and mc_ver in v.get("game_versions", [])
- ]
-
- if not compatible_versions:
- print(f"[Modrinth] No Fabric versions found for {slug} on MC {mc_ver}")
- failed_mods.append(slug)
- continue
-
- latest = max(compatible_versions, key=lambda v: v["date_published"])
- file_info = latest.get("files", [])[0]
- jar_url = file_info["url"]
- filename = file_info["filename"]
-
- print(f"[Modrinth] Downloading {slug} -> {filename} ...")
- jar_resp = await client.get(jar_url)
- if jar_resp.status_code != 200:
- print(f"[Modrinth] Couldn't download jar for {slug}")
- failed_mods.append(slug)
- continue
-
- path = os.path.join(mods_dir, filename)
- with open(path, "wb") as f:
- f.write(jar_resp.content)
-
- downloaded_files.append(path)
- print(f"[Modrinth] Successfully downloaded: {filename}")
-
- except Exception as e:
- print(f"[Modrinth] Error downloading {slug}: {e}")
- failed_mods.append(slug)
-
- return downloaded_files, failed_mods
-
-async def download_fabric_server(mc_ver: str, loader_ver: str, meta_ver: str):
- url = f"https://meta.fabricmc.net/v2/versions/loader/{mc_ver}/{loader_ver}/{meta_ver}/server/jar"
- filename = "server.jar"
- path = os.path.join(server_dir, filename)
-
- async with httpx.AsyncClient(timeout=60) as client:
- try:
- print(f"[Fabric] Downloading Fabric server for MC {mc_ver} ...")
- resp = await client.get(url)
- if resp.status_code != 200:
- print(f"[Fabric] Failed to download Fabric server JAR: {resp.status_code}")
- return None
-
- with open(path, "wb") as f:
- f.write(resp.content)
-
- print(f"[Fabric] Successfully downloaded Fabric server: {filename}")
- return path
- except Exception as e:
- print(f"[Fabric] Error downloading Fabric server: {e}")
- return None
-
-async def run_fabric_server():
- jar_path = os.path.join(server_dir, "server.jar")
-
- if not os.path.exists(jar_path):
- print("[Server] Server JAR not found, cannot start server.")
- return
-
- eula_path = os.path.join(server_dir, "eula.txt")
- if not os.path.exists(eula_path):
- print("[Server] Accepting EULA automatically...")
- with open(eula_path, "w") as f:
- f.write("eula=true\n")
-
- print(f"[Server] Starting Fabric server from {jar_path} ...")
-
- process = subprocess.Popen(
- ["java", "-jar", "server.jar", "nogui"],
- cwd=server_dir
- )
- process.wait()
- print("[Server] Fabric server stopped.")
-
-def cleanup_server(delete_list: List[str]):
- print("[Cleanup] Cleaning up server files...")
- for item in delete_list:
- path = os.path.join(server_dir, item)
- if os.path.exists(path):
- try:
- if os.path.isdir(path):
- shutil.rmtree(path)
- print(f"[Cleanup] Removed folder: {item}")
- else:
- os.remove(path)
- print(f"[Cleanup] Removed file: {item}")
- except Exception as e:
- print(f"[Cleanup] Failed to remove {item}: {e}")
- else:
- print(f"[Cleanup] {item} does not exist, skipping.")
- print("[Cleanup] Cleanup complete.")
-
-async def main():
- print(f"Starting Benson Mod Updater for Fabric MC {mc_version}...\n")
- files, failed_mods = await download_modrinth(modrinth_mods, mc_version)
- server_file = await download_fabric_server(mc_version, fabric_loader_version, loader_meta_version)
-
- print("\nAll downloads complete!")
- print(f"Downloaded {len(files)} mods:")
- for f in files:
- print(f" - {f}")
-
- if failed_mods:
- print("\nThe following mods failed to download:")
- for mod in failed_mods:
- print(f" - {mod}")
- else:
- print("\nAll mods downloaded successfully!")
-
- if server_file:
- await run_fabric_server()
- cleanup_server(delete_list)
-
-if __name__ == "__main__":
- asyncio.run(main())