mirror of
https://github.com/ceratic/project_vollidioten_mod.git
synced 2026-05-14 00:16:47 +02:00
implement GitHub resource pack auto-updater with HTTP server and configuration
This commit is contained in:
46
README.md
46
README.md
@@ -1,10 +1,11 @@
|
|||||||
# Projectvollidioten
|
# Projectvollidioten
|
||||||
|
|
||||||
A Minecraft Fabric mod that logs player data on join and leave events to an external API.
|
A Minecraft Fabric mod that provides comprehensive player data logging and automatic resource pack management via GitHub integration.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Logs player join and leave events
|
### Player Data Logging
|
||||||
|
- Logs player join and leave events to external API
|
||||||
- Captures detailed player statistics including:
|
- Captures detailed player statistics including:
|
||||||
- Health and max health
|
- Health and max health
|
||||||
- Hunger level
|
- Hunger level
|
||||||
@@ -15,6 +16,15 @@ A Minecraft Fabric mod that logs player data on join and leave events to an exte
|
|||||||
- Asynchronous API communication to prevent server lag
|
- Asynchronous API communication to prevent server lag
|
||||||
- Pretty-printed JSON logging for debugging
|
- Pretty-printed JSON logging for debugging
|
||||||
|
|
||||||
|
### Automatic Resource Pack Updates
|
||||||
|
- **GitHub Integration**: Automatically monitors GitHub releases for new resource packs
|
||||||
|
- **HTTP Server**: Built-in HTTP server (port 25585) to serve resource packs locally
|
||||||
|
- **Smart Updates**: Downloads and installs new resource pack versions automatically
|
||||||
|
- **Server Configuration**: Automatically updates `server.properties` with correct URLs and SHA1 hashes
|
||||||
|
- **Player Notifications**: Broadcasts resource pack availability to all players
|
||||||
|
- **Periodic Checking**: Configurable interval checking (default: every 60 minutes)
|
||||||
|
- **Secure Serving**: Only serves configured resource pack files for security
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Minecraft 1.21.11
|
- Minecraft 1.21.11
|
||||||
@@ -30,10 +40,42 @@ A Minecraft Fabric mod that logs player data on join and leave events to an exte
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
### Player Data Logging
|
||||||
The mod sends data to a hardcoded API endpoint: `http://localhost:3000/api/data`
|
The mod sends data to a hardcoded API endpoint: `http://localhost:3000/api/data`
|
||||||
|
|
||||||
To change the API endpoint, modify the `API_URL` constant in `PlayerDataLogger.java`.
|
To change the API endpoint, modify the `API_URL` constant in `PlayerDataLogger.java`.
|
||||||
|
|
||||||
|
### Resource Pack Auto-Updater
|
||||||
|
The resource pack updater is configured via `config/projectvollidioten/resourcepack.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"githubOwner": "your-username",
|
||||||
|
"githubRepo": "your-repo-name",
|
||||||
|
"resourcePackFileName": "my-pack.zip",
|
||||||
|
"checkIntervalMinutes": 60
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Options:**
|
||||||
|
- `githubOwner`: Your GitHub username or organization name
|
||||||
|
- `githubRepo`: The repository name containing the resource pack releases
|
||||||
|
- `resourcePackFileName`: The exact filename of the resource pack ZIP in GitHub releases
|
||||||
|
- `checkIntervalMinutes`: How often to check for updates (default: 60 minutes)
|
||||||
|
|
||||||
|
**Setup Instructions:**
|
||||||
|
1. Create a GitHub repository for your resource packs
|
||||||
|
2. Upload resource pack ZIP files as release assets
|
||||||
|
3. Update the configuration file with your repository details
|
||||||
|
4. The mod will automatically download and serve new versions
|
||||||
|
5. Players will be notified when new packs are available
|
||||||
|
|
||||||
|
**Important Notes:**
|
||||||
|
- The built-in HTTP server runs on port 25585 by default
|
||||||
|
- Resource packs are stored in `server-resource-packs/` directory
|
||||||
|
- Only the configured filename can be served for security
|
||||||
|
- SHA1 hashes are automatically computed and updated in server.properties
|
||||||
|
|
||||||
## API Format
|
## API Format
|
||||||
|
|
||||||
The mod sends POST requests with JSON payloads in the following format:
|
The mod sends POST requests with JSON payloads in the following format:
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
package ceratic.projectvollidioten;
|
||||||
|
|
||||||
|
import ceratic.projectvollidioten.config.GithubResourcePackConfig;
|
||||||
|
import ceratic.projectvollidioten.http.ResourcePackHttpServer;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static ceratic.projectvollidioten.config.GithubResourcePackConfig.loadOrCreate;
|
||||||
|
|
||||||
|
public class GithubResourcePackUpdater {
|
||||||
|
private ResourcePackHttpServer httpServer;
|
||||||
|
private final MinecraftServer server;
|
||||||
|
private final Path configDir;
|
||||||
|
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
private GithubResourcePackConfig config;
|
||||||
|
private String latestVersionTag = "";
|
||||||
|
private String downloadUrl = "";
|
||||||
|
|
||||||
|
public GithubResourcePackUpdater(MinecraftServer server) {
|
||||||
|
this.server = server;
|
||||||
|
this.configDir = server.getRunDirectory().resolve("config/projectvollidioten");
|
||||||
|
this.config = loadOrCreate(configDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
// Start HTTP server FIRST (so we have URL ready)
|
||||||
|
try {
|
||||||
|
httpServer = new ResourcePackHttpServer(server, config.resourcePackFileName, 25585); // Use port 25585 for now
|
||||||
|
httpServer.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("[GithubRP] ❌ Failed to start HTTP server: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule periodic checks
|
||||||
|
long interval = config.checkIntervalMinutes;
|
||||||
|
scheduler.scheduleAtFixedRate(this::checkForUpdate, 0, interval, TimeUnit.MINUTES);
|
||||||
|
System.out.println("[GithubRP] Auto-updater started. Checking every " + interval + " minutes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (httpServer != null) httpServer.stop();
|
||||||
|
scheduler.shutdown();
|
||||||
|
System.out.println("[GithubRP] Auto-updater stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForUpdate() {
|
||||||
|
try {
|
||||||
|
String apiUrl = String.format(
|
||||||
|
"https://api.github.com/repos/%s/%s/releases/latest",
|
||||||
|
config.githubOwner, config.githubRepo
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection();
|
||||||
|
conn.setRequestProperty("User-Agent", "FabricMod-GithubRPUpdater");
|
||||||
|
conn.setConnectTimeout(10_000);
|
||||||
|
conn.setReadTimeout(15_000);
|
||||||
|
|
||||||
|
int responseCode = conn.getResponseCode();
|
||||||
|
if (responseCode != 200) {
|
||||||
|
System.err.println("[GithubRP] Failed to fetch release: HTTP " + responseCode);
|
||||||
|
conn.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject release = JsonParser.parseReader(new InputStreamReader(conn.getInputStream())).getAsJsonObject();
|
||||||
|
conn.disconnect();
|
||||||
|
|
||||||
|
String tag = release.get("tag_name").getAsString();
|
||||||
|
if (tag.equals(latestVersionTag)) {
|
||||||
|
// Already up to date
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray assets = release.getAsJsonArray("assets");
|
||||||
|
for (var assetElem : assets) {
|
||||||
|
JsonObject asset = assetElem.getAsJsonObject();
|
||||||
|
String name = asset.get("name").getAsString();
|
||||||
|
if (name.equals(config.resourcePackFileName)) {
|
||||||
|
downloadUrl = asset.get("browser_download_url").getAsString();
|
||||||
|
downloadAndInstallResourcePack(downloadUrl, tag);
|
||||||
|
latestVersionTag = tag;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.err.println("[GithubRP] Release " + tag + " found, but no asset named '" + config.resourcePackFileName + "'");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("[GithubRP] Error checking for update: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadAndInstallResourcePack(String downloadUrl, String tag) throws IOException {
|
||||||
|
Path packsDir = server.getRunDirectory().resolve("server-resource-packs");
|
||||||
|
Files.createDirectories(packsDir);
|
||||||
|
|
||||||
|
Path tempFile = Files.createTempFile("github-rp-", ".zip");
|
||||||
|
Path targetFile = packsDir.resolve(config.resourcePackFileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Download
|
||||||
|
System.out.println("[GithubRP] Downloading new resource pack: " + tag);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) new URL(downloadUrl).openConnection();
|
||||||
|
conn.setRequestProperty("User-Agent", "FabricMod-GithubRPUpdater");
|
||||||
|
try (InputStream in = conn.getInputStream()) {
|
||||||
|
Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ZIP (basic check)
|
||||||
|
try (ZipFile ignored = new ZipFile(tempFile.toFile())) {
|
||||||
|
// OK if no exception
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("Downloaded file is not a valid ZIP: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace old pack
|
||||||
|
Files.move(tempFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
updateServerPropertiesWithHttpUrl(targetFile); // ✅ Now uses local HTTP URL!
|
||||||
|
|
||||||
|
// Update server.properties to point to the new pack
|
||||||
|
// updateServerProperties(targetFile);
|
||||||
|
|
||||||
|
// Update server.properties if needed (optional — Fabric handles reload via packet)
|
||||||
|
if (server instanceof DedicatedServer dedicated) {
|
||||||
|
// Force reload of resource packs (Fabric supports dynamic reload)
|
||||||
|
// We’ll push updated pack hash to clients on next join via login packet
|
||||||
|
System.out.println("[GithubRP] Resource pack updated to " + tag + ". New clients will get it.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { Files.deleteIfExists(tempFile); } catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateServerPropertiesWithHttpUrl(Path packPath) {
|
||||||
|
try {
|
||||||
|
String sha1 = computeSHA1(packPath);
|
||||||
|
if (sha1 == null) return;
|
||||||
|
|
||||||
|
String url = (httpServer != null)
|
||||||
|
? httpServer.getPackUrl()
|
||||||
|
: "http://127.0.0.1:25585/resourcepacks/" + config.resourcePackFileName;
|
||||||
|
|
||||||
|
// Update server.properties
|
||||||
|
Path serverProps = server.getRunDirectory().resolve("server.properties");
|
||||||
|
if (!Files.exists(serverProps)) return;
|
||||||
|
|
||||||
|
List<String> lines = Files.readAllLines(serverProps);
|
||||||
|
List<String> newLines = new ArrayList<>();
|
||||||
|
boolean foundRp = false, foundSha = false;
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
String t = line.trim();
|
||||||
|
if (t.startsWith("resource-pack=")) {
|
||||||
|
line = "resource-pack=" + url + (line.contains("#") ? " " + line.substring(line.indexOf('#')) : "");
|
||||||
|
foundRp = true;
|
||||||
|
} else if (t.startsWith("resource-pack-sha1=")) {
|
||||||
|
line = "resource-pack-sha1=" + sha1 + (line.contains("#") ? " " + line.substring(line.indexOf('#')) : "");
|
||||||
|
foundSha = true;
|
||||||
|
}
|
||||||
|
newLines.add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundRp) newLines.add("resource-pack=" + url);
|
||||||
|
if (!foundSha) newLines.add("resource-pack-sha1=" + sha1);
|
||||||
|
|
||||||
|
Path tmp = serverProps.resolveSibling("server.properties.tmp");
|
||||||
|
Files.write(tmp, newLines);
|
||||||
|
Files.move(tmp, serverProps, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
|
System.out.println("[GithubRP] ✅ server.properties updated:");
|
||||||
|
System.out.println(" URL: " + url);
|
||||||
|
System.out.println(" SHA1: " + sha1.substring(0, 8) + "...");
|
||||||
|
|
||||||
|
// Optional: Broadcast to all players
|
||||||
|
server.execute(() -> {
|
||||||
|
String shortUrl = url.replace("http://", "").replace("https://", "");
|
||||||
|
server.getPlayerManager().getPlayerList().forEach(p -> {
|
||||||
|
p.sendMessage(net.minecraft.text.Text.literal("[GithubRP] 🌐 Pack served at " + shortUrl), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("[GithubRP] Failed to update server.properties with HTTP URL: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String computeSHA1(Path file) {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
try (InputStream in = Files.newInputStream(file)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int n;
|
||||||
|
while ((n = in.read(buffer)) != -1) {
|
||||||
|
digest.update(buffer, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] hash = digest.digest();
|
||||||
|
StringBuilder hex = new StringBuilder();
|
||||||
|
for (byte b : hash) {
|
||||||
|
hex.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
return hex.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package ceratic.projectvollidioten;
|
package ceratic.projectvollidioten;
|
||||||
|
|
||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -14,6 +16,8 @@ public class Projectvollidioten implements ModInitializer {
|
|||||||
// That way, it's clear which mod wrote info, warnings, and errors.
|
// That way, it's clear which mod wrote info, warnings, and errors.
|
||||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||||
|
|
||||||
|
private GithubResourcePackUpdater updater;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
// This code runs as soon as Minecraft is in a mod-load-ready state.
|
// This code runs as soon as Minecraft is in a mod-load-ready state.
|
||||||
@@ -22,6 +26,9 @@ public class Projectvollidioten implements ModInitializer {
|
|||||||
|
|
||||||
LOGGER.info("Hello Fabric world!");
|
LOGGER.info("Hello Fabric world!");
|
||||||
|
|
||||||
|
ServerLifecycleEvents.SERVER_STARTED.register(this::onServerStart);
|
||||||
|
ServerLifecycleEvents.SERVER_STOPPING.register(this::onServerStop);
|
||||||
|
|
||||||
// Event: Player Join
|
// Event: Player Join
|
||||||
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
|
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
|
||||||
PlayerDataLogger.sendPlayerData(handler.getPlayer(), server, "JOIN");
|
PlayerDataLogger.sendPlayerData(handler.getPlayer(), server, "JOIN");
|
||||||
@@ -32,4 +39,15 @@ public class Projectvollidioten implements ModInitializer {
|
|||||||
PlayerDataLogger.sendPlayerData(handler.getPlayer(), server, "LEAVE");
|
PlayerDataLogger.sendPlayerData(handler.getPlayer(), server, "LEAVE");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onServerStart(MinecraftServer server) {
|
||||||
|
updater = new GithubResourcePackUpdater(server);
|
||||||
|
updater.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onServerStop(MinecraftServer server) {
|
||||||
|
if (updater != null) {
|
||||||
|
updater.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package ceratic.projectvollidioten.config;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class GithubResourcePackConfig {
|
||||||
|
public String githubOwner = "your-username";
|
||||||
|
public String githubRepo = "your-repo";
|
||||||
|
public String resourcePackFileName = "my-pack.zip";
|
||||||
|
public int checkIntervalMinutes = 60; // check every hour
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private static final String CONFIG_NAME = "resourcepack.json";
|
||||||
|
|
||||||
|
public static GithubResourcePackConfig loadOrCreate(Path configDir) {
|
||||||
|
Path configFile = configDir.resolve(CONFIG_NAME);
|
||||||
|
try {
|
||||||
|
if (Files.exists(configFile)) {
|
||||||
|
try (Reader reader = Files.newBufferedReader(configFile)) {
|
||||||
|
return GSON.fromJson(reader, GithubResourcePackConfig.class);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create default config
|
||||||
|
GithubResourcePackConfig defaultConfig = new GithubResourcePackConfig();
|
||||||
|
Files.createDirectories(configDir);
|
||||||
|
try (Writer writer = Files.newBufferedWriter(configFile)) {
|
||||||
|
GSON.toJson(defaultConfig, writer);
|
||||||
|
}
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("[GithubRP] Failed to load config, using defaults: " + e.getMessage());
|
||||||
|
return new GithubResourcePackConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package ceratic.projectvollidioten.http;
|
||||||
|
|
||||||
|
import ceratic.projectvollidioten.config.GithubResourcePackConfig;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class ResourcePackHttpServer {
|
||||||
|
private final HttpServer server;
|
||||||
|
private final Path resourcePacksDir;
|
||||||
|
private final String expectedFileName;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
public ResourcePackHttpServer(MinecraftServer mcServer, String expectedFileName, int port) throws IOException {
|
||||||
|
this.resourcePacksDir = mcServer.getRunDirectory().resolve("server-resource-packs");
|
||||||
|
this.expectedFileName = expectedFileName;
|
||||||
|
this.port = port;
|
||||||
|
|
||||||
|
Files.createDirectories(resourcePacksDir);
|
||||||
|
|
||||||
|
this.server = HttpServer.create(new InetSocketAddress("0.0.0.0", port), 0);
|
||||||
|
this.server.createContext("/resourcepacks", new ResourcePackHandler());
|
||||||
|
this.server.setExecutor(null); // Uses default executor
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
server.start();
|
||||||
|
System.out.println("[GithubRP] 🌐 HTTP server started on http://0.0.0.0:" + port + "/resourcepacks/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
server.stop(1);
|
||||||
|
System.out.println("[GithubRP] 🌐 HTTP server stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns URL clients should use — e.g. http://192.168.1.100:25585/resourcepacks/my-pack.zip
|
||||||
|
public String getPackUrl() {
|
||||||
|
// Try to get LAN IP (optional: replace with public IP if port-forwarded)
|
||||||
|
String host = "127.0.0.1"; // fallback
|
||||||
|
try {
|
||||||
|
java.net.InetAddress addr = java.net.InetAddress.getLocalHost();
|
||||||
|
if (!addr.isLoopbackAddress() && addr.isSiteLocalAddress()) {
|
||||||
|
host = addr.getHostAddress();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
return String.format("http://%s:%d/resourcepacks/%s", host, port, expectedFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ResourcePackHandler implements HttpHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) throws IOException {
|
||||||
|
String path = exchange.getRequestURI().getPath();
|
||||||
|
String targetFile = path.substring("/resourcepacks/".length());
|
||||||
|
|
||||||
|
// Security: only allow configured pack filename
|
||||||
|
if (!targetFile.equals(expectedFileName)) {
|
||||||
|
sendResponse(exchange, 403, "Forbidden: unknown resource pack");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path packFile = resourcePacksDir.resolve(targetFile);
|
||||||
|
if (!Files.exists(packFile) || !Files.isRegularFile(packFile)) {
|
||||||
|
sendResponse(exchange, 404, "Resource pack not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", "application/zip");
|
||||||
|
exchange.getResponseHeaders().set("Content-Length", String.valueOf(Files.size(packFile)));
|
||||||
|
exchange.getResponseHeaders().set("Cache-Control", "public, max-age=3600");
|
||||||
|
exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
// Send file
|
||||||
|
exchange.sendResponseHeaders(200, 0); // chunked
|
||||||
|
try (OutputStream os = exchange.getResponseBody();
|
||||||
|
var fis = Files.newInputStream(packFile)) {
|
||||||
|
fis.transferTo(os);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("[GithubRP] Client disconnected during pack download.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendResponse(HttpExchange exchange, int code, String msg) throws IOException {
|
||||||
|
byte[] data = msg.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
exchange.sendResponseHeaders(code, data.length);
|
||||||
|
exchange.getResponseBody().write(data);
|
||||||
|
exchange.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
"id": "projectvollidioten",
|
"id": "projectvollidioten",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
"name": "projectvollidioten",
|
"name": "projectvollidioten",
|
||||||
"description": "This is an example description! Tell everyone what your mod is about!",
|
"description": "A Minecraft Fabric mod that provides comprehensive player data logging and automatic resource pack management via GitHub integration.",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Me!"
|
"Me!"
|
||||||
],
|
],
|
||||||
"contact": {
|
"contact": {
|
||||||
"homepage": "https://fabricmc.net/",
|
"homepage": "https://vollidioten.ceraticsoft.de/",
|
||||||
"sources": "https://github.com/FabricMC/fabric-example-mod"
|
"sources": "https://github.com/ceratic/projekt_vollidion_mod"
|
||||||
},
|
},
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"icon": "assets/projectvollidioten/icon.png",
|
"icon": "assets/projectvollidioten/icon.png",
|
||||||
|
|||||||
Reference in New Issue
Block a user