diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index e7f3f09af9..f674f5809a 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -235,7 +235,7 @@ public BungeeCord() throws IOException getPluginManager().registerCommand( null, new CommandReload() ); getPluginManager().registerCommand( null, new CommandEnd() ); getPluginManager().registerCommand( null, new CommandIP() ); - getPluginManager().registerCommand( null, new CommandBungee() ); + getPluginManager().registerCommand( null, new CommandBungee( this ) ); getPluginManager().registerCommand( null, new CommandPerms() ); if ( !Boolean.getBoolean( "net.md_5.bungee.native.disable" ) ) diff --git a/proxy/src/main/java/net/md_5/bungee/command/CommandBungee.java b/proxy/src/main/java/net/md_5/bungee/command/CommandBungee.java index b07987915f..d3429a86ba 100644 --- a/proxy/src/main/java/net/md_5/bungee/command/CommandBungee.java +++ b/proxy/src/main/java/net/md_5/bungee/command/CommandBungee.java @@ -1,21 +1,82 @@ package net.md_5.bungee.command; +import java.util.concurrent.CompletableFuture; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.hover.content.Text; import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.module.ModuleVersion; +import net.md_5.bungee.util.BuildVersionRetriever; public class CommandBungee extends Command { + private final BuildVersionRetriever versionRetriever = new BuildVersionRetriever(); + private int currentVersion = -1; - public CommandBungee() + public CommandBungee(ProxyServer proxy) { super( "bungee" ); + ModuleVersion moduleVersion = ModuleVersion.parse( proxy.getVersion() ); + try + { + if ( moduleVersion != null ) + { + currentVersion = Integer.parseInt( moduleVersion.getBuild() ); + } + } catch ( NumberFormatException ignored ) + { + } } - @Override public void execute(CommandSender sender, String[] args) { - sender.sendMessage( ChatColor.BLUE + "This server is running BungeeCord version " + ProxyServer.getInstance().getVersion() + " by md_5" ); + TextComponent component = new TextComponent( "This server is running BungeeCord version " ); + component.setColor( ChatColor.BLUE ); + TextComponent version = new TextComponent( ProxyServer.getInstance().getVersion() ); + version.setClickEvent( new ClickEvent( ClickEvent.Action.COPY_TO_CLIPBOARD, ProxyServer.getInstance().getVersion() ) ); + version.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new Text( new TextComponent( ProxyServer.getInstance().getTranslation( "click_to_copy" ) ) ) ) ); + component.addExtra( version ); + component.addExtra( " by md_5" ); + sender.sendMessage( component ); + + // return if custom build or no permission + if ( currentVersion == -1 || !sender.hasPermission( "bungeecord.command.bungee.versioncheck" ) ) + { + return; + } + + CompletableFuture future = versionRetriever.retrieveLatestBuild(); + + if ( !future.isDone() ) + { + sender.sendMessage( ChatColor.YELLOW + "Checking latest version..." ); + } + + future.thenAccept( latest -> + { + if ( currentVersion >= latest ) + { + sender.sendMessage( ChatColor.GREEN + "This server is running the latest build of BungeeCord" ); + return; + } + + int behind = latest - currentVersion; + sender.sendMessage( ChatColor.YELLOW + "This server is " + behind + " builds behind" ); + sender.sendMessage( new ComponentBuilder( "Click here to get the latest build" ) + .italic( true ) + .color( ChatColor.YELLOW ) + .event( new ClickEvent( ClickEvent.Action.OPEN_URL, "https://ci.md-5.net/job/BungeeCord/lastBuild/" ) ) + .build() ); + + } ).exceptionally( ex -> + { + sender.sendMessage( ChatColor.RED + "Error fetching latest successful build" ); + return null; + } ); } } diff --git a/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java b/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java index d659a138fb..c5828c4a92 100644 --- a/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java +++ b/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java @@ -95,7 +95,7 @@ public void load() } ) ); set( "permissions.admin", Arrays.asList( new String[] { - "bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload", "bungeecord.command.kick", "bungeecord.command.send", "bungeecord.command.find" + "bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload", "bungeecord.command.kick", "bungeecord.command.send", "bungeecord.command.find", "bungeecord.command.bungee.versioncheck" } ) ); } diff --git a/proxy/src/main/java/net/md_5/bungee/util/BuildVersionRetriever.java b/proxy/src/main/java/net/md_5/bungee/util/BuildVersionRetriever.java new file mode 100644 index 0000000000..5ccc0664f7 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/util/BuildVersionRetriever.java @@ -0,0 +1,69 @@ +package net.md_5.bungee.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class BuildVersionRetriever +{ + private static final Gson GSON = new GsonBuilder().create(); + + private final Executor executor = Executors.newSingleThreadExecutor(); + private int cachedLatestBuildVersion; + private long lastLatestBuildRetrieve; + private CompletableFuture currentRetrievingFuture; + + public synchronized CompletableFuture retrieveLatestBuild() + { + // return the awaiting retrieving future is there is any right now + if ( currentRetrievingFuture != null && !currentRetrievingFuture.isDone() ) + { + return currentRetrievingFuture; + } + + // cache result for 15 minutes + if ( System.currentTimeMillis() < lastLatestBuildRetrieve + TimeUnit.MINUTES.toMillis( 15 ) ) + { + // if retrieving failed this is 0, so we try again. + if ( cachedLatestBuildVersion == 0 ) + { + return retrieveLatestBuild0(); + } + return CompletableFuture.completedFuture( cachedLatestBuildVersion ); + } + + return retrieveLatestBuild0(); + } + + private CompletableFuture retrieveLatestBuild0() + { + CompletableFuture completableFuture = currentRetrievingFuture = new CompletableFuture<>(); + executor.execute( () -> + { + lastLatestBuildRetrieve = System.currentTimeMillis(); + try + { + URL restApi = new URL( "https://ci.md-5.net/job/BungeeCord/api/json" ); + URLConnection connection = restApi.openConnection(); + + connection.setConnectTimeout( 15000 ); + connection.setReadTimeout( 15000 ); + + JsonObject jsonObject = GSON.fromJson( new InputStreamReader( connection.getInputStream() ), JsonObject.class ); + jsonObject = (JsonObject) jsonObject.get( "lastSuccessfulBuild" ); + completableFuture.complete( cachedLatestBuildVersion = jsonObject.get( "number" ).getAsInt() ); + } catch ( Exception exception ) + { + completableFuture.completeExceptionally( exception ); + } + } ); + return completableFuture; + } +} diff --git a/proxy/src/main/resources/messages.properties b/proxy/src/main/resources/messages.properties index 149c742f25..766f8f17d8 100644 --- a/proxy/src/main/resources/messages.properties +++ b/proxy/src/main/resources/messages.properties @@ -41,3 +41,4 @@ command_ip=\u00a79IP of {0} is {1} illegal_chat_characters=\u00a7cIllegal characters in chat ({0}) kick_message=\u00a7cYou have been kicked off the proxy. reject_transfer=\u00a7cYour transfer was rejected. +click_to_copy=Click to copy to clipboard