diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java index f6963c96..d88cdd5f 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java @@ -27,7 +27,6 @@ import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; -import com.google.gson.JsonElement; import it.unimi.dsi.fastutil.Pair; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -36,18 +35,16 @@ import java.util.function.BooleanSupplier; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.connection.audience.UserAudience; +import org.geysermc.floodgate.core.http.api.GlobalApiClient; import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand; import org.geysermc.floodgate.core.platform.command.MessageType; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; -import org.geysermc.floodgate.core.util.Constants; -import org.geysermc.floodgate.core.util.HttpClient; -import org.geysermc.floodgate.core.util.HttpClient.HttpResponse; import org.geysermc.floodgate.core.util.Utils; import org.incendo.cloud.context.CommandContext; @Singleton final class FirewallCheckSubcommand extends FloodgateSubCommand { - @Inject HttpClient httpProvider; + @Inject GlobalApiClient globalApiClient; FirewallCheckSubcommand() { super( @@ -72,19 +69,7 @@ public void execute(CommandContext context) { } private BooleanSupplier globalApiCheck(UserAudience sender) { - return executeFirewallText( - sender, "global api", - () -> { - HttpResponse response = - httpProvider.get(Constants.HEALTH_URL, JsonElement.class); - - if (!response.isCodeOk()) { - throw new IllegalStateException(String.format( - "Didn't receive an 'ok' http code. Got: %s, response: %s", - response.getHttpCode(), response.getResponse() - )); - } - }); + return executeFirewallText(sender, "global api", () -> globalApiClient.health()); } private BooleanSupplier executeFirewallText( diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java index 9b45b77d..40a77fb2 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java @@ -27,23 +27,23 @@ import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; -import com.google.gson.JsonElement; import jakarta.inject.Inject; import jakarta.inject.Singleton; import org.geysermc.floodgate.core.command.CommonCommandMessage; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.connection.audience.UserAudience; +import org.geysermc.floodgate.core.http.downloads.DownloadClient; import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand; import org.geysermc.floodgate.core.platform.command.MessageType; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; -import org.geysermc.floodgate.core.util.HttpClient; +import org.geysermc.floodgate.core.util.DynamicConstants; import org.incendo.cloud.context.CommandContext; @Singleton public class VersionSubcommand extends FloodgateSubCommand { - @Inject HttpClient httpClient; + @Inject DownloadClient downloadClient; @Inject FloodgateLogger logger; VersionSubcommand() { @@ -60,55 +60,33 @@ public void execute(CommandContext context) { UserAudience sender = context.sender(); sender.sendMessage( Message.VERSION_INFO, - literal("version", Constants.FULL_VERSION), - literal("branch", Constants.GIT_BRANCH)); - sender.sendMessage(Message.VERSION_FETCH_INFO); + literal("version", DynamicConstants.FULL_VERSION), + literal("branch", DynamicConstants.GIT_BRANCH)); - String baseUrl = String.format( - "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/%s/lastSuccessfulBuild/", - Constants.GIT_BRANCH - ); + sender.sendMessage(Message.VERSION_FETCH_INFO); - httpClient.asyncGet( - baseUrl + "buildNumber", - JsonElement.class - ).whenComplete((result, error) -> { + downloadClient.latestBuildFor("floodgate").whenComplete((result, error) -> { if (error != null) { sender.sendMessage(Message.VERSION_FETCH_ERROR); - error.printStackTrace(); + logger.error("An error occurred while fetching latest version", error); return; } - JsonElement response = result.getResponse(); - - logger.info(String.valueOf(response)); - logger.info("{}", result.getHttpCode()); - - if (result.getHttpCode() == 404) { + if (result == null) { sender.sendMessage(Message.VERSION_FETCH_NOT_FOUND); return; } - if (!result.isCodeOk()) { - //todo make it more generic instead of using a Whitelist command strings - logger.error( - "Got an error from requesting the latest Floodgate version: {}", - response.toString() - ); - sender.sendMessage(CommonCommandMessage.UNEXPECTED_ERROR); - return; - } - - int buildNumber = response.getAsInt(); + int buildNumber = result.build(); - if (buildNumber > Constants.BUILD_NUMBER) { + if (buildNumber > DynamicConstants.BUILD_NUMBER) { sender.sendMessage( Message.VERSION_OUTDATED, - literal("count", buildNumber - Constants.BUILD_NUMBER), - literal("url", baseUrl)); + literal("count", buildNumber - DynamicConstants.BUILD_NUMBER), + literal("url", Constants.LATEST_DOWNLOAD_URL)); return; } - if (buildNumber == Constants.BUILD_NUMBER) { + if (buildNumber == DynamicConstants.BUILD_NUMBER) { sender.sendMessage(Message.VERSION_LATEST); return; } @@ -119,7 +97,7 @@ public void execute(CommandContext context) { public static final class Message { public static final TranslatableMessage VERSION_INFO = new TranslatableMessage("floodgate.command.main.version.info", MessageType.NORMAL); public static final TranslatableMessage VERSION_FETCH_INFO = new TranslatableMessage("floodgate.command.main.version.fetch.info", MessageType.INFO); - public static final TranslatableMessage VERSION_FETCH_ERROR = new TranslatableMessage("floodgate.command.main.version.fetch.error", MessageType.ERROR); + public static final TranslatableMessage VERSION_FETCH_ERROR = new TranslatableMessage("floodgate.command.main.version.fetch.error " + CommonCommandMessage.CHECK_CONSOLE, MessageType.ERROR); public static final TranslatableMessage VERSION_FETCH_NOT_FOUND = new TranslatableMessage("floodgate.command.main.version.fetch.not_found", MessageType.ERROR); public static final TranslatableMessage VERSION_OUTDATED = new TranslatableMessage("floodgate.command.main.version.fetch.result.outdated", MessageType.NORMAL); public static final TranslatableMessage VERSION_LATEST = new TranslatableMessage("floodgate.command.main.version.fetch.result.latest", MessageType.SUCCESS); diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/api/GlobalApiClient.java b/core/src/main/java/org/geysermc/floodgate/core/http/api/GlobalApiClient.java new file mode 100644 index 00000000..dea30fb1 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/http/api/GlobalApiClient.java @@ -0,0 +1,17 @@ +package org.geysermc.floodgate.core.http.api; + +import io.micronaut.http.HttpHeaders; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Header; +import io.micronaut.http.client.annotation.Client; +import org.geysermc.floodgate.core.util.Constants; + +@Client("${http.baseUrl.api}") +@Header(name = HttpHeaders.USER_AGENT, value = Constants.USER_AGENT) +public interface GlobalApiClient { + /** + * Checks if it can connect to the Global Api, any other status code than 204 or 404 will throw + */ + @Get("/healthy") + void health(); +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/downloads/DownloadClient.java b/core/src/main/java/org/geysermc/floodgate/core/http/downloads/DownloadClient.java new file mode 100644 index 00000000..6b70685e --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/http/downloads/DownloadClient.java @@ -0,0 +1,16 @@ +package org.geysermc.floodgate.core.http.downloads; + +import io.micronaut.http.HttpHeaders; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Header; +import io.micronaut.http.client.annotation.Client; +import jakarta.validation.constraints.NotBlank; +import java.util.concurrent.CompletableFuture; +import org.geysermc.floodgate.core.util.Constants; + +@Client("${http.baseUrl.download}") +@Header(name = HttpHeaders.USER_AGENT, value = Constants.USER_AGENT) +public interface DownloadClient { + @Get("/v2/projects/{project}/versions/latest/builds/latest") + CompletableFuture latestBuildFor(@NotBlank String project); +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/downloads/LatestBuildResult.java b/core/src/main/java/org/geysermc/floodgate/core/http/downloads/LatestBuildResult.java new file mode 100644 index 00000000..c0d32b67 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/http/downloads/LatestBuildResult.java @@ -0,0 +1,7 @@ +package org.geysermc.floodgate.core.http.downloads; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public record LatestBuildResult(int build) { +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/http/xbox/XboxClient.java b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/XboxClient.java index a930ee57..c4cbb8c0 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/http/xbox/XboxClient.java +++ b/core/src/main/java/org/geysermc/floodgate/core/http/xbox/XboxClient.java @@ -25,19 +25,17 @@ package org.geysermc.floodgate.core.http.xbox; -import static io.micronaut.http.HttpHeaders.ACCEPT; -import static io.micronaut.http.HttpHeaders.USER_AGENT; - +import io.micronaut.http.HttpHeaders; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Header; import io.micronaut.http.client.annotation.Client; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import java.util.concurrent.CompletableFuture; +import org.geysermc.floodgate.core.util.Constants; -@Client("${http.baseUrl}/v2/xbox") -@Header(name = USER_AGENT, value = "${http.userAgent}") -@Header(name = ACCEPT, value = "application/json") +@Client("${http.baseUrl.api}/v2/xbox") +@Header(name = HttpHeaders.USER_AGENT, value = Constants.USER_AGENT) public interface XboxClient { @Get("/xuid/{gamertag}") CompletableFuture xuidByGamertag( diff --git a/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java b/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java index 57400e04..55241e6b 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/core/module/CommonModule.java @@ -40,7 +40,6 @@ import org.geysermc.floodgate.core.crypto.DataCodecType; import org.geysermc.floodgate.core.crypto.topping.Base64Topping; import org.geysermc.floodgate.core.crypto.topping.Topping; -import org.geysermc.floodgate.core.util.Constants; @Factory public class CommonModule { @@ -71,20 +70,6 @@ public Topping topping() { return new Base64Topping(); } - @Bean - @Singleton - @Named("gitBranch") - public String gitBranch() { - return Constants.GIT_BRANCH; - } - - @Bean - @Singleton - @Named("buildNumber") - public int buildNumber() { - return Constants.BUILD_NUMBER; - } - @Bean @Singleton @Named("kickMessageAttribute") diff --git a/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java b/core/src/main/java/org/geysermc/floodgate/core/util/Constants.java similarity index 73% rename from core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java rename to core/src/main/java/org/geysermc/floodgate/core/util/Constants.java index ca25c57d..1f7b7bea 100644 --- a/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/Constants.java @@ -26,10 +26,6 @@ package org.geysermc.floodgate.core.util; public final class Constants { - public static final String FULL_VERSION = "@fullVersion@"; - public static final String VERSION = "@version@"; - public static final int BUILD_NUMBER = Integer.parseInt("@buildNumber@"); - public static final String GIT_BRANCH = "@branch@"; public static final int METRICS_ID = 14649; public static final int CONFIG_VERSION = 3; @@ -38,25 +34,14 @@ public final class Constants { public static final boolean DEBUG_MODE = false; public static final boolean PRINT_ALL_PACKETS = false; - public static final String DATABASE_NAME_FORMAT = "^floodgate-[a-zA-Z0-9_]{0,16}-database.jar$"; - public static final String USER_AGENT = "GeyserMC/Floodgate"; private static final String API_BASE_URL = "s://api.geysermc.org"; public static final String HEALTH_URL = "http" + API_BASE_URL + "/health"; - public static final String WEBSOCKET_URL = "ws" + API_BASE_URL + "/ws"; - public static final String GET_XUID_URL = "http" + API_BASE_URL + "/v2/xbox/xuid/"; - public static final String GET_GAMERTAG_URL = "http" + API_BASE_URL + "/v2/xbox/gamertag/"; - public static final String NEWS_OVERVIEW_URL = "http" + API_BASE_URL + "/v2/news/"; - public static final String GET_BEDROCK_LINK = "http" + API_BASE_URL + "/v2/link/bedrock/"; - public static final String LINK_INFO_URL = "https://link.geysermc.org/"; + public static final String LATEST_DOWNLOAD_URL = "https://geysermc.org/download"; - public static final String NEWS_PROJECT_NAME = "floodgate"; - - - public static final String NTP_SERVER = "time.cloudflare.com"; public static final String INTERNAL_ERROR_MESSAGE = "An internal error happened while handling Floodgate data." + " Try logging in again or contact a server administrator if the issue persists."; diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/HttpClient.java b/core/src/main/java/org/geysermc/floodgate/core/util/HttpClient.java deleted file mode 100644 index 39003c04..00000000 --- a/core/src/main/java/org/geysermc/floodgate/core/util/HttpClient.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Floodgate - */ - -package org.geysermc.floodgate.core.util; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.inject.Singleton; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - -// resources are properly closed and ignoring the original stack trace is intended -@SuppressWarnings({"PMD.CloseResource", "PMD.PreserveStackTrace"}) -@Singleton -public class HttpClient { - private final Gson gson = new Gson(); - @Inject - @Named("commonPool") - ExecutorService executorService; - - public CompletableFuture asyncGet(String urlString) { - return CompletableFuture.supplyAsync(() -> get(urlString), executorService); - } - - public CompletableFuture> asyncGet(String urlString, Class response) { - return CompletableFuture.supplyAsync(() -> get(urlString, response), executorService); - } - - public DefaultHttpResponse get(String urlString) { - return readDefaultResponse(request(urlString)); - } - - public HttpResponse get(String urlString, Class clazz) { - return readResponse(request(urlString), clazz); - } - - public HttpResponse getSilent(String urlString, Class clazz) { - return readResponseSilent(request(urlString), clazz); - } - - private HttpURLConnection request(String urlString) { - HttpURLConnection connection; - - try { - URL url = new URL(urlString.replace(" ", "%20")); // Encode spaces correctly - connection = (HttpURLConnection) url.openConnection(); - } catch (Exception exception) { - throw new RuntimeException("Failed to create connection", exception); - } - - try { - connection.setRequestMethod("GET"); - connection.setUseCaches(false); - connection.setRequestProperty("User-Agent", Constants.USER_AGENT); - connection.setConnectTimeout(3000); - connection.setReadTimeout(5000); - } catch (Exception exception) { - throw new RuntimeException("Failed to create request", exception); - } - - return connection; - } - - @NonNull - private HttpResponse readResponse(HttpURLConnection connection, Class clazz) { - InputStreamReader streamReader = createReader(connection); - if (streamReader == null) { - return new HttpResponse<>(-1, null); - } - - int responseCode = -1; - try { - responseCode = connection.getResponseCode(); - T response = gson.fromJson(streamReader, clazz); - return new HttpResponse<>(responseCode, response); - } catch (Exception ignored) { - // e.g. when the response isn't JSON - return new HttpResponse<>(responseCode, null); - } finally { - try { - streamReader.close(); - } catch (Exception ignored) { - } - } - } - - @NonNull - private HttpResponse readResponseSilent(HttpURLConnection connection, Class clazz) { - try { - return readResponse(connection, clazz); - } catch (Exception ignored) { - return new HttpResponse<>(-1, null); - } - } - - @NonNull - private DefaultHttpResponse readDefaultResponse(HttpURLConnection connection) { - InputStreamReader streamReader = createReader(connection); - if (streamReader == null) { - return new DefaultHttpResponse(-1, null); - } - - try { - int responseCode = connection.getResponseCode(); - JsonObject response = gson.fromJson(streamReader, JsonObject.class); - return new DefaultHttpResponse(responseCode, response); - } catch (Exception exception) { - throw new RuntimeException("Failed to read response", exception); - } finally { - try { - streamReader.close(); - } catch (Exception ignored) { - } - } - } - - @Nullable - private InputStreamReader createReader(HttpURLConnection connection) { - InputStream stream; - try { - stream = connection.getInputStream(); - } catch (Exception exception) { - stream = connection.getErrorStream(); - } - - // it's null for example when it couldn't connect to the server - if (stream != null) { - return new InputStreamReader(stream); - } - return null; - } - - @Getter - @AllArgsConstructor(access = AccessLevel.PRIVATE) - public static class HttpResponse { - private final int httpCode; - private final T response; - - public boolean isCodeOk() { - return httpCode >= 200 && httpCode < 300; - } - } - - public static final class DefaultHttpResponse extends HttpResponse { - DefaultHttpResponse(int httpCode, JsonObject response) { - super(httpCode, response); - } - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java b/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java index 622c2ccf..349deca7 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java @@ -101,7 +101,7 @@ public final class Metrics { ); metricsBase.addCustomChart( - new SimplePie("floodgate_version", () -> Constants.FULL_VERSION) + new SimplePie("floodgate_version", () -> DynamicConstants.FULL_VERSION) ); metricsBase.addCustomChart( diff --git a/core/src/main/resources/application.yaml b/core/src/main/resources/application.yaml index 9a9c389b..e08c2861 100644 --- a/core/src/main/resources/application.yaml +++ b/core/src/main/resources/application.yaml @@ -1,3 +1,4 @@ http: - baseUrl: https://api.geysermc.org - userAgent: GeyserMC/Floodgate \ No newline at end of file + baseUrl: + api: https://api.geysermc.org + download: https://download.geysermc.org diff --git a/core/src/main/templates/org/geysermc/floodgate/core/util/DynamicConstants.java b/core/src/main/templates/org/geysermc/floodgate/core/util/DynamicConstants.java new file mode 100644 index 00000000..a3262dc6 --- /dev/null +++ b/core/src/main/templates/org/geysermc/floodgate/core/util/DynamicConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.util; + +public final class DynamicConstants { + public static final String FULL_VERSION = "@fullVersion@"; + public static final String VERSION = "@version@"; + public static final int BUILD_NUMBER = Integer.parseInt("@buildNumber@"); + public static final String GIT_BRANCH = "@branch@"; +}