diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0be865d --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Eclipse +.classpath +.project +.metadata +.settings/ + +# IntelliJ +.idea/ +.idea_modules/ +*.iml +*.iws +/out/ + +# Mac +.DS_Store + +# Gradle +.gradle +build/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2c38805 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +sudo: false + +language: java +jdk: + - openjdk8 + - openjdk11 + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + +script: + - ./gradlew build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1f5679f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Azuriom.com + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c09a76 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# AzLink + +AzLink is a plugin to link a Minecraft server or proxy with [Azuriom](https://azuriom.com). + +This plugin currently support the following platforms: +* [Bukkit/Spigot](https://www.spigotmc.org/) +* [BungeeCord](https://github.com/SpigotMC/BungeeCord) +* [Sponge](https://www.spongepowered.org/) +* [Velocity](https://www.velocitypowered.com/) + +## Setup + +### Installation +The plugin works with the same .jar for all the platforms, except Bukkit/Spigot 1.7.10 wich require the legacy version of the plugin. + +You just need to download the plugin, add it to the plugins folder of your server, and restart your server. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..558df72 --- /dev/null +++ b/build.gradle @@ -0,0 +1,21 @@ +allprojects { + group 'com.azuriom' + version '1.0-SNAPSHOT' +} + +subprojects { + apply plugin: 'java' + + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + repositories { + mavenCentral() + + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } + + dependencies { + compileOnly 'com.google.code.gson:gson:2.8.5' + } +} diff --git a/bukkit/build.gradle b/bukkit/build.gradle new file mode 100644 index 0000000..844b73c --- /dev/null +++ b/bukkit/build.gradle @@ -0,0 +1,14 @@ +repositories { + maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } +} + +dependencies { + compileOnly 'org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT' + compile project(':azlink-common') +} + +processResources { + from(sourceSets.main.resources.srcDirs) { + expand 'pluginVersion': project.version + } +} diff --git a/bukkit/src/main/java/com/azuriom/azlink/bukkit/AzLinkBukkitPlugin.java b/bukkit/src/main/java/com/azuriom/azlink/bukkit/AzLinkBukkitPlugin.java new file mode 100644 index 0000000..3e74fa0 --- /dev/null +++ b/bukkit/src/main/java/com/azuriom/azlink/bukkit/AzLinkBukkitPlugin.java @@ -0,0 +1,108 @@ +package com.azuriom.azlink.bukkit; + +import com.azuriom.azlink.bukkit.command.BukkitCommandExecutor; +import com.azuriom.azlink.bukkit.command.BukkitCommandSender; +import com.azuriom.azlink.common.AzLinkPlatform; +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.PlatformType; +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.data.WorldData; +import com.azuriom.azlink.common.logger.JulLoggerAdapter; +import com.azuriom.azlink.common.logger.LoggerAdapter; +import org.bukkit.plugin.java.JavaPlugin; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; + +public final class AzLinkBukkitPlugin extends JavaPlugin implements AzLinkPlatform { + + private final AzLinkPlugin plugin = new AzLinkPlugin(this); + + private LoggerAdapter logger; + + @Override + public void onEnable() { + logger = new JulLoggerAdapter(getLogger()); + + plugin.init(); + + getCommand("azlink").setExecutor(new BukkitCommandExecutor(plugin)); + } + + @Override + public void onDisable() { + plugin.shutdown(); + } + + @Override + public AzLinkPlugin getPlugin() { + return plugin; + } + + @Override + public LoggerAdapter getLoggerAdapter() { + return logger; + } + + @Override + public PlatformType getPlatformType() { + return PlatformType.BUKKIT; + } + + @Override + public String getPlatformName() { + return getServer().getName(); + } + + @Override + public String getPlatformVersion() { + return getServer().getVersion(); + } + + @Override + public String getPluginVersion() { + return getDescription().getVersion(); + } + + @Override + public Path getDataDirectory() { + return getDataFolder().toPath(); + } + + @Override + public Optional getWorldData() { + int loadedChunks = getServer().getWorlds().stream() + .mapToInt(w -> w.getLoadedChunks().length) + .sum(); + + int entities = getServer().getWorlds().stream().mapToInt(w -> w.getEntities().size()).sum(); + + return Optional.of(new WorldData(0, loadedChunks, entities)); + } + + @Override + public Stream getOnlinePlayers() { + return getServer().getOnlinePlayers().stream().map(BukkitCommandSender::new); + } + + @Override + public int getMaxPlayers() { + return getServer().getMaxPlayers(); + } + + @Override + public void dispatchConsoleCommand(String command) { + getServer().dispatchCommand(getServer().getConsoleSender(), command); + } + + @Override + public void executeSync(Runnable runnable) { + getServer().getScheduler().runTask(this, runnable); + } + + @Override + public void executeAsync(Runnable runnable) { + getServer().getScheduler().runTaskAsynchronously(this, runnable); + } +} diff --git a/bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandExecutor.java b/bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandExecutor.java new file mode 100644 index 0000000..1026959 --- /dev/null +++ b/bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandExecutor.java @@ -0,0 +1,28 @@ +package com.azuriom.azlink.bukkit.command; + +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.command.AzLinkCommand; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; + +import java.util.List; + +public class BukkitCommandExecutor extends AzLinkCommand implements TabExecutor { + + public BukkitCommandExecutor(AzLinkPlugin plugin) { + super(plugin); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + execute(new BukkitCommandSender(sender), args); + + return true; + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return tabComplete(new BukkitCommandSender(sender), args); + } +} diff --git a/bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandSender.java b/bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandSender.java new file mode 100644 index 0000000..5a84275 --- /dev/null +++ b/bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandSender.java @@ -0,0 +1,39 @@ +package com.azuriom.azlink.bukkit.command; + +import com.azuriom.azlink.common.command.CommandSender; +import org.bukkit.entity.Entity; + +import java.util.UUID; + +public class BukkitCommandSender implements CommandSender { + + private final org.bukkit.command.CommandSender sender; + + public BukkitCommandSender(org.bukkit.command.CommandSender sender) { + this.sender = sender; + } + + @Override + public String getName() { + return sender.getName(); + } + + @Override + public UUID getUuid() { + if (sender instanceof Entity) { + return ((Entity) sender).getUniqueId(); + } + + return UUID.nameUUIDFromBytes(getName().getBytes()); + } + + @Override + public void sendMessage(String message) { + sender.sendMessage(message); + } + + @Override + public boolean hasPermission(String permission) { + return sender.hasPermission(permission); + } +} diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml new file mode 100644 index 0000000..4889acb --- /dev/null +++ b/bukkit/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: AzLink +version: ${pluginVersion} +author: Azuriom Team +description: The plugin to link your Azuriom website with your server. +website: https://azuriom.com +main: com.azuriom.azlink.bukkit.AzLinkBukkitPlugin +api-version: 1.13 +commands: + azlink: + description: Manage the AzLink plugin. + usage: / [status|key|url] + aliases: [azuriomlink] \ No newline at end of file diff --git a/bungee/build.gradle b/bungee/build.gradle new file mode 100644 index 0000000..b398103 --- /dev/null +++ b/bungee/build.gradle @@ -0,0 +1,10 @@ +dependencies { + compileOnly 'net.md-5:bungeecord-api:1.14-SNAPSHOT' + compile project(':azlink-common') +} + +processResources { + from(sourceSets.main.resources.srcDirs) { + expand 'pluginVersion': project.version + } +} diff --git a/bungee/src/main/java/com/azuriom/azlink/bungee/AzLinkBungeePlugin.java b/bungee/src/main/java/com/azuriom/azlink/bungee/AzLinkBungeePlugin.java new file mode 100644 index 0000000..0db7dd1 --- /dev/null +++ b/bungee/src/main/java/com/azuriom/azlink/bungee/AzLinkBungeePlugin.java @@ -0,0 +1,90 @@ +package com.azuriom.azlink.bungee; + +import com.azuriom.azlink.bungee.command.BungeeCommandExecutor; +import com.azuriom.azlink.bungee.command.BungeeCommandSender; +import com.azuriom.azlink.common.AzLinkPlatform; +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.PlatformType; +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.logger.JulLoggerAdapter; +import com.azuriom.azlink.common.logger.LoggerAdapter; +import net.md_5.bungee.api.plugin.Plugin; + +import java.nio.file.Path; +import java.util.stream.Stream; + +public final class AzLinkBungeePlugin extends Plugin implements AzLinkPlatform { + + private final AzLinkPlugin plugin = new AzLinkPlugin(this); + + private LoggerAdapter loggerAdapter; + + @Override + public void onEnable() { + loggerAdapter = new JulLoggerAdapter(getLogger()); + + plugin.init(); + + getProxy().getPluginManager().registerCommand(this, new BungeeCommandExecutor(plugin)); + } + + @Override + public void onDisable() { + plugin.shutdown(); + } + + @Override + public AzLinkPlugin getPlugin() { + return plugin; + } + + @Override + public LoggerAdapter getLoggerAdapter() { + return loggerAdapter; + } + + @Override + public PlatformType getPlatformType() { + return PlatformType.BUNGEE; + } + + @Override + public String getPlatformName() { + return getProxy().getName(); + } + + @Override + public String getPlatformVersion() { + return getProxy().getVersion(); + } + + @Override + public String getPluginVersion() { + return getDescription().getVersion(); + } + + @Override + public Path getDataDirectory() { + return getDataFolder().toPath(); + } + + @Override + public Stream getOnlinePlayers() { + return getProxy().getPlayers().stream().map(BungeeCommandSender::new); + } + + @Override + public void dispatchConsoleCommand(String command) { + getProxy().getPluginManager().dispatchCommand(getProxy().getConsole(), command); + } + + @Override + public int getMaxPlayers() { + return getProxy().getConfig().getPlayerLimit(); + } + + @Override + public void executeAsync(Runnable runnable) { + getProxy().getScheduler().runAsync(this, runnable); + } +} diff --git a/bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandExecutor.java b/bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandExecutor.java new file mode 100644 index 0000000..e259084 --- /dev/null +++ b/bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandExecutor.java @@ -0,0 +1,28 @@ +package com.azuriom.azlink.bungee.command; + +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.command.AzLinkCommand; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.TabExecutor; + +public class BungeeCommandExecutor extends Command implements TabExecutor { + + private final AzLinkCommand command; + + public BungeeCommandExecutor(AzLinkPlugin plugin) { + super("gazlink", "azlink.admin", "gazuriomlink"); + + command = new AzLinkCommand(plugin); + } + + @Override + public void execute(CommandSender sender, String[] args) { + command.execute(new BungeeCommandSender(sender), args); + } + + @Override + public Iterable onTabComplete(CommandSender sender, String[] args) { + return command.tabComplete(new BungeeCommandSender(sender), args); + } +} diff --git a/bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandSender.java b/bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandSender.java new file mode 100644 index 0000000..f6e7a0d --- /dev/null +++ b/bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandSender.java @@ -0,0 +1,40 @@ +package com.azuriom.azlink.bungee.command; + +import com.azuriom.azlink.common.command.CommandSender; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.util.UUID; + +public class BungeeCommandSender implements CommandSender { + + private final net.md_5.bungee.api.CommandSender sender; + + public BungeeCommandSender(net.md_5.bungee.api.CommandSender sender) { + this.sender = sender; + } + + @Override + public String getName() { + return sender.getName(); + } + + @Override + public UUID getUuid() { + if (sender instanceof ProxiedPlayer) { + return ((ProxiedPlayer) sender).getUniqueId(); + } + + return UUID.nameUUIDFromBytes(getName().getBytes()); + } + + @Override + public void sendMessage(String message) { + sender.sendMessage(TextComponent.fromLegacyText(message)); + } + + @Override + public boolean hasPermission(String permission) { + return sender.hasPermission(permission); + } +} diff --git a/bungee/src/main/resources/bungee.yml b/bungee/src/main/resources/bungee.yml new file mode 100644 index 0000000..b856c90 --- /dev/null +++ b/bungee/src/main/resources/bungee.yml @@ -0,0 +1,5 @@ +name: AzLink +version: ${pluginVersion} +author: Azuriom Team +description: The plugin to link your Azuriom website with your server. +main: com.azuriom.azlink.bungee.AzLinkBungeePlugin diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..d8760e1 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compileOnly 'org.slf4j:slf4j-api:1.7.28' + compile 'com.squareup.okhttp3:okhttp:3.14.3' +} diff --git a/common/src/main/java/com/azuriom/azlink/common/AzLinkPlatform.java b/common/src/main/java/com/azuriom/azlink/common/AzLinkPlatform.java new file mode 100644 index 0000000..a2bd98f --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/AzLinkPlatform.java @@ -0,0 +1,47 @@ +package com.azuriom.azlink.common; + +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.data.PlatformData; +import com.azuriom.azlink.common.data.WorldData; +import com.azuriom.azlink.common.logger.LoggerAdapter; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; + +public interface AzLinkPlatform { + + AzLinkPlugin getPlugin(); + + LoggerAdapter getLoggerAdapter(); + + PlatformType getPlatformType(); + + String getPlatformName(); + + String getPlatformVersion(); + + String getPluginVersion(); + + Path getDataDirectory(); + + Stream getOnlinePlayers(); + + int getMaxPlayers(); + + default Optional getWorldData() { + return Optional.empty(); + } + + void dispatchConsoleCommand(String command); + + default void executeSync(Runnable runnable) { + executeAsync(runnable); + } + + void executeAsync(Runnable runnable); + + default PlatformData getPlatformData() { + return new PlatformData(getPlatformType(), getPlatformName(), getPlatformVersion()); + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/AzLinkPlugin.java b/common/src/main/java/com/azuriom/azlink/common/AzLinkPlugin.java new file mode 100644 index 0000000..a351d5c --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/AzLinkPlugin.java @@ -0,0 +1,159 @@ +package com.azuriom.azlink.common; + +import com.azuriom.azlink.common.command.AzLinkCommand; +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.config.PluginConfig; +import com.azuriom.azlink.common.data.PlatformData; +import com.azuriom.azlink.common.data.PlayerData; +import com.azuriom.azlink.common.data.ServerData; +import com.azuriom.azlink.common.data.SystemData; +import com.azuriom.azlink.common.data.WorldData; +import com.azuriom.azlink.common.http.HttpClient; +import com.azuriom.azlink.common.scheduler.ThreadBuilder; +import com.azuriom.azlink.common.tasks.FetcherTask; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class AzLinkPlugin { + + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> new ThreadBuilder(r).name("azlink-thread").daemon().build()); + + private final HttpClient httpClient = new HttpClient(this); + + private final Gson gson = new Gson(); + private final Gson gsonPrettyPrint = new GsonBuilder().setPrettyPrinting().create(); + + private final AzLinkCommand command = new AzLinkCommand(this); + + private final FetcherTask fetcherTask = new FetcherTask(this); + + private final AzLinkPlatform platform; + + private Path configFile; + private PluginConfig config = new PluginConfig(null, null); + + private boolean logCpuError = true; + + public AzLinkPlugin(AzLinkPlatform platform) { + this.platform = platform; + } + + public void init() { + configFile = platform.getDataDirectory().resolve("config.json"); + + try (BufferedReader reader = Files.newBufferedReader(configFile)) { + config = gson.fromJson(reader, PluginConfig.class); + } catch (NoSuchFileException e) { + // ignore, not setup yet + } catch (IOException e) { + platform.getLoggerAdapter().error("Error while loading configuration", e); + return; + } + + LocalDateTime start = LocalDateTime.now().truncatedTo(ChronoUnit.HOURS).plusHours(1); + long startDelay = Duration.between(LocalDateTime.now(), start).toMillis(); + + scheduler.scheduleAtFixedRate(fetcherTask, startDelay, TimeUnit.HOURS.toMillis(1), TimeUnit.MILLISECONDS); + + if (!config.isValid()) { + platform.getLoggerAdapter().warn("Invalid configuration, you can use '/azlink' to setup the plugin."); + return; + } + + platform.executeAsync(() -> { + try { + httpClient.getStatus(); + } catch (IOException e) { + platform.getLoggerAdapter().warn("Unable to connect", e); + } + }); + } + + public void shutdown() { + scheduler.shutdown(); + } + + public void saveConfig(PluginConfig config) throws IOException { + this.config = config; + + if (!Files.isDirectory(platform.getDataDirectory())) { + Files.createDirectories(platform.getDataDirectory()); + } + + try (BufferedWriter writer = Files.newBufferedWriter(configFile)) { + gsonPrettyPrint.toJson(config, writer); + } + } + + public AzLinkCommand getCommand() { + return command; + } + + public ServerData getServerData() { + List players = platform.getOnlinePlayers() + .map(CommandSender::toData) + .collect(Collectors.toList()); + + PlatformData platformData = platform.getPlatformData(); + SystemData system = new SystemData(getMemoryUsage(), getCpuUsage()); + WorldData world = platform.getWorldData().orElse(null); + int max = platform.getMaxPlayers(); + + return new ServerData(platformData, platform.getPluginVersion(), players, max, system, world); + } + + public double getCpuUsage() { + try { + if (ManagementFactory.getOperatingSystemMXBean() instanceof com.sun.management.OperatingSystemMXBean) { + return ((com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getProcessCpuLoad(); + } + } catch (Throwable t) { + if (logCpuError) { + logCpuError = false; + + platform.getLoggerAdapter().warn("Error while retrieving cpu usage", t); + } + } + return -1; + } + + public double getMemoryUsage() { + return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024.0 / 1024.0; + } + + public PluginConfig getConfig() { + return config; + } + + public AzLinkPlatform getPlatform() { + return platform; + } + + public HttpClient getHttpClient() { + return httpClient; + } + + public Gson getGson() { + return gson; + } + + public Gson getGsonPrettyPrint() { + return gsonPrettyPrint; + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/PlatformType.java b/common/src/main/java/com/azuriom/azlink/common/PlatformType.java new file mode 100644 index 0000000..f2111b9 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/PlatformType.java @@ -0,0 +1,6 @@ +package com.azuriom.azlink.common; + +public enum PlatformType { + + BUKKIT, BUNGEE, SPONGE, VELOCITY +} diff --git a/common/src/main/java/com/azuriom/azlink/common/command/AzLinkCommand.java b/common/src/main/java/com/azuriom/azlink/common/command/AzLinkCommand.java new file mode 100644 index 0000000..a6d929c --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/command/AzLinkCommand.java @@ -0,0 +1,68 @@ +package com.azuriom.azlink.common.command; + +import com.azuriom.azlink.common.AzLinkPlugin; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class AzLinkCommand { + + private static final String[] COMPLETIONS = {"status", "setup", "key", "site"}; + + private final AzLinkPlugin plugin; + + public AzLinkCommand(AzLinkPlugin plugin) { + this.plugin = plugin; + } + + public void execute(CommandSender sender, String[] args) { + if (args.length == 0 || !sender.hasPermission("azlink.admin")) { + sendUsage(sender); + return; + } + + if (args[0].equalsIgnoreCase("setup")) { + // TODO + return; + } + + if (args[0].equalsIgnoreCase("status")) { + // TODO + return; + } + + if (args[0].equalsIgnoreCase("key")) { + // TODO + return; + } + + if (args[0].equalsIgnoreCase("site")) { + // TODO + return; + } + + sendUsage(sender); + } + + public List tabComplete(CommandSender sender, String[] args) { + if (!sender.hasPermission("azlink.admin")) { + return Collections.emptyList(); + } + + if (args.length == 1) { + return Stream.of(COMPLETIONS).filter(s -> startsWithIgnoreCase(args[0], s)).collect(Collectors.toList()); + } + + return Collections.emptyList(); + } + + private void sendUsage(CommandSender sender) { + // TODO + } + + private static boolean startsWithIgnoreCase(String string, String prefix) { + return string.length() >= prefix.length() && string.regionMatches(true, 0, prefix, 0, prefix.length()); + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/command/CommandSender.java b/common/src/main/java/com/azuriom/azlink/common/command/CommandSender.java new file mode 100644 index 0000000..3515dc5 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/command/CommandSender.java @@ -0,0 +1,20 @@ +package com.azuriom.azlink.common.command; + +import com.azuriom.azlink.common.data.PlayerData; + +import java.util.UUID; + +public interface CommandSender { + + String getName(); + + UUID getUuid(); + + void sendMessage(String message); + + boolean hasPermission(String permission); + + default PlayerData toData() { + return new PlayerData(getName(), getUuid()); + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/config/PluginConfig.java b/common/src/main/java/com/azuriom/azlink/common/config/PluginConfig.java new file mode 100644 index 0000000..3add04e --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/config/PluginConfig.java @@ -0,0 +1,24 @@ +package com.azuriom.azlink.common.config; + +public class PluginConfig { + + private final String siteKey; + private final String siteUrl; + + public PluginConfig(String siteKey, String siteUrl) { + this.siteKey = siteKey; + this.siteUrl = siteUrl; + } + + public String getSiteKey() { + return siteKey; + } + + public String getSiteUrl() { + return siteUrl; + } + + public boolean isValid() { + return siteKey != null && !siteKey.isEmpty() && siteUrl != null && !siteUrl.isEmpty(); + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/data/PlatformData.java b/common/src/main/java/com/azuriom/azlink/common/data/PlatformData.java new file mode 100644 index 0000000..e8f9044 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/data/PlatformData.java @@ -0,0 +1,28 @@ +package com.azuriom.azlink.common.data; + +import com.azuriom.azlink.common.PlatformType; + +public class PlatformData { + + private final PlatformType type; + private final String name; + private final String version; + + public PlatformData(PlatformType type, String name, String version) { + this.type = type; + this.name = name; + this.version = version; + } + + public PlatformType getType() { + return type; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/data/PlayerData.java b/common/src/main/java/com/azuriom/azlink/common/data/PlayerData.java new file mode 100644 index 0000000..6c2c421 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/data/PlayerData.java @@ -0,0 +1,22 @@ +package com.azuriom.azlink.common.data; + +import java.util.UUID; + +public class PlayerData { + + private final String name; + private final UUID uuid; + + public PlayerData(String name, UUID uuid) { + this.name = name; + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public UUID getUuid() { + return uuid; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/azuriom/azlink/common/data/ServerData.java b/common/src/main/java/com/azuriom/azlink/common/data/ServerData.java new file mode 100644 index 0000000..22e22ce --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/data/ServerData.java @@ -0,0 +1,50 @@ +package com.azuriom.azlink.common.data; + +import com.azuriom.azlink.common.PlatformType; + +import java.util.List; + +public class ServerData { + + private final PlatformData platform; + private final String version; + + private final List players; + private final int maxPlayers; + + private final SystemData system; + private final WorldData worlds; + + public ServerData(PlatformData platform, String version, List players, int maxPlayers, SystemData system, WorldData worlds) { + this.platform = platform; + this.version = version; + this.players = players; + this.maxPlayers = maxPlayers; + this.system = system; + this.worlds = worlds; + } + + public PlatformData getPlatform() { + return platform; + } + + public String getVersion() { + return version; + } + + public List getPlayers() { + return players; + } + + public int getMaxPlayers() { + return maxPlayers; + } + + public SystemData getSystem() { + return system; + } + + public WorldData getWorlds() { + return worlds; + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/data/SystemData.java b/common/src/main/java/com/azuriom/azlink/common/data/SystemData.java new file mode 100644 index 0000000..3a52885 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/data/SystemData.java @@ -0,0 +1,20 @@ +package com.azuriom.azlink.common.data; + +public class SystemData { + + private final double memory; + private final double cpu; + + public SystemData(double memory, double cpu) { + this.memory = memory; + this.cpu = cpu; + } + + public double getMemory() { + return memory; + } + + public double getCpu() { + return cpu; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/azuriom/azlink/common/data/WebsiteResponse.java b/common/src/main/java/com/azuriom/azlink/common/data/WebsiteResponse.java new file mode 100644 index 0000000..0b9fa98 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/data/WebsiteResponse.java @@ -0,0 +1,17 @@ +package com.azuriom.azlink.common.data; + +import java.util.HashMap; +import java.util.Map; + +public class WebsiteResponse { + + private final Map commands = new HashMap<>(); + + public WebsiteResponse(Map commands) { + this.commands.putAll(commands); + } + + public Map getCommands() { + return commands; + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/data/WorldData.java b/common/src/main/java/com/azuriom/azlink/common/data/WorldData.java new file mode 100644 index 0000000..5a240a3 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/data/WorldData.java @@ -0,0 +1,26 @@ +package com.azuriom.azlink.common.data; + +public class WorldData { + + private final int tps; + private final int chunks; + private final int entities; + + public WorldData(int tps, int chunks, int entities) { + this.tps = tps; + this.chunks = chunks; + this.entities = entities; + } + + public int getTps() { + return tps; + } + + public int getChunks() { + return chunks; + } + + public int getEntities() { + return entities; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/azuriom/azlink/common/http/HttpClient.java b/common/src/main/java/com/azuriom/azlink/common/http/HttpClient.java new file mode 100644 index 0000000..a6783dc --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/http/HttpClient.java @@ -0,0 +1,81 @@ +package com.azuriom.azlink.common.http; + +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.data.ServerData; +import com.azuriom.azlink.common.data.WebsiteResponse; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class HttpClient { + + private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8"); + + private final OkHttpClient httpClient = new OkHttpClient.Builder() + .addInterceptor(chain -> chain.proceed(addHeadersToRequest(chain.request()))) + .build(); + + private final AzLinkPlugin plugin; + + public HttpClient(AzLinkPlugin plugin) { + this.plugin = plugin; + } + + public Response getStatus() throws IOException { + return makeCall(new Request.Builder().url(getSiteUrl()).build()); + } + + public WebsiteResponse postData(ServerData data) throws IOException { + Request request = new Request.Builder().url(getSiteUrl()) + .post(RequestBody.create(JSON_TYPE, data.toString())) + .build(); + + try (Response response = makeCall(request)) { + try (ResponseBody body = response.body()) { + if (body == null) { + throw new RuntimeException("No body in response"); + } + + try (InputStream is = body.byteStream()) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + return plugin.getGson().fromJson(reader, WebsiteResponse.class); + } + } + } + } + } + + public Response makeCall(Request request) throws IOException { + Response response = httpClient.newCall(request).execute(); + + if (!response.isSuccessful()) { + throw new IOException("Invalid response: " + response.code() + " (" + response.message() + ")"); + } + + return response; + } + + private Request addHeadersToRequest(Request request) { + byte[] key = plugin.getConfig().getSiteKey().getBytes(StandardCharsets.UTF_8); + String keyEncoded = Base64.getEncoder().encodeToString(key); + + return request.newBuilder() + .header("Authorization", "Basic " + keyEncoded) + .header("User-Agent", "AzLink v" + plugin.getPlatform().getPluginVersion()) + .build(); + } + + private String getSiteUrl() { + return plugin.getConfig().getSiteUrl() + "/api/v1/azlink"; + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/logger/JulLoggerAdapter.java b/common/src/main/java/com/azuriom/azlink/common/logger/JulLoggerAdapter.java new file mode 100644 index 0000000..0d6ad03 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/logger/JulLoggerAdapter.java @@ -0,0 +1,43 @@ +package com.azuriom.azlink.common.logger; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JulLoggerAdapter implements LoggerAdapter{ + + private final Logger logger; + + public JulLoggerAdapter(Logger logger) { + this.logger = logger; + } + + @Override + public void info(String message) { + logger.info(message); + } + + @Override + public void info(String message, Throwable throwable) { + logger.log(Level.INFO, message, throwable); + } + + @Override + public void warn(String message) { + logger.warning(message); + } + + @Override + public void warn(String message, Throwable throwable) { + logger.log(Level.WARNING, message, throwable); + } + + @Override + public void error(String message) { + logger.severe(message); + } + + @Override + public void error(String message, Throwable throwable) { + logger.log(Level.SEVERE, message, throwable); + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/logger/LoggerAdapter.java b/common/src/main/java/com/azuriom/azlink/common/logger/LoggerAdapter.java new file mode 100644 index 0000000..178fc62 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/logger/LoggerAdapter.java @@ -0,0 +1,16 @@ +package com.azuriom.azlink.common.logger; + +public interface LoggerAdapter { + + void info(String message); + + void info(String message, Throwable throwable); + + void warn(String message); + + void warn(String message, Throwable throwable); + + void error(String message); + + void error(String message, Throwable throwable); +} diff --git a/common/src/main/java/com/azuriom/azlink/common/logger/Slf4jLoggerAdapter.java b/common/src/main/java/com/azuriom/azlink/common/logger/Slf4jLoggerAdapter.java new file mode 100644 index 0000000..28f2080 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/logger/Slf4jLoggerAdapter.java @@ -0,0 +1,42 @@ +package com.azuriom.azlink.common.logger; + +import org.slf4j.Logger; + +public class Slf4jLoggerAdapter implements LoggerAdapter { + + private final Logger logger; + + public Slf4jLoggerAdapter(Logger logger) { + this.logger = logger; + } + + @Override + public void info(String message) { + logger.info(message); + } + + @Override + public void info(String message, Throwable throwable) { + logger.info(message, throwable); + } + + @Override + public void warn(String message) { + logger.warn(message); + } + + @Override + public void warn(String message, Throwable throwable) { + logger.warn(message, throwable); + } + + @Override + public void error(String message) { + logger.error(message); + } + + @Override + public void error(String message, Throwable throwable) { + logger.error(message, throwable); + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/scheduler/Cancellable.java b/common/src/main/java/com/azuriom/azlink/common/scheduler/Cancellable.java new file mode 100644 index 0000000..9cb30fd --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/scheduler/Cancellable.java @@ -0,0 +1,8 @@ +package com.azuriom.azlink.common.scheduler; + +@FunctionalInterface +public interface Cancellable { + + void cancel(); + +} diff --git a/common/src/main/java/com/azuriom/azlink/common/scheduler/ThreadBuilder.java b/common/src/main/java/com/azuriom/azlink/common/scheduler/ThreadBuilder.java new file mode 100644 index 0000000..7df5ae8 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/scheduler/ThreadBuilder.java @@ -0,0 +1,39 @@ +package com.azuriom.azlink.common.scheduler; + +import java.util.concurrent.Executors; + +public class ThreadBuilder { + + private final Thread thread; + + public ThreadBuilder(Runnable runnable) { + this(Executors.defaultThreadFactory().newThread(runnable)); + } + + public ThreadBuilder(Thread thread) { + this.thread = thread; + } + + public ThreadBuilder name(String name) { + thread.setName(name); + return this; + } + + public ThreadBuilder daemon() { + return daemon(true); + } + + public ThreadBuilder daemon(boolean daemon) { + thread.setDaemon(daemon); + return this; + } + + public ThreadBuilder setPriority(int priority) { + thread.setPriority(priority); + return this; + } + + public Thread build() { + return thread; + } +} diff --git a/common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java b/common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java new file mode 100644 index 0000000..45c0023 --- /dev/null +++ b/common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java @@ -0,0 +1,58 @@ +package com.azuriom.azlink.common.tasks; + +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.data.ServerData; +import com.azuriom.azlink.common.data.WebsiteResponse; + +import java.io.IOException; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class FetcherTask implements Runnable { + + private final AzLinkPlugin plugin; + + public FetcherTask(AzLinkPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + plugin.getPlatform().executeSync(() -> { + ServerData data = plugin.getServerData(); + + plugin.getPlatform().executeAsync(() -> sendData(data)); + }); + } + + private void sendData(ServerData data) { + try { + WebsiteResponse response = plugin.getHttpClient().postData(data); + + if (response.getCommands().isEmpty()) { + return; + } + + plugin.getPlatform().getLoggerAdapter().info("Dispatching " + response.getCommands() + " commands."); + + Map players = plugin.getPlatform() + .getOnlinePlayers() + .collect(Collectors.toMap(CommandSender::getName, Function.identity())); + + for (Map.Entry entry : response.getCommands().entrySet()) { + CommandSender player = players.get(entry.getKey()); + String command = entry.getValue() + .replace("{name}", player.getName()) + .replace("{uuid}", player.getUuid().toString()); + + plugin.getPlatform().getLoggerAdapter().info("Dispatching command for player " + player.getName() + ": " + command); + + plugin.getPlatform().dispatchConsoleCommand(command); + } + } catch (IOException e) { + plugin.getPlatform().getLoggerAdapter().error("Unable to send data to website", e); + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..87b738c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bba3ea3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Nov 13 23:43:12 CET 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..af6708f --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6d57edc --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..bc9da64 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,6 @@ +rootProject.name = 'azlink' + +['common', 'bukkit', 'bungee', 'sponge', 'velocity', 'universal', 'universal-legacy'].each { + include ":azlink-${it}" + project(":azlink-${it}").projectDir = file(it) +} diff --git a/sponge/build.gradle b/sponge/build.gradle new file mode 100644 index 0000000..4706f7f --- /dev/null +++ b/sponge/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'net.kyori.blossom' version '1.1.0' +} + +repositories { + maven { url 'https://repo.spongepowered.org/maven/' } +} + +dependencies { + compileOnly 'org.spongepowered:spongeapi:7.1.0' + compile project(':azlink-common') +} + +blossom { + replaceToken '${pluginVersion}', project.version, 'src/main/java/com/azuriom/azlink/sponge/AzLinkSpongePlugin.java' +} diff --git a/sponge/src/main/java/com/azuriom/azlink/sponge/AzLinkSpongePlugin.java b/sponge/src/main/java/com/azuriom/azlink/sponge/AzLinkSpongePlugin.java new file mode 100644 index 0000000..125fcef --- /dev/null +++ b/sponge/src/main/java/com/azuriom/azlink/sponge/AzLinkSpongePlugin.java @@ -0,0 +1,129 @@ +package com.azuriom.azlink.sponge; + +import com.azuriom.azlink.common.AzLinkPlatform; +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.PlatformType; +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.data.WorldData; +import com.azuriom.azlink.common.logger.LoggerAdapter; +import com.azuriom.azlink.common.logger.Slf4jLoggerAdapter; +import com.azuriom.azlink.sponge.command.SpongeCommandExecutor; +import com.azuriom.azlink.sponge.command.SpongeCommandSender; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.spongepowered.api.Game; +import org.spongepowered.api.Platform; +import org.spongepowered.api.config.ConfigDir; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.game.state.GamePreInitializationEvent; +import org.spongepowered.api.plugin.Plugin; +import org.spongepowered.api.scheduler.Task; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; + +@Plugin( + id = "azlink", + name = "AzLink", + version = "${pluginVersion}", + description = "The plugin to link your Azuriom website with your server.", + url = "https://azuriom.com", + authors = "Azuriom Team" +) +public final class AzLinkSpongePlugin implements AzLinkPlatform { + + private final AzLinkPlugin plugin = new AzLinkPlugin(this); + + private final Game game; + + private final Path configDirectory; + + private final LoggerAdapter logger; + + @Inject + public AzLinkSpongePlugin(Game game, @ConfigDir(sharedRoot = false) Path configDirectory, Logger logger) { + this.game = game; + this.configDirectory = configDirectory; + this.logger = new Slf4jLoggerAdapter(logger); + } + + @Listener + public void onGamePreInitialization(GamePreInitializationEvent event) { + plugin.init(); + + game.getCommandManager().register(this, new SpongeCommandExecutor(plugin), "azlink", "azuriomlink"); + } + + @Override + public AzLinkPlugin getPlugin() { + return plugin; + } + + @Override + public LoggerAdapter getLoggerAdapter() { + return logger; + } + + @Override + public PlatformType getPlatformType() { + return PlatformType.SPONGE; + } + + @Override + public String getPlatformName() { + return game.getPlatform().getContainer(Platform.Component.IMPLEMENTATION).getName(); + } + + @Override + public String getPlatformVersion() { + return game.getPlatform().getContainer(Platform.Component.IMPLEMENTATION).getVersion().orElse("unknown"); + } + + @Override + public String getPluginVersion() { + return "${pluginVersion}"; + } + + @Override + public Path getDataDirectory() { + return configDirectory; + } + + @Override + public Optional getWorldData() { + int loadedChunks = game.getServer().getWorlds().stream() + .mapToInt(w -> Iterables.size(w.getLoadedChunks())) + .sum(); + + int entities = game.getServer().getWorlds().stream().mapToInt(w -> w.getEntities().size()).sum(); + + return Optional.of(new WorldData(0, loadedChunks, entities)); + } + + @Override + public Stream getOnlinePlayers() { + return game.getServer().getOnlinePlayers().stream().map(SpongeCommandSender::new); + } + + @Override + public void dispatchConsoleCommand(String command) { + game.getCommandManager().process(game.getServer().getConsole(), command); + } + + @Override + public int getMaxPlayers() { + return game.getServer().getMaxPlayers(); + } + + @Override + public void executeSync(Runnable runnable) { + Task.builder().execute(runnable).submit(this); + } + + @Override + public void executeAsync(Runnable runnable) { + Task.builder().execute(runnable).async().submit(this); + } +} diff --git a/sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandExecutor.java b/sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandExecutor.java new file mode 100644 index 0000000..dcd9e1d --- /dev/null +++ b/sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandExecutor.java @@ -0,0 +1,60 @@ +package com.azuriom.azlink.sponge.command; + +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.command.AzLinkCommand; +import org.spongepowered.api.command.CommandCallable; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; + +public class SpongeCommandExecutor extends AzLinkCommand implements CommandCallable { + + public SpongeCommandExecutor(AzLinkPlugin plugin) { + super(plugin); + } + + @Override + @Nonnull + public CommandResult process(@Nonnull CommandSource source, String arguments) throws CommandException { + execute(new SpongeCommandSender(source), arguments.split(" ")); + + return CommandResult.success(); + } + + @Override + @Nonnull + public List getSuggestions(@Nonnull CommandSource source, String arguments, @Nullable Location targetPosition) throws CommandException { + return tabComplete(new SpongeCommandSender(source), arguments.split(" ")); + } + + @Override + public boolean testPermission(CommandSource source) { + return source.hasPermission("azlink.admin"); + } + + @Override + @Nonnull + public Optional getShortDescription(@Nonnull CommandSource source) { + return Optional.of(Text.of("Manage the AzLink plugin.")); + } + + @Override + @Nonnull + public Optional getHelp(@Nonnull CommandSource source) { + return Optional.empty(); + } + + @Override + @Nonnull + public Text getUsage(@Nonnull CommandSource source) { + return Text.of("Usage: /azlink [status|key|url]"); + } +} diff --git a/sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandSender.java b/sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandSender.java new file mode 100644 index 0000000..d1f7a8d --- /dev/null +++ b/sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandSender.java @@ -0,0 +1,41 @@ +package com.azuriom.azlink.sponge.command; + +import com.azuriom.azlink.common.command.CommandSender; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.text.serializer.TextSerializers; +import org.spongepowered.api.util.Identifiable; + +import java.util.UUID; + +public class SpongeCommandSender implements CommandSender { + + private final CommandSource source; + + public SpongeCommandSender(CommandSource source) { + this.source = source; + } + + @Override + public String getName() { + return source.getName(); + } + + @Override + public UUID getUuid() { + if (source instanceof Identifiable) { + return ((Identifiable) source).getUniqueId(); + } + + return UUID.nameUUIDFromBytes(getName().getBytes()); + } + + @Override + public void sendMessage(String message) { + source.sendMessage(TextSerializers.FORMATTING_CODE.deserialize(message.replace('\u00A7', '&'))); + } + + @Override + public boolean hasPermission(String permission) { + return source.hasPermission(permission); + } +} diff --git a/universal-legacy/build.gradle b/universal-legacy/build.gradle new file mode 100644 index 0000000..e15141c --- /dev/null +++ b/universal-legacy/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +dependencies { + compile project(':azlink-common') + compile project(':azlink-bukkit') + compile project(':azlink-bungee') + compile project(':azlink-sponge') + + compile 'com.google.code.gson:gson:2.8.5' +} + +shadowJar { + archiveFileName = "AzLink-Legacy-${project.version}.jar" + + relocate 'okio', 'com.azuriom.azlink.libs.okio' + relocate 'okhttp3', 'com.azuriom.azlink.libs.okhttp3' +} + +artifacts { + archives shadowJar +} diff --git a/universal/build.gradle b/universal/build.gradle new file mode 100644 index 0000000..bc80b94 --- /dev/null +++ b/universal/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +dependencies { + compile project(':azlink-common') + compile project(':azlink-bukkit') + compile project(':azlink-bungee') + compile project(':azlink-sponge') +} + +shadowJar { + archiveFileName = "AzLink-${project.version}.jar" + + relocate 'okio', 'com.azuriom.azlink.libs.okio' + relocate 'okhttp3', 'com.azuriom.azlink.libs.okhttp3' +} + +artifacts { + archives shadowJar +} diff --git a/velocity/build.gradle b/velocity/build.gradle new file mode 100644 index 0000000..216855d --- /dev/null +++ b/velocity/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'net.kyori.blossom' version '1.1.0' +} + +repositories { + maven { url 'https://repo.velocitypowered.com/snapshots/' } +} + +dependencies { + compileOnly 'com.velocitypowered:velocity-api:1.0.4-SNAPSHOT' + annotationProcessor 'com.velocitypowered:velocity-api:1.0.4-SNAPSHOT' + compile project(':azlink-common') +} + +blossom { + replaceToken '${pluginVersion}', project.version, 'src/main/java/com/azuriom/azlink/velocity/AzLinkVelocityPlugin.java' +} diff --git a/velocity/src/main/java/com/azuriom/azlink/velocity/AzLinkVelocityPlugin.java b/velocity/src/main/java/com/azuriom/azlink/velocity/AzLinkVelocityPlugin.java new file mode 100644 index 0000000..52b8339 --- /dev/null +++ b/velocity/src/main/java/com/azuriom/azlink/velocity/AzLinkVelocityPlugin.java @@ -0,0 +1,108 @@ +package com.azuriom.azlink.velocity; + +import com.azuriom.azlink.common.AzLinkPlatform; +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.PlatformType; +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.logger.LoggerAdapter; +import com.azuriom.azlink.common.logger.Slf4jLoggerAdapter; +import com.azuriom.azlink.velocity.command.VelocityCommandExecutor; +import com.azuriom.azlink.velocity.command.VelocityCommandSender; +import com.google.inject.Inject; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import org.slf4j.Logger; + +import java.nio.file.Path; +import java.util.stream.Stream; + +@Plugin( + id = "azlink", + name = "AzLink", + version = "${pluginVersion}", + description = "The plugin to link your Azuriom website with your server.", + url = "https://azuriom.com", + authors = "Azuriom Team" +) +public final class AzLinkVelocityPlugin implements AzLinkPlatform { + + private final AzLinkPlugin plugin = new AzLinkPlugin(this); + + private final ProxyServer server; + + private final Path dataDirectory; + + private final LoggerAdapter logger; + + @Inject + public AzLinkVelocityPlugin(ProxyServer server, @DataDirectory Path dataDirectory, Logger logger) { + this.server = server; + this.dataDirectory = dataDirectory; + this.logger = new Slf4jLoggerAdapter(logger); + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + plugin.init(); + + server.getCommandManager().register(new VelocityCommandExecutor(plugin), "gazlink", "gazuriomlink"); + } + + @Override + public AzLinkPlugin getPlugin() { + return plugin; + } + + @Override + public LoggerAdapter getLoggerAdapter() { + return logger; + } + + @Override + public PlatformType getPlatformType() { + return PlatformType.VELOCITY; + } + + @Override + public String getPlatformName() { + return server.getVersion().getName(); + } + + @Override + public String getPlatformVersion() { + return server.getVersion().getVersion(); + } + + @Override + public String getPluginVersion() { + return "${pluginVersion}"; + } + + @Override + public Path getDataDirectory() { + return dataDirectory; + } + + @Override + public Stream getOnlinePlayers() { + return server.getAllPlayers().stream().map(VelocityCommandSender::new); + } + + @Override + public int getMaxPlayers() { + return server.getConfiguration().getShowMaxPlayers(); + } + + @Override + public void dispatchConsoleCommand(String command) { + server.getCommandManager().execute(server.getConsoleCommandSource(), command); + } + + @Override + public void executeAsync(Runnable runnable) { + server.getScheduler().buildTask(this, runnable).schedule(); + } +} diff --git a/velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandExecutor.java b/velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandExecutor.java new file mode 100644 index 0000000..764c756 --- /dev/null +++ b/velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandExecutor.java @@ -0,0 +1,26 @@ +package com.azuriom.azlink.velocity.command; + +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.command.AzLinkCommand; +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; + +public class VelocityCommandExecutor extends AzLinkCommand implements Command { + + public VelocityCommandExecutor(AzLinkPlugin plugin) { + super(plugin); + } + + @Override + public void execute(CommandSource source, @NonNull String[] args) { + execute(new VelocityCommandSender(source), args); + } + + @Override + public List suggest(CommandSource source, @NonNull String[] currentArgs) { + return tabComplete(new VelocityCommandSender(source), currentArgs); + } +} diff --git a/velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandSender.java b/velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandSender.java new file mode 100644 index 0000000..d25ca56 --- /dev/null +++ b/velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandSender.java @@ -0,0 +1,41 @@ +package com.azuriom.azlink.velocity.command; + +import com.azuriom.azlink.common.command.CommandSender; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.UUID; + +public class VelocityCommandSender implements CommandSender { + + private final CommandSource source; + + public VelocityCommandSender(CommandSource source) { + this.source = source; + } + + @Override + public String getName() { + return source instanceof Player ? ((Player) source).getUsername() : "Console"; + } + + @Override + public UUID getUuid() { + if (source instanceof Player) { + return ((Player) source).getUniqueId(); + } + + return UUID.nameUUIDFromBytes(getName().getBytes()); + } + + @Override + public void sendMessage(String message) { + source.sendMessage(LegacyComponentSerializer.legacyLinking().deserialize(message)); + } + + @Override + public boolean hasPermission(String permission) { + return source.hasPermission(permission); + } +}