From d4359bf480d332389159654cb4330511874ec771 Mon Sep 17 00:00:00 2001 From: akastijn Date: Mon, 23 Jun 2025 23:06:47 +0200 Subject: [PATCH] Add notification server and file download service implementation - Introduce `NotificationServer` for handling HTTP notifications. - Register `ProxyShutdownEvent` to stop the notification server on shutdown. - Add `FileDownloadService` for asynchronous file downloads using the configured endpoint. - Update `Config` to include `download-endpoint`. - Add Javalin and SLF4J dependencies for the HTTP server. --- build.gradle.kts | 2 + .../com/alttd/webinterface/WebInterface.java | 29 +++++++ .../com/alttd/webinterface/config/Config.java | 2 + .../webinterface/http/NotificationServer.java | 83 +++++++++++++++++++ .../web_interact/FileDownloadService.java | 58 +++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 src/main/java/com/alttd/webinterface/http/NotificationServer.java create mode 100644 src/main/java/com/alttd/webinterface/web_interact/FileDownloadService.java diff --git a/build.gradle.kts b/build.gradle.kts index 0ce92a2..2fd3a72 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { compileOnly("com.gitlab.ruany:LiteBansAPI:0.6.1") compileOnly("org.projectlombok:lombok:1.18.30") annotationProcessor("org.projectlombok:lombok:1.18.30") + implementation("io.javalin:javalin:6.6.0") // Javalin for HTTP server + implementation("org.slf4j:slf4j-api:2.0.9") // Required by Javalin } tasks.test { diff --git a/src/main/java/com/alttd/webinterface/WebInterface.java b/src/main/java/com/alttd/webinterface/WebInterface.java index 2ba5ee3..51236c1 100644 --- a/src/main/java/com/alttd/webinterface/WebInterface.java +++ b/src/main/java/com/alttd/webinterface/WebInterface.java @@ -3,10 +3,12 @@ package com.alttd.webinterface; import com.alttd.webinterface.commands.Login; import com.alttd.webinterface.commands.Reload; import com.alttd.webinterface.config.Config; +import com.alttd.webinterface.http.NotificationServer; import com.alttd.webinterface.listeners.ProxyPlayerListener; import com.google.inject.Inject; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import lombok.extern.slf4j.Slf4j; @@ -18,6 +20,8 @@ import lombok.extern.slf4j.Slf4j; ) public class WebInterface { private final ProxyServer server; + private NotificationServer notificationServer; + private static final int HTTP_PORT = 8080; // Default port, could be made configurable @Inject public WebInterface(ProxyServer proxyServer) { @@ -29,11 +33,36 @@ public class WebInterface { reloadConfig(); server.getEventManager().register(this, new ProxyPlayerListener()); loadCommands(); + startNotificationServer(); + } + + @Subscribe + public void onProxyShutdown(ProxyShutdownEvent event) { + stopNotificationServer(); + } + + private void startNotificationServer() { + notificationServer = new NotificationServer(HTTP_PORT); + notificationServer.startServer(); + log.info("Started notification server on port {}", HTTP_PORT); + } + + private void stopNotificationServer() { + if (notificationServer != null) { + notificationServer.stopServer(); + log.info("Stopped notification server"); + } } public void reloadConfig() { Config.init(); log.info("Reloaded WebInterface config."); + + // Restart the notification server to apply any configuration changes + if (notificationServer != null) { + stopNotificationServer(); + } + startNotificationServer(); } public void loadCommands() {// all (proxy)commands go here diff --git a/src/main/java/com/alttd/webinterface/config/Config.java b/src/main/java/com/alttd/webinterface/config/Config.java index 89f7e2e..88c21d3 100644 --- a/src/main/java/com/alttd/webinterface/config/Config.java +++ b/src/main/java/com/alttd/webinterface/config/Config.java @@ -201,6 +201,7 @@ public final class Config { public static String LOGIN_CODE_ENDPOINT = "https://alttd.com/login/requestNewUserLogin/"; public static String LOGIN_ENDPOINT = "https://alttd.com/login"; + public static String DOWNLOAD_ENDPOINT = "https://alttd.com/particles/download"; public static String SECRET = ""; private static void site() { if (SECRET.isEmpty()) { @@ -210,6 +211,7 @@ public final class Config { } SECRET = getString("secret", SECRET); LOGIN_ENDPOINT = getString("login-endpoint", LOGIN_ENDPOINT); + DOWNLOAD_ENDPOINT = getString("download-endpoint", DOWNLOAD_ENDPOINT); LOGIN_CODE_ENDPOINT = getString("login-code-endpoint", LOGIN_CODE_ENDPOINT); } } diff --git a/src/main/java/com/alttd/webinterface/http/NotificationServer.java b/src/main/java/com/alttd/webinterface/http/NotificationServer.java new file mode 100644 index 0000000..58e5c45 --- /dev/null +++ b/src/main/java/com/alttd/webinterface/http/NotificationServer.java @@ -0,0 +1,83 @@ +package com.alttd.webinterface.http; + +import com.alttd.webinterface.web_interact.FileDownloadService; +import io.javalin.Javalin; +import io.javalin.http.Context; +import io.javalin.http.HttpStatus; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +/** + * HTTP server that exposes the /notify/.json endpoint. + */ +@Slf4j +public class NotificationServer { + private final int port; + private Javalin app; + + /** + * Creates a new NotificationServer that listens on the specified port. + * + * @param port The port to listen on + */ + public NotificationServer(int port) { + this.port = port; + } + + /** + * Starts the server. + */ + public void startServer() { + try { + app = Javalin.create(config -> { + config.showJavalinBanner = false; + }).start(port); + + app.get("/notify/{file}.json", this::handleNotifyRequest); + + log.info("NotificationServer started on port {}", port); + } catch (Exception e) { + log.error("Failed to start NotificationServer", e); + } + } + + /** + * Stops the server. + */ + public void stopServer() { + if (app != null) { + app.stop(); + log.info("NotificationServer stopped"); + } + } + + /** + * Handles requests to the /notify/.json endpoint. + */ + private void handleNotifyRequest(Context ctx) { + String uri = ctx.path(); + log.info("Received request: {} {}", ctx.method(), uri); + + String fileName = ctx.pathParam("file") + ".json"; + log.info("Requested file: {}", fileName); + + FileDownloadService.downloadFileAsync(fileName).thenAccept(fileData -> { + if (fileData.isPresent()) { + ctx.contentType("application/json"); + ctx.result(fileData.get()); + } else { + ctx.status(HttpStatus.NOT_FOUND); + ctx.contentType("text/plain"); + ctx.result("File not found or download failed"); + } + }).exceptionally(e -> { + log.error("Error downloading file: {}", fileName, e); + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR); + ctx.contentType("text/plain"); + ctx.result("Failed to handle download"); + return null; + }); + } +} diff --git a/src/main/java/com/alttd/webinterface/web_interact/FileDownloadService.java b/src/main/java/com/alttd/webinterface/web_interact/FileDownloadService.java new file mode 100644 index 0000000..289d7c4 --- /dev/null +++ b/src/main/java/com/alttd/webinterface/web_interact/FileDownloadService.java @@ -0,0 +1,58 @@ +package com.alttd.webinterface.web_interact; + +import com.alttd.webinterface.config.Config; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Service for downloading files from the configured download endpoint. + */ +@Slf4j +public class FileDownloadService { + + /** + * Asynchronously downloads a file from the configured download endpoint. + * + * @param fileName The name of the file to download + * @return A CompletableFuture containing an Optional with the file content if successful, + * or an empty Optional otherwise + */ + public static CompletableFuture> downloadFileAsync(String fileName) { + String downloadUrl = Config.DOWNLOAD_ENDPOINT; + if (!downloadUrl.endsWith("/")) { + downloadUrl += "/"; + } + downloadUrl += fileName; + + log.debug("Downloading file from {}", downloadUrl); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(downloadUrl)) + .header("Authorization", "SECRET " + Config.SECRET) + .GET() + .build(); + + return client.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()) + .thenApply(response -> { + if (response.statusCode() == HttpServletResponse.SC_OK) { + log.debug("Successfully downloaded file: {}", fileName); + return Optional.of(response.body()); + } else { + log.error("Failed to download file: {}. Status code: {}", fileName, response.statusCode()); + return Optional.empty(); + } + }) + .exceptionally(e -> { + log.error("Exception occurred while downloading file: {}", fileName, e); + return Optional.empty(); + }); + } +}