package dev.plex.util; import com.google.common.base.Charsets; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import dev.plex.PlexBase; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.apache.commons.io.FileUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.json.JSONObject; public class UpdateChecker implements PlexBase { /* * -4 = Never checked for updates * -3 = Likely rate limited * -2 = Unknown commit * -1 = Error occurred * 0 = Up to date * > 0 = Number of commits behind */ private final String DOWNLOAD_PAGE = "https://ci.plex.us.org/job/"; private String BRANCH = plugin.config.getString("update_branch"); private final String REPO = plugin.config.getString("update_repo"); private int distance = -4; // Adapted from Paper private int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) { try { HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).openConnection(); connection.connect(); if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { return -2; // Unknown commit } if (connection.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) { return -3; // Rate limited likely } try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { JsonObject obj = new Gson().fromJson(reader, JsonObject.class); String status = obj.get("status").getAsString(); return switch (status) { case "identical" -> 0; case "behind" -> obj.get("behind_by").getAsInt(); default -> -1; }; } catch (JsonSyntaxException | NumberFormatException e) { e.printStackTrace(); return -1; } } catch (IOException e) { e.printStackTrace(); return -1; } } // If verbose is 0, it will display nothing // If verbose is 1, it will only display a message if there is an update available // If verbose is 2, it will display all messages public boolean getUpdateStatusMessage(CommandSender sender, boolean cached, int verbosity) { if (BRANCH == null) { PlexLog.error("You did not specify a branch to use for update checking. Defaulting to master."); BRANCH = "master"; } // If it's -4, it hasn't checked for updates yet if (distance == -4) { distance = fetchDistanceFromGitHub(REPO, BRANCH, BuildInfo.getHead()); PlexLog.debug("Never checked for updates, checking now..."); } else { // If the request isn't asked to be cached, fetch it if (!cached) { distance = fetchDistanceFromGitHub(REPO, BRANCH, BuildInfo.getHead()); PlexLog.debug("We have checked for updates before, but this request was not asked to be cached."); } else { PlexLog.debug("We have checked for updates before, using cache."); } } switch (distance) { case -1 -> { if (verbosity == 2) { sender.sendMessage(Component.text("There was an error checking for updates.").color(NamedTextColor.RED)); } return false; } case 0 -> { if (verbosity == 2) { sender.sendMessage(Component.text("Plex is up to date!").color(NamedTextColor.GREEN)); } return false; } case -2 -> { if (verbosity == 2) { sender.sendMessage(Component.text("Unknown version, unable to check for updates.").color(NamedTextColor.RED)); } return false; } default -> { if (verbosity >= 1) { sender.sendMessage(Component.text("Plex is not up to date!", NamedTextColor.RED)); sender.sendMessage(Component.text("Download a new version at: " + DOWNLOAD_PAGE + "Plex").color(NamedTextColor.RED)); sender.sendMessage(Component.text("Or run: /plex update").color(NamedTextColor.RED)); } return true; } } } public void updateJar(CommandSender sender, String name, boolean module) { CloseableHttpClient client = HttpClients.createDefault(); AtomicReference url = new AtomicReference<>(DOWNLOAD_PAGE + name); if (!module) { url.set(url.get() + "/job/" + BRANCH); } PlexLog.debug(url.toString()); HttpGet get = new HttpGet(url + "/lastSuccessfulBuild/api/json"); try { HttpResponse response = client.execute(get); JSONObject object = new JSONObject(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8)); JSONObject artifact = object.getJSONArray("artifacts").getJSONObject(0); String jarFile = artifact.getString("fileName"); sender.sendMessage(PlexUtils.mmDeserialize("Downloading latest JAR file: " + jarFile)); File copyTo; if (!module) { copyTo = new File(Bukkit.getUpdateFolderFile(), jarFile); } else { copyTo = new File(plugin.getModulesFolder().getPath(), jarFile); } CompletableFuture.runAsync(() -> { try { FileUtils.copyURLToFile( new URL(url + "/lastSuccessfulBuild/artifact/build/libs/" + jarFile), copyTo ); sender.sendMessage(PlexUtils.mmDeserialize("New JAR file downloaded successfully.")); } catch (IOException e) { e.printStackTrace(); } }); } catch (IOException e) { e.printStackTrace(); } } }