├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── bukkit ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── azuriom │ │ └── azlink │ │ └── bukkit │ │ ├── AzLinkBukkitPlugin.java │ │ ├── command │ │ ├── BukkitCommandExecutor.java │ │ └── BukkitCommandSender.java │ │ ├── injector │ │ ├── HttpDecoder.java │ │ ├── InjectedHttpServer.java │ │ └── NettyLibraryLoader.java │ │ └── integrations │ │ ├── AuthMeIntegration.java │ │ ├── FoliaSchedulerAdapter.java │ │ ├── MoneyPlaceholderExpansion.java │ │ ├── NLoginIntegration.java │ │ └── SkinsRestorerIntegration.java │ └── resources │ ├── config.yml │ └── plugin.yml ├── bungee ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── azuriom │ │ └── azlink │ │ └── bungee │ │ ├── AzLinkBungeePlugin.java │ │ ├── BungeeSchedulerAdapter.java │ │ ├── command │ │ ├── BungeeCommandExecutor.java │ │ └── BungeeCommandSender.java │ │ └── integrations │ │ ├── NLoginIntegration.java │ │ └── SkinsRestorerIntegration.java │ └── resources │ ├── bungee-config.yml │ └── bungee.yml ├── common ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── azuriom │ │ └── azlink │ │ └── common │ │ ├── AzLinkPlatform.java │ │ ├── AzLinkPlugin.java │ │ ├── command │ │ ├── AzLinkCommand.java │ │ └── CommandSender.java │ │ ├── config │ │ └── PluginConfig.java │ │ ├── data │ │ ├── PlatformData.java │ │ ├── PlayerData.java │ │ ├── ServerData.java │ │ ├── SystemData.java │ │ ├── UserInfo.java │ │ ├── WebsiteResponse.java │ │ └── WorldData.java │ │ ├── http │ │ ├── client │ │ │ └── HttpClient.java │ │ └── server │ │ │ ├── HttpChannelInitializer.java │ │ │ ├── HttpHandler.java │ │ │ ├── HttpServer.java │ │ │ └── NettyHttpServer.java │ │ ├── integrations │ │ ├── BaseNLogin.java │ │ └── BaseSkinsRestorer.java │ │ ├── logger │ │ ├── JavaLoggerAdapter.java │ │ ├── LoggerAdapter.java │ │ └── Slf4jLoggerAdapter.java │ │ ├── platform │ │ ├── PlatformInfo.java │ │ └── PlatformType.java │ │ ├── scheduler │ │ ├── CancellableTask.java │ │ ├── JavaSchedulerAdapter.java │ │ ├── SchedulerAdapter.java │ │ └── ThreadFactoryBuilder.java │ │ ├── tasks │ │ ├── FetcherTask.java │ │ └── TpsTask.java │ │ ├── users │ │ ├── EditMoneyResult.java │ │ ├── MoneyAction.java │ │ └── UserManager.java │ │ └── utils │ │ ├── Hash.java │ │ ├── SystemUtils.java │ │ └── UpdateChecker.java │ └── test │ └── java │ └── com │ └── azuriom │ └── azlink │ └── common │ ├── HashTest.java │ ├── ThreadFactoryBuilderTest.java │ └── UpdateCheckerTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── nukkit ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── azuriom │ │ └── azlink │ │ └── nukkit │ │ ├── AzLinkNukkitPlugin.java │ │ ├── command │ │ ├── NukkitCommandExecutor.java │ │ └── NukkitCommandSender.java │ │ └── utils │ │ └── NukkitLoggerAdapter.java │ └── resources │ └── nukkit.yml ├── settings.gradle ├── sponge-legacy ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── azuriom │ └── azlink │ └── sponge │ └── legacy │ ├── AzLinkSpongePlugin.java │ └── command │ ├── SpongeCommandExecutor.java │ └── SpongeCommandSender.java ├── sponge ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── azuriom │ │ └── azlink │ │ └── sponge │ │ ├── AzLinkSpongePlugin.java │ │ ├── command │ │ ├── SpongeCommandExecutor.java │ │ └── SpongeCommandSender.java │ │ ├── integrations │ │ └── SkinsRestorerIntegration.java │ │ └── logger │ │ └── Log4jLoggerAdapter.java │ └── resources │ ├── META-INF │ └── sponge_plugins.json │ └── azlink.conf ├── universal-legacy └── build.gradle ├── universal └── build.gradle └── velocity ├── build.gradle └── src └── main ├── java └── com │ └── azuriom │ └── azlink │ └── velocity │ ├── AzLinkVelocityPlugin.java │ ├── VelocitySchedulerAdapter.java │ ├── command │ ├── VelocityCommandExecutor.java │ └── VelocityCommandSender.java │ └── integrations │ ├── LimboAuthIntegration.java │ ├── NLoginIntegration.java │ └── SkinsRestorerIntegration.java └── resources └── velocity-config.yml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | java-version: [ 17 ] 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup JDK ${{ matrix.java-version }} 22 | uses: actions/setup-java@v4 23 | with: 24 | distribution: temurin 25 | java-version: ${{ matrix.java-version }} 26 | 27 | - name: Validate Gradle Wrapper 28 | uses: gradle/actions/wrapper-validation@v3 29 | 30 | - name: Setup Gradle 31 | uses: gradle/actions/setup-gradle@v3 32 | 33 | - name: Build 34 | run: ./gradlew build 35 | 36 | - name: Upload AzLink.jar 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: AzLink 40 | path: universal/build/libs/AzLink-*.jar 41 | overwrite: true 42 | 43 | - name: Upload AzLink-Legacy.jar 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: AzLink-Legacy 47 | path: universal-legacy/build/libs/AzLink-Legacy-*.jar 48 | overwrite: true 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .metadata 5 | .settings/ 6 | 7 | # IntelliJ 8 | .idea/ 9 | .idea_modules/ 10 | *.iml 11 | *.iws 12 | /out/ 13 | 14 | # Mac 15 | .DS_Store 16 | 17 | # Gradle 18 | .gradle 19 | build/ 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 Azuriom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AzLink 2 | 3 | [![Tests](https://img.shields.io/github/actions/workflow/status/Azuriom/AzLink/build.yml?branch=master&style=flat-square)](https://github.com/Azuriom/AzLink/actions/workflows/build.yml) 4 | [![Chat](https://img.shields.io/discord/625774284823986183?color=5865f2&label=Discord&logo=discord&logoColor=fff&style=flat-square)](https://azuriom.com/discord) 5 | 6 | AzLink is a plugin to link a Minecraft server or proxy with [Azuriom](https://azuriom.com/). 7 | 8 | This plugin currently supports the following platforms: 9 | * [Bukkit/Spigot/Paper/Folia](https://papermc.io/) 10 | * [BungeeCord](https://github.com/SpigotMC/BungeeCord) 11 | * [Sponge](https://www.spongepowered.org/) 12 | * [Velocity](https://velocitypowered.com/) 13 | * [Nukkit](https://cloudburstmc.org/articles/) 14 | 15 | ## Setup 16 | 17 | ### Installation 18 | 19 | The plugin works with the same .jar for all the platforms, except Bukkit/Spigot 1.7.10 which requires the legacy version of the plugin. 20 | 21 | You just need to download the plugin, add it to the `plugins` folder of your server, and restart your server. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | group 'com.azuriom' 3 | version '1.3.8' 4 | } 5 | 6 | subprojects { 7 | apply plugin: 'java' 8 | 9 | java { 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | } 13 | 14 | tasks.withType(JavaCompile) { 15 | options.encoding = 'UTF-8' 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bukkit/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 3 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 4 | maven { url 'https://repo.codemc.io/repository/maven-public/' } 5 | maven { url 'https://repo.nickuc.com/maven-releases/' } 6 | maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } 7 | } 8 | 9 | dependencies { 10 | implementation project(':azlink-common') 11 | compileOnly 'dev.folia:folia-api:1.19.4-R0.1-SNAPSHOT' 12 | compileOnly 'io.netty:netty-all:4.1.25.Final' 13 | compileOnly 'fr.xephi:authme:5.6.0-beta2' 14 | compileOnly 'com.nickuc.login:nlogin-api:10.3' 15 | compileOnly 'me.clip:placeholderapi:2.11.6' 16 | } 17 | 18 | java { 19 | // Folia is compiled with Java 17 20 | disableAutoTargetJvm() 21 | } 22 | 23 | processResources { 24 | filesMatching('*.yml') { 25 | expand 'pluginVersion': project.version 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/AzLinkBukkitPlugin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit; 2 | 3 | import com.azuriom.azlink.bukkit.command.BukkitCommandExecutor; 4 | import com.azuriom.azlink.bukkit.command.BukkitCommandSender; 5 | import com.azuriom.azlink.bukkit.injector.InjectedHttpServer; 6 | import com.azuriom.azlink.bukkit.injector.NettyLibraryLoader; 7 | import com.azuriom.azlink.bukkit.integrations.AuthMeIntegration; 8 | import com.azuriom.azlink.bukkit.integrations.FoliaSchedulerAdapter; 9 | import com.azuriom.azlink.bukkit.integrations.MoneyPlaceholderExpansion; 10 | import com.azuriom.azlink.bukkit.integrations.SkinsRestorerIntegration; 11 | import com.azuriom.azlink.bukkit.integrations.NLoginIntegration; 12 | import com.azuriom.azlink.common.AzLinkPlatform; 13 | import com.azuriom.azlink.common.AzLinkPlugin; 14 | import com.azuriom.azlink.common.command.CommandSender; 15 | import com.azuriom.azlink.common.data.WorldData; 16 | import com.azuriom.azlink.common.http.server.HttpServer; 17 | import com.azuriom.azlink.common.logger.JavaLoggerAdapter; 18 | import com.azuriom.azlink.common.logger.LoggerAdapter; 19 | import com.azuriom.azlink.common.platform.PlatformInfo; 20 | import com.azuriom.azlink.common.platform.PlatformType; 21 | import com.azuriom.azlink.common.scheduler.JavaSchedulerAdapter; 22 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 23 | import com.azuriom.azlink.common.tasks.TpsTask; 24 | import org.bukkit.entity.Player; 25 | import org.bukkit.metadata.MetadataValue; 26 | import org.bukkit.plugin.java.JavaPlugin; 27 | 28 | import java.nio.file.Path; 29 | import java.util.Optional; 30 | import java.util.stream.Stream; 31 | 32 | public final class AzLinkBukkitPlugin extends JavaPlugin implements AzLinkPlatform { 33 | 34 | private final TpsTask tpsTask = new TpsTask(); 35 | private final SchedulerAdapter scheduler = createSchedulerAdapter(); 36 | 37 | private AzLinkPlugin plugin; 38 | private LoggerAdapter logger; 39 | 40 | @Override 41 | public void onLoad() { 42 | this.logger = new JavaLoggerAdapter(getLogger()); 43 | } 44 | 45 | @Override 46 | public void onEnable() { 47 | try { 48 | Class.forName("com.google.gson.JsonObject"); 49 | Class.forName("io.netty.channel.Channel"); 50 | } catch (ClassNotFoundException e) { 51 | this.logger.error("Your server version is not compatible with this version of AzLink."); 52 | this.logger.error("Please download AzLink Legacy on https://azuriom.com/azlink"); 53 | getServer().getPluginManager().disablePlugin(this); 54 | return; 55 | } 56 | 57 | this.plugin = new AzLinkPlugin(this) { 58 | @Override 59 | protected HttpServer createHttpServer() { 60 | NettyLibraryLoader libraryLoader = new NettyLibraryLoader(this); 61 | 62 | try { 63 | libraryLoader.loadRequiredLibraries(); 64 | } catch (Exception e) { 65 | getLogger().error("Unable to load required libraries for instant commands", e); 66 | return null; 67 | } 68 | 69 | if (plugin.getConfig().getHttpPort() == getServer().getPort()) { 70 | return new InjectedHttpServer(AzLinkBukkitPlugin.this); 71 | } 72 | 73 | return super.createHttpServer(); 74 | } 75 | }; 76 | 77 | saveDefaultConfig(); 78 | 79 | this.plugin.init(); 80 | 81 | getCommand("azlink").setExecutor(new BukkitCommandExecutor(this.plugin)); 82 | 83 | scheduleTpsTask(); 84 | 85 | if (getConfig().getBoolean("authme-integration") 86 | && getServer().getPluginManager().getPlugin("AuthMe") != null) { 87 | getServer().getPluginManager().registerEvents(new AuthMeIntegration(this), this); 88 | } 89 | 90 | if (getConfig().getBoolean("nlogin-integration") 91 | && getServer().getPluginManager().getPlugin("nLogin") != null) { 92 | NLoginIntegration.register(this); 93 | } 94 | 95 | if (getConfig().getBoolean("skinrestorer-integration") 96 | && getServer().getPluginManager().getPlugin("SkinsRestorer") != null) { 97 | try { 98 | Class.forName("net.skinsrestorer.api.SkinsRestorer"); 99 | 100 | getServer().getPluginManager().registerEvents(new SkinsRestorerIntegration(this), this); 101 | } catch (ClassNotFoundException e) { 102 | getLogger().severe("SkinsRestorer integration requires SkinsRestorer v15.0.0 or higher"); 103 | } 104 | } 105 | 106 | if (getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) { 107 | MoneyPlaceholderExpansion.enable(this); 108 | } 109 | } 110 | 111 | @Override 112 | public void onDisable() { 113 | if (this.plugin != null) { 114 | this.plugin.shutdown(); 115 | } 116 | } 117 | 118 | @Override 119 | public AzLinkPlugin getPlugin() { 120 | return this.plugin; 121 | } 122 | 123 | @Override 124 | public LoggerAdapter getLoggerAdapter() { 125 | return this.logger; 126 | } 127 | 128 | @Override 129 | public SchedulerAdapter getSchedulerAdapter() { 130 | return this.scheduler; 131 | } 132 | 133 | @Override 134 | public PlatformType getPlatformType() { 135 | return PlatformType.BUKKIT; 136 | } 137 | 138 | @Override 139 | public PlatformInfo getPlatformInfo() { 140 | return new PlatformInfo(getServer().getName(), getServer().getVersion()); 141 | } 142 | 143 | @Override 144 | @SuppressWarnings("deprecation") // Folia support 145 | public String getPluginVersion() { 146 | return getDescription().getVersion(); 147 | } 148 | 149 | @Override 150 | public Path getDataDirectory() { 151 | return getDataFolder().toPath(); 152 | } 153 | 154 | @Override 155 | public Optional getWorldData() { 156 | int loadedChunks = getServer().getWorlds().stream() 157 | .mapToInt(w -> w.getLoadedChunks().length) 158 | .sum(); 159 | 160 | // Prevent 'Accessing entity state off owning region's thread' exception on Folia 161 | int entities = isFolia() ? 0 : getServer().getWorlds().stream() 162 | .mapToInt(w -> w.getEntities().size()) 163 | .sum(); 164 | 165 | return Optional.of(new WorldData(this.tpsTask.getTps(), loadedChunks, entities)); 166 | } 167 | 168 | @Override 169 | public Stream getOnlinePlayers() { 170 | if (getConfig().getBoolean("ignore-vanished-players", false)) { 171 | return getServer().getOnlinePlayers() 172 | .stream() 173 | .filter(this::isPlayerVisible) 174 | .map(BukkitCommandSender::new); 175 | } 176 | 177 | return getServer().getOnlinePlayers().stream().map(BukkitCommandSender::new); 178 | } 179 | 180 | @Override 181 | public int getMaxPlayers() { 182 | return getServer().getMaxPlayers(); 183 | } 184 | 185 | @Override 186 | public void dispatchConsoleCommand(String command) { 187 | getServer().dispatchCommand(getServer().getConsoleSender(), command); 188 | } 189 | 190 | private boolean isPlayerVisible(Player player) { 191 | for (MetadataValue meta : player.getMetadata("vanished")) { 192 | if (meta.asBoolean()) { 193 | return false; 194 | } 195 | } 196 | return true; 197 | } 198 | 199 | private void scheduleTpsTask() { 200 | if (isFolia()) { 201 | FoliaSchedulerAdapter.scheduleSyncTask(this, this.tpsTask, 1, 1); 202 | 203 | return; 204 | } 205 | 206 | getServer().getScheduler().runTaskTimer(this, this.tpsTask, 1, 1); 207 | } 208 | 209 | private SchedulerAdapter createSchedulerAdapter() { 210 | try { 211 | Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask"); 212 | 213 | return FoliaSchedulerAdapter.create(this); 214 | } catch (ClassNotFoundException e) { 215 | return new JavaSchedulerAdapter( 216 | r -> getServer().getScheduler().runTask(this, r), 217 | r -> getServer().getScheduler().runTaskAsynchronously(this, r) 218 | ); 219 | } 220 | } 221 | 222 | private boolean isFolia() { 223 | return this.scheduler instanceof FoliaSchedulerAdapter; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.command; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.command.AzLinkCommand; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.command.TabExecutor; 8 | 9 | import java.util.List; 10 | 11 | public class BukkitCommandExecutor extends AzLinkCommand implements TabExecutor { 12 | 13 | public BukkitCommandExecutor(AzLinkPlugin plugin) { 14 | super(plugin); 15 | } 16 | 17 | @Override 18 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 19 | execute(new BukkitCommandSender(sender), args); 20 | 21 | return true; 22 | } 23 | 24 | @Override 25 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 26 | return tabComplete(new BukkitCommandSender(sender), args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/command/BukkitCommandSender.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.command; 2 | 3 | import com.azuriom.azlink.common.command.CommandSender; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.entity.Entity; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.UUID; 9 | 10 | public class BukkitCommandSender implements CommandSender { 11 | 12 | private final org.bukkit.command.CommandSender sender; 13 | 14 | public BukkitCommandSender(org.bukkit.command.CommandSender sender) { 15 | this.sender = sender; 16 | } 17 | 18 | @Override 19 | public String getName() { 20 | return this.sender.getName(); 21 | } 22 | 23 | @Override 24 | public UUID getUuid() { 25 | if (this.sender instanceof Entity) { 26 | return ((Entity) this.sender).getUniqueId(); 27 | } 28 | 29 | return UUID.nameUUIDFromBytes(getName().getBytes(StandardCharsets.UTF_8)); 30 | } 31 | 32 | @Override 33 | public void sendMessage(String message) { 34 | this.sender.sendMessage(ChatColor.translateAlternateColorCodes('&', message)); 35 | } 36 | 37 | @Override 38 | public boolean hasPermission(String permission) { 39 | return this.sender.hasPermission(permission); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/injector/HttpDecoder.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.injector; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.http.server.HttpHandler; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.handler.codec.ByteToMessageDecoder; 10 | import io.netty.handler.codec.http.HttpObjectAggregator; 11 | import io.netty.handler.codec.http.HttpServerCodec; 12 | 13 | import java.util.List; 14 | import java.util.NoSuchElementException; 15 | 16 | /** 17 | * This file is based on JSONAPI by Alec Gorge, under the MIT license. 18 | * 19 | * https://github.com/alecgorge/jsonapi/blob/master/src/main/java/com/alecgorge/minecraft/jsonapi/packets/netty/JSONAPIChannelDecoder.java 20 | */ 21 | public class HttpDecoder extends ByteToMessageDecoder { 22 | 23 | private final AzLinkPlugin plugin; 24 | 25 | public HttpDecoder(AzLinkPlugin plugin) { 26 | this.plugin = plugin; 27 | } 28 | 29 | @Override 30 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 31 | // use 4 bytes to detect HTTP or abort 32 | if (in.readableBytes() < 4) { 33 | return; 34 | } 35 | 36 | in.retain(2); 37 | in.retain(2); 38 | 39 | int magic1 = in.getUnsignedByte(in.readerIndex()); 40 | int magic2 = in.getUnsignedByte(in.readerIndex() + 1); 41 | int magic3 = in.getUnsignedByte(in.readerIndex() + 2); 42 | int magic4 = in.getUnsignedByte(in.readerIndex() + 3); 43 | ChannelPipeline pipeline = ctx.channel().pipeline(); 44 | 45 | if (!isHttp(magic1, magic2, magic3, magic4)) { 46 | try { 47 | pipeline.remove(this); 48 | } catch (NoSuchElementException e) { 49 | // probably okay, it just needs to be off 50 | } 51 | 52 | in.release(); 53 | in.release(); 54 | 55 | return; 56 | } 57 | 58 | ByteBuf copy = in.copy(); 59 | ctx.channel().config().setOption(ChannelOption.TCP_NODELAY, true); 60 | 61 | try { 62 | while (pipeline.removeLast() != null); 63 | } catch (NoSuchElementException e) { 64 | // ignore 65 | } 66 | 67 | pipeline.addLast("codec-http", new HttpServerCodec()); 68 | pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); 69 | pipeline.addLast("handler", new HttpHandler(this.plugin)); 70 | 71 | pipeline.fireChannelRead(copy); 72 | in.release(); 73 | in.release(); 74 | } 75 | 76 | private boolean isHttp(int magic1, int magic2, int magic3, int magic4) { 77 | return magic1 == 'G' && magic2 == 'E' && magic3 == 'T' && magic4 == ' ' || // GET 78 | magic1 == 'P' && magic2 == 'O' && magic3 == 'S' && magic4 == 'T'; // POST 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/injector/InjectedHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.injector; 2 | 3 | import com.azuriom.azlink.bukkit.AzLinkBukkitPlugin; 4 | import com.azuriom.azlink.common.http.server.HttpServer; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.ChannelInboundHandler; 10 | import io.netty.channel.ChannelInboundHandlerAdapter; 11 | import io.netty.channel.ChannelInitializer; 12 | import org.bukkit.Bukkit; 13 | 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.Method; 16 | import java.util.List; 17 | import java.util.NoSuchElementException; 18 | 19 | public class InjectedHttpServer implements HttpServer { 20 | 21 | private final ChannelHandler serverChannelHandler = createChannelHandler(); 22 | 23 | private final AzLinkBukkitPlugin plugin; 24 | 25 | private ChannelFuture serverChannel; 26 | 27 | public InjectedHttpServer(AzLinkBukkitPlugin plugin) { 28 | this.plugin = plugin; 29 | } 30 | 31 | @Override 32 | public void start() { 33 | // Make sure Netty isn't relocated 34 | if (!HttpDecoder.class.getSuperclass().getName().startsWith("io.")) { 35 | plugin.getLoggerAdapter().error("Injecting HTTP server on server channel is not supported with AzLink legacy."); 36 | return; 37 | } 38 | 39 | if (!Bukkit.getServer().getClass().getSimpleName().equals("CraftServer")) { 40 | plugin.getLoggerAdapter().error("Injecting HTTP server on server channel is only supported on CraftBukkit based servers. You can use an other port for AzLink."); 41 | return; 42 | } 43 | 44 | try { 45 | inject(); 46 | } catch (Exception e) { 47 | plugin.getLoggerAdapter().error("Unable to inject HTTP server. Try using a different port for AzLink", e); 48 | } 49 | } 50 | 51 | @Override 52 | public void stop() { 53 | try { 54 | uninject(); 55 | } catch (Exception e) { 56 | this.plugin.getLoggerAdapter().error("An error occurred while removing HTTP server", e); 57 | } 58 | } 59 | 60 | private void inject() throws Exception { 61 | Object craftServer = Bukkit.getServer(); 62 | Method serverGetHandle = craftServer.getClass().getMethod("getServer"); 63 | 64 | Object minecraftServer = serverGetHandle.invoke(Bukkit.getServer()); 65 | Method getServerConnection = getServerConnectionMethod(minecraftServer); 66 | 67 | Object serverConnection = getServerConnection.invoke(minecraftServer); 68 | 69 | for (Field field : serverConnection.getClass().getDeclaredFields()) { 70 | if (field.getType() != List.class) { 71 | continue; 72 | } 73 | 74 | field.setAccessible(true); 75 | 76 | List list = (List) field.get(serverConnection); 77 | 78 | for (Object item : list) { 79 | if (!(item instanceof ChannelFuture)) { 80 | break; // Not the good list, try the next one 81 | } 82 | 83 | injectServerChannel((ChannelFuture) item); 84 | 85 | plugin.getLoggerAdapter().info("HTTP server successfully injected."); 86 | 87 | return; 88 | } 89 | } 90 | 91 | throw new IllegalStateException("Unable to find server channel, try disabling late bind"); 92 | } 93 | 94 | public void uninject() { 95 | if (this.serverChannel == null) { 96 | return; // We are not injected 97 | } 98 | 99 | Channel channel = serverChannel.channel(); 100 | 101 | channel.eventLoop().submit(() -> { 102 | try { 103 | channel.pipeline().remove(this.serverChannelHandler); 104 | } catch (NoSuchElementException e) { 105 | // ignore 106 | } 107 | }); 108 | 109 | serverChannel = null; 110 | } 111 | 112 | private void injectServerChannel(ChannelFuture future) { 113 | future.channel().pipeline().addFirst(this.serverChannelHandler); 114 | 115 | this.serverChannel = future; 116 | } 117 | 118 | private Method getServerConnectionMethod(Object minecraftServer) throws NoSuchMethodException { 119 | Class serverClass = minecraftServer.getClass(); 120 | 121 | try { 122 | return serverClass.getMethod("getServerConnection"); 123 | } catch (NoSuchMethodException e) { 124 | for (Method method : serverClass.getMethods()) { 125 | String type = method.getReturnType().getSimpleName(); 126 | 127 | if (type.equals("ServerConnection") || type.equals("ServerConnectionListener")) { 128 | return method; 129 | } 130 | } 131 | } 132 | 133 | throw new NoSuchMethodException("Unable to find server connection method in " + serverClass.getName()); 134 | } 135 | 136 | private ChannelHandler createChannelHandler() { 137 | // Handle connected channels 138 | ChannelInboundHandler endInitProtocol = new ChannelInitializer() { 139 | @Override 140 | protected void initChannel(Channel channel) { 141 | try { 142 | channel.eventLoop().submit(() -> channel.pipeline().addFirst(new HttpDecoder(plugin.getPlugin()))); 143 | } catch (Exception e) { 144 | plugin.getLoggerAdapter().error("Unable to init channel", e); 145 | } 146 | } 147 | }; 148 | 149 | // This is executed before Minecraft's channel handler 150 | ChannelInboundHandler beginInitProtocol = new ChannelInitializer() { 151 | @Override 152 | protected void initChannel(Channel channel) { 153 | // Our only job is to add init protocol 154 | channel.pipeline().addLast(endInitProtocol); 155 | } 156 | }; 157 | 158 | return new ChannelInboundHandlerAdapter() { 159 | @Override 160 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 161 | Channel channel = (Channel) msg; 162 | 163 | // Prepare to initialize this channel 164 | channel.pipeline().addFirst(beginInitProtocol); 165 | 166 | ctx.fireChannelRead(msg); 167 | } 168 | }; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/injector/NettyLibraryLoader.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.injector; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import io.netty.util.Version; 5 | 6 | import java.io.InputStream; 7 | import java.lang.reflect.Field; 8 | import java.net.URI; 9 | import java.net.URL; 10 | import java.net.URLClassLoader; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | 14 | /** 15 | * Since Spigot 1.19, netty-codec-http is no longer included, so we need to manually load it. 16 | *

17 | * We are not including it in the plugin.yml 'libraries' list, because it's not needed for all Minecraft versions, 18 | * and the Netty version changes between Minecraft versions. 19 | */ 20 | public class NettyLibraryLoader { 21 | 22 | private static final String MAVEN_CENTRAL = "https://repo1.maven.org/maven2/%s/%s/%s/%s-%s.jar"; 23 | 24 | private final AzLinkPlugin plugin; 25 | private final Path libsFolder; 26 | 27 | public NettyLibraryLoader(AzLinkPlugin plugin) { 28 | this.plugin = plugin; 29 | this.libsFolder = plugin.getPlatform().getDataDirectory().resolve("libs"); 30 | } 31 | 32 | public void loadRequiredLibraries() throws Exception { 33 | try { 34 | Class.forName("io.netty.handler.codec.http.HttpServerCodec"); 35 | // netty-codec-http is already loaded, all good 36 | return; 37 | } catch (ClassNotFoundException e) { 38 | // manually load netty-codec-http below 39 | } 40 | 41 | this.plugin.getLogger().info("Loading netty-codec-http..."); 42 | 43 | loadLibrary("io.netty", "netty-codec-http", identifyNettyVersion()); 44 | 45 | this.plugin.getLogger().info("Loaded netty-codec-http successfully."); 46 | } 47 | 48 | private void loadLibrary(String groupId, String artifactId, String version) throws Exception { 49 | Path jar = this.libsFolder.resolve(artifactId + "-" + version + ".jar"); 50 | 51 | if (!Files.exists(jar)) { 52 | Files.createDirectories(jar.getParent()); 53 | 54 | this.plugin.getLogger().warn("Downloading " + artifactId + " v" + version + "..."); 55 | 56 | String url = String.format(MAVEN_CENTRAL, groupId.replace('.', '/'), artifactId, version, artifactId, version); 57 | 58 | try (InputStream in = URI.create(url).toURL().openStream()) { 59 | Files.copy(in, jar); 60 | } 61 | 62 | this.plugin.getLogger().info("Successfully downloaded " + artifactId + "."); 63 | } 64 | 65 | URL[] urls = {jar.toUri().toURL()}; 66 | ClassLoader classLoader = plugin.getClass().getClassLoader(); 67 | Field classLoaderField = classLoader.getClass().getDeclaredField("libraryLoader"); 68 | 69 | classLoaderField.setAccessible(true); 70 | classLoaderField.set(classLoader, new URLClassLoader(urls, classLoader.getParent())); 71 | } 72 | 73 | private String identifyNettyVersion() { 74 | Version version = Version.identify().get("netty-codec"); 75 | 76 | return version != null ? version.artifactVersion() : "4.1.97.Final"; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/integrations/AuthMeIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.integrations; 2 | 3 | import com.azuriom.azlink.bukkit.AzLinkBukkitPlugin; 4 | import fr.xephi.authme.api.v3.AuthMeApi; 5 | import fr.xephi.authme.api.v3.AuthMePlayer; 6 | import fr.xephi.authme.datasource.DataSource; 7 | import fr.xephi.authme.events.EmailChangedEvent; 8 | import fr.xephi.authme.events.PasswordEncryptionEvent; 9 | import fr.xephi.authme.events.RegisterEvent; 10 | import fr.xephi.authme.security.crypts.EncryptionMethod; 11 | import fr.xephi.authme.security.crypts.HashedPassword; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.event.EventHandler; 14 | import org.bukkit.event.EventPriority; 15 | import org.bukkit.event.Listener; 16 | 17 | import java.lang.reflect.Field; 18 | import java.net.InetAddress; 19 | import java.net.InetSocketAddress; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | public class AuthMeIntegration implements Listener { 25 | 26 | private final Map passwords = new HashMap<>(); 27 | 28 | private final AzLinkBukkitPlugin plugin; 29 | private DataSource dataSource; 30 | 31 | public AuthMeIntegration(AzLinkBukkitPlugin plugin) { 32 | this.plugin = plugin; 33 | 34 | this.plugin.getLoggerAdapter().info("AuthMe integration enabled."); 35 | 36 | this.plugin.getPlugin().getScheduler().executeSync(() -> { 37 | try { 38 | Field field = AuthMeApi.class.getDeclaredField("dataSource"); 39 | field.setAccessible(true); 40 | 41 | this.dataSource = (DataSource) field.get(AuthMeApi.getInstance()); 42 | } catch (ReflectiveOperationException e) { 43 | throw new UnsupportedOperationException("Unable to register AuthMe integration", e); 44 | } 45 | }); 46 | } 47 | 48 | @EventHandler(priority = EventPriority.HIGHEST) 49 | public void onPasswordEncryption(PasswordEncryptionEvent event) { 50 | event.setMethod(new ForwardingEncryptionMethod(event.getMethod())); 51 | } 52 | 53 | @EventHandler 54 | public void onEmailChanged(EmailChangedEvent event) { 55 | Player player = event.getPlayer(); 56 | 57 | this.plugin.getPlugin() 58 | .getHttpClient() 59 | .updateEmail(player.getUniqueId(), event.getNewEmail()) 60 | .exceptionally(ex -> { 61 | this.plugin.getLoggerAdapter().error("Unable to update email for " + player.getName(), ex); 62 | 63 | return null; 64 | }); 65 | } 66 | 67 | @EventHandler 68 | public void onRegister(RegisterEvent event) { 69 | Player player = event.getPlayer(); 70 | HashedPassword hashedPassword = this.dataSource.getPassword(event.getPlayer().getName()); 71 | String password = this.passwords.remove(hashedPassword.getHash()); 72 | String email = AuthMeApi.getInstance().getPlayerInfo(player.getName()) 73 | .flatMap(AuthMePlayer::getEmail) 74 | .orElse(null); 75 | InetSocketAddress address = player.getAddress(); 76 | InetAddress ip = address != null ? address.getAddress() : null; 77 | 78 | if (password == null) { 79 | this.plugin.getLoggerAdapter().warn("Unable to get password of " + player.getName()); 80 | return; 81 | } 82 | 83 | this.plugin.getPlugin() 84 | .getHttpClient() 85 | .registerUser(player.getName(), email, player.getUniqueId(), password, ip) 86 | .exceptionally(ex -> { 87 | this.plugin.getLoggerAdapter().error("Unable to register " + player.getName(), ex); 88 | 89 | return null; 90 | }); 91 | } 92 | 93 | private void handlePasswordHash(String playerName, String password, String hash) { 94 | Player player = this.plugin.getServer().getPlayer(playerName); 95 | 96 | if (player == null || !AuthMeApi.getInstance().isAuthenticated(player)) { 97 | this.passwords.put(hash, password); 98 | 99 | return; 100 | } 101 | 102 | this.plugin.getSchedulerAdapter().scheduleAsyncLater(() -> { 103 | HashedPassword hashedPassword = this.dataSource.getPassword(playerName); 104 | 105 | if (!hashedPassword.getHash().equals(hash)) { 106 | return; 107 | } 108 | 109 | this.plugin.getPlugin() 110 | .getHttpClient() 111 | .updatePassword(player.getUniqueId(), password) 112 | .exceptionally(ex -> { 113 | this.plugin.getLoggerAdapter().error("Unable to update password for " + player.getName(), ex); 114 | 115 | return null; 116 | }); 117 | }, 1, TimeUnit.SECONDS); 118 | } 119 | 120 | public class ForwardingEncryptionMethod implements EncryptionMethod { 121 | 122 | private final EncryptionMethod method; 123 | 124 | public ForwardingEncryptionMethod(EncryptionMethod method) { 125 | this.method = method; 126 | } 127 | 128 | @Override 129 | public HashedPassword computeHash(String password, String name) { 130 | HashedPassword hash = this.method.computeHash(password, name); 131 | handlePasswordHash(name, password, hash.getHash()); 132 | return hash; 133 | } 134 | 135 | @Override 136 | public String computeHash(String password, String salt, String name) { 137 | String hash = this.method.computeHash(password, salt, name); 138 | handlePasswordHash(name, password, hash); 139 | return hash; 140 | } 141 | 142 | @Override 143 | public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { 144 | return this.method.comparePassword(password, hashedPassword, name); 145 | } 146 | 147 | @Override 148 | public String generateSalt() { 149 | return this.method.generateSalt(); 150 | } 151 | 152 | @Override 153 | public boolean hasSeparateSalt() { 154 | return this.method.hasSeparateSalt(); 155 | } 156 | 157 | @Override 158 | public boolean equals(Object o) { 159 | return this.method.equals(o); 160 | } 161 | 162 | @Override 163 | public int hashCode() { 164 | return this.method.hashCode(); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/integrations/FoliaSchedulerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.integrations; 2 | 3 | import com.azuriom.azlink.common.scheduler.JavaSchedulerAdapter; 4 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 5 | import org.bukkit.plugin.Plugin; 6 | 7 | import java.util.concurrent.Executor; 8 | 9 | public class FoliaSchedulerAdapter extends JavaSchedulerAdapter { 10 | 11 | private FoliaSchedulerAdapter(Executor syncExecutor, Executor asyncExecutor) { 12 | super(syncExecutor, asyncExecutor); 13 | } 14 | 15 | public static void scheduleSyncTask(Plugin plugin, Runnable task, long delay, long interval) { 16 | plugin.getServer() 17 | .getGlobalRegionScheduler() 18 | .runAtFixedRate(plugin, t -> task.run(), delay, interval); 19 | } 20 | 21 | public static SchedulerAdapter create(Plugin plugin) { 22 | plugin.getLogger().info("Folia support enabled successfully."); 23 | 24 | return new FoliaSchedulerAdapter( 25 | r -> plugin.getServer().getGlobalRegionScheduler().execute(plugin, r), 26 | r -> plugin.getServer().getAsyncScheduler().runNow(plugin, t -> r.run()) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/integrations/MoneyPlaceholderExpansion.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.integrations; 2 | 3 | import com.azuriom.azlink.bukkit.AzLinkBukkitPlugin; 4 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 5 | import org.bukkit.OfflinePlayer; 6 | 7 | import java.text.DecimalFormat; 8 | import java.text.DecimalFormatSymbols; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Locale; 12 | 13 | public class MoneyPlaceholderExpansion extends PlaceholderExpansion { 14 | 15 | private static final DecimalFormat FORMATTER = createDecimalFormat(); 16 | 17 | private final AzLinkBukkitPlugin plugin; 18 | 19 | public MoneyPlaceholderExpansion(AzLinkBukkitPlugin plugin) { 20 | this.plugin = plugin; 21 | } 22 | 23 | @Override 24 | public String getName() { 25 | return this.plugin.getName(); 26 | } 27 | 28 | @Override 29 | public String getAuthor() { 30 | return String.join("Azuriom"); 31 | } 32 | 33 | @Override 34 | public String getIdentifier() { 35 | return "azlink"; 36 | } 37 | 38 | @Override 39 | public String getVersion() { 40 | return this.plugin.getPluginVersion(); 41 | } 42 | 43 | @Override 44 | public List getPlaceholders() { 45 | return Collections.singletonList("%azlink_money%"); 46 | } 47 | 48 | @Override 49 | public boolean persist() { 50 | return true; 51 | } 52 | 53 | @Override 54 | public String onRequest(OfflinePlayer player, String params) { 55 | if (params.equalsIgnoreCase("money")) { 56 | return this.plugin.getPlugin() 57 | .getUserManager() 58 | .getUserByName(player.getName()) 59 | .map(user -> FORMATTER.format(user.getMoney())) 60 | .orElse("?"); 61 | } 62 | 63 | return null; 64 | } 65 | 66 | public static void enable(AzLinkBukkitPlugin plugin) { 67 | if (new MoneyPlaceholderExpansion(plugin).register()) { 68 | plugin.getLogger().info("PlaceholderAPI expansion enabled"); 69 | } 70 | } 71 | 72 | private static DecimalFormat createDecimalFormat() { 73 | DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Locale.ROOT); 74 | return new DecimalFormat("0.##", symbols); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/integrations/NLoginIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.integrations; 2 | 3 | import com.azuriom.azlink.bukkit.AzLinkBukkitPlugin; 4 | import com.azuriom.azlink.common.integrations.BaseNLogin; 5 | import com.nickuc.login.api.enums.TwoFactorType; 6 | import com.nickuc.login.api.event.bukkit.account.PasswordUpdateEvent; 7 | import com.nickuc.login.api.event.bukkit.auth.RegisterEvent; 8 | import com.nickuc.login.api.event.bukkit.twofactor.TwoFactorAddEvent; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.EventHandler; 11 | import org.bukkit.event.Listener; 12 | 13 | import java.net.InetAddress; 14 | import java.net.InetSocketAddress; 15 | 16 | public class NLoginIntegration extends BaseNLogin implements Listener { 17 | 18 | public NLoginIntegration(AzLinkBukkitPlugin plugin) { 19 | super(plugin.getPlugin()); 20 | } 21 | 22 | @EventHandler 23 | public void onEmailAdded(TwoFactorAddEvent event) { 24 | if (event.getType() != TwoFactorType.EMAIL) { 25 | return; 26 | } 27 | 28 | handleEmailUpdated(event.getPlayerId(), event.getPlayerName(), event.getAccount()); 29 | } 30 | 31 | @EventHandler 32 | public void onRegister(RegisterEvent event) { 33 | Player player = event.getPlayer(); 34 | InetSocketAddress socketAddress = player.getAddress(); 35 | InetAddress address = socketAddress != null ? socketAddress.getAddress() : null; 36 | 37 | handleRegister(player.getUniqueId(), player.getName(), event.getPassword(), address); 38 | } 39 | 40 | @EventHandler 41 | public void onPasswordUpdate(PasswordUpdateEvent event) { 42 | handleUpdatePassword(event.getPlayerId(), event.getPlayerName(), event.getNewPassword()); 43 | } 44 | 45 | public static void register(AzLinkBukkitPlugin plugin) { 46 | if (ensureApiVersion(plugin)) { 47 | plugin.getServer().getPluginManager().registerEvents(new NLoginIntegration(plugin), plugin); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bukkit/src/main/java/com/azuriom/azlink/bukkit/integrations/SkinsRestorerIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bukkit.integrations; 2 | 3 | import com.azuriom.azlink.bukkit.AzLinkBukkitPlugin; 4 | import com.azuriom.azlink.common.integrations.BaseSkinsRestorer; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.player.PlayerJoinEvent; 9 | 10 | public class SkinsRestorerIntegration 11 | extends BaseSkinsRestorer implements Listener { 12 | 13 | public SkinsRestorerIntegration(AzLinkBukkitPlugin plugin) { 14 | super(plugin.getPlugin(), Player.class); 15 | } 16 | 17 | @EventHandler 18 | public void onPlayerJoin(PlayerJoinEvent e) { 19 | Player player = e.getPlayer(); 20 | 21 | handleJoin(player.getName(), player); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bukkit/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # 2 | # AzLink Bukkit config 3 | # 4 | 5 | # When enabled, vanished players will be ignored by AzLink: they won't be 6 | # included in players count and won't receive commands while vanished. 7 | # Try disabling this option if you have some issues. 8 | ignore-vanished-players: false 9 | 10 | # When enabled, new users registered with the AuthMe plugin will automatically be registered on the website 11 | # WARNING: You need AuthMeReloaded version 5.6.0-beta2 or newer AND Azuriom version 1.0.0 or newer! 12 | # If you are using multiples servers, you should use the same database for AuthMe to prevent users 13 | # from registering multiple times 14 | authme-integration: false 15 | 16 | # When enabled, new users registered with the nLogin plugin will automatically be registered on the website 17 | # WARNING: You need nLogin version 10.2.43 or newer! 18 | # If you are using multiples servers, you should use the same database for nLogin to prevent users 19 | # from registering multiple times 20 | nlogin-integration: false 21 | 22 | # When enabled, if SkinsRestorer is installed, and the SkinAPI plugin is present on the website, 23 | # the player's skin will be updated to the website's skin 24 | skinrestorer-integration: false 25 | -------------------------------------------------------------------------------- /bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: AzLink 2 | version: ${pluginVersion} 3 | author: Azuriom Team 4 | description: The plugin to link your Azuriom website with your server. 5 | website: https://azuriom.com 6 | main: com.azuriom.azlink.bukkit.AzLinkBukkitPlugin 7 | api-version: 1.13 8 | softdepend: [nLogin, PlaceholderAPI, SkinsRestorer] 9 | loadbefore: [AuthMe] 10 | folia-supported: true 11 | commands: 12 | azlink: 13 | description: Manage the AzLink plugin. 14 | usage: / [status|setup|fetch|port] 15 | aliases: [azuriomlink] 16 | 17 | permissions: 18 | azlink.admin: 19 | description: Allows you to use the /azlink command. 20 | default: op -------------------------------------------------------------------------------- /bungee/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 3 | maven { url 'https://repo.nickuc.com/maven-releases/' } 4 | } 5 | 6 | dependencies { 7 | implementation project(':azlink-common') 8 | compileOnly 'net.md-5:bungeecord-api:1.16-R0.4' 9 | compileOnly 'com.nickuc.login:nlogin-api:10.3' 10 | } 11 | 12 | processResources { 13 | filesMatching('*.yml') { 14 | expand 'pluginVersion': project.version 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bungee/src/main/java/com/azuriom/azlink/bungee/AzLinkBungeePlugin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bungee; 2 | 3 | import com.azuriom.azlink.bungee.command.BungeeCommandExecutor; 4 | import com.azuriom.azlink.bungee.command.BungeeCommandSender; 5 | import com.azuriom.azlink.bungee.integrations.NLoginIntegration; 6 | import com.azuriom.azlink.bungee.integrations.SkinsRestorerIntegration; 7 | import com.azuriom.azlink.common.AzLinkPlatform; 8 | import com.azuriom.azlink.common.AzLinkPlugin; 9 | import com.azuriom.azlink.common.command.CommandSender; 10 | import com.azuriom.azlink.common.logger.JavaLoggerAdapter; 11 | import com.azuriom.azlink.common.logger.LoggerAdapter; 12 | import com.azuriom.azlink.common.platform.PlatformInfo; 13 | import com.azuriom.azlink.common.platform.PlatformType; 14 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 15 | import net.md_5.bungee.api.plugin.Plugin; 16 | import net.md_5.bungee.config.Configuration; 17 | import net.md_5.bungee.config.ConfigurationProvider; 18 | import net.md_5.bungee.config.YamlConfiguration; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.nio.file.Path; 23 | import java.util.stream.Stream; 24 | 25 | public final class AzLinkBungeePlugin extends Plugin implements AzLinkPlatform { 26 | 27 | private final SchedulerAdapter scheduler = new BungeeSchedulerAdapter(this); 28 | 29 | private AzLinkPlugin plugin; 30 | private LoggerAdapter loggerAdapter; 31 | private Configuration config; 32 | 33 | @Override 34 | public void onLoad() { 35 | this.loggerAdapter = new JavaLoggerAdapter(getLogger()); 36 | } 37 | 38 | @Override 39 | public void onEnable() { 40 | this.plugin = new AzLinkPlugin(this); 41 | this.plugin.init(); 42 | 43 | getProxy().getPluginManager().registerCommand(this, new BungeeCommandExecutor(this.plugin)); 44 | 45 | loadConfig(); 46 | 47 | if (this.config.getBoolean("nlogin-integration") 48 | && getProxy().getPluginManager().getPlugin("nLogin") != null) { 49 | NLoginIntegration.register(this); 50 | } 51 | 52 | if (this.config.getBoolean("skinsrestorer-integration") 53 | && getProxy().getPluginManager().getPlugin("SkinsRestorer") != null) { 54 | getProxy().getPluginManager().registerListener(this, new SkinsRestorerIntegration(this)); 55 | } 56 | } 57 | 58 | @Override 59 | public void onDisable() { 60 | if (this.plugin != null) { 61 | this.plugin.shutdown(); 62 | } 63 | } 64 | 65 | @Override 66 | public AzLinkPlugin getPlugin() { 67 | return this.plugin; 68 | } 69 | 70 | @Override 71 | public LoggerAdapter getLoggerAdapter() { 72 | return this.loggerAdapter; 73 | } 74 | 75 | @Override 76 | public SchedulerAdapter getSchedulerAdapter() { 77 | return this.scheduler; 78 | } 79 | 80 | @Override 81 | public PlatformType getPlatformType() { 82 | return PlatformType.BUNGEE; 83 | } 84 | 85 | @Override 86 | public PlatformInfo getPlatformInfo() { 87 | return new PlatformInfo(getProxy().getName(), getProxy().getVersion()); 88 | } 89 | 90 | @Override 91 | public String getPluginVersion() { 92 | return getDescription().getVersion(); 93 | } 94 | 95 | @Override 96 | public Path getDataDirectory() { 97 | return getDataFolder().toPath(); 98 | } 99 | 100 | @Override 101 | public Stream getOnlinePlayers() { 102 | return getProxy().getPlayers().stream().map(BungeeCommandSender::new); 103 | } 104 | 105 | @Override 106 | public void dispatchConsoleCommand(String command) { 107 | getProxy().getPluginManager().dispatchCommand(getProxy().getConsole(), command); 108 | } 109 | 110 | @Override 111 | public int getMaxPlayers() { 112 | return getProxy().getConfig().getPlayerLimit(); 113 | } 114 | 115 | private void loadConfig() { 116 | File configFile = new File(getDataFolder(), "config.yml"); 117 | 118 | try { 119 | saveResource(configFile.toPath(), "bungee-config.yml"); 120 | 121 | this.config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); 122 | } catch (IOException e) { 123 | throw new RuntimeException("Unable to load configuration", e); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /bungee/src/main/java/com/azuriom/azlink/bungee/BungeeSchedulerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bungee; 2 | 3 | import com.azuriom.azlink.common.scheduler.CancellableTask; 4 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 5 | import net.md_5.bungee.api.scheduler.ScheduledTask; 6 | 7 | import java.util.concurrent.Executor; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class BungeeSchedulerAdapter implements SchedulerAdapter { 11 | 12 | private final Executor executor = this::executeAsync; 13 | 14 | private final AzLinkBungeePlugin plugin; 15 | 16 | public BungeeSchedulerAdapter(AzLinkBungeePlugin plugin) { 17 | this.plugin = plugin; 18 | } 19 | 20 | @Override 21 | public void executeSync(Runnable runnable) { 22 | this.executeAsync(runnable); 23 | } 24 | 25 | @Override 26 | public void executeAsync(Runnable runnable) { 27 | this.plugin.getProxy().getScheduler().runAsync(this.plugin, runnable); 28 | } 29 | 30 | @Override 31 | public Executor syncExecutor() { 32 | return this.executor; 33 | } 34 | 35 | @Override 36 | public Executor asyncExecutor() { 37 | return this.executor; 38 | } 39 | 40 | @Override 41 | public CancellableTask scheduleAsyncLater(Runnable runnable, long delay, TimeUnit unit) { 42 | ScheduledTask task = this.plugin.getProxy() 43 | .getScheduler() 44 | .schedule(this.plugin, runnable, delay, unit); 45 | 46 | return new CancellableBungeeTask(task); 47 | } 48 | 49 | @Override 50 | public CancellableTask scheduleAsyncRepeating(Runnable runnable, long delay, long interval, TimeUnit unit) { 51 | ScheduledTask task = this.plugin.getProxy() 52 | .getScheduler() 53 | .schedule(this.plugin, runnable, delay, interval, unit); 54 | 55 | return new CancellableBungeeTask(task); 56 | } 57 | 58 | private static class CancellableBungeeTask implements CancellableTask { 59 | 60 | private final ScheduledTask task; 61 | 62 | public CancellableBungeeTask(ScheduledTask task) { 63 | this.task = task; 64 | } 65 | 66 | @Override 67 | public void cancel() { 68 | this.task.cancel(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bungee.command; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.command.AzLinkCommand; 5 | import net.md_5.bungee.api.CommandSender; 6 | import net.md_5.bungee.api.plugin.Command; 7 | import net.md_5.bungee.api.plugin.TabExecutor; 8 | 9 | public class BungeeCommandExecutor extends Command implements TabExecutor { 10 | 11 | private final AzLinkCommand command; 12 | 13 | public BungeeCommandExecutor(AzLinkPlugin plugin) { 14 | super("azlink", "azlink.admin", "azuriomlink"); 15 | 16 | this.command = new AzLinkCommand(plugin); 17 | } 18 | 19 | @Override 20 | public void execute(CommandSender sender, String[] args) { 21 | this.command.execute(new BungeeCommandSender(sender), args); 22 | } 23 | 24 | @Override 25 | public Iterable onTabComplete(CommandSender sender, String[] args) { 26 | return this.command.tabComplete(new BungeeCommandSender(sender), args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bungee/src/main/java/com/azuriom/azlink/bungee/command/BungeeCommandSender.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bungee.command; 2 | 3 | import com.azuriom.azlink.common.command.CommandSender; 4 | import net.md_5.bungee.api.ChatColor; 5 | import net.md_5.bungee.api.chat.TextComponent; 6 | import net.md_5.bungee.api.connection.ProxiedPlayer; 7 | 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.UUID; 10 | 11 | public class BungeeCommandSender implements CommandSender { 12 | 13 | private final net.md_5.bungee.api.CommandSender sender; 14 | 15 | public BungeeCommandSender(net.md_5.bungee.api.CommandSender sender) { 16 | this.sender = sender; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return this.sender.getName(); 22 | } 23 | 24 | @Override 25 | public UUID getUuid() { 26 | if (this.sender instanceof ProxiedPlayer) { 27 | return ((ProxiedPlayer) this.sender).getUniqueId(); 28 | } 29 | 30 | return UUID.nameUUIDFromBytes(getName().getBytes(StandardCharsets.UTF_8)); 31 | } 32 | 33 | @Override 34 | public void sendMessage(String message) { 35 | String formatted = ChatColor.translateAlternateColorCodes('&', message); 36 | 37 | this.sender.sendMessage(TextComponent.fromLegacyText(formatted)); 38 | } 39 | 40 | @Override 41 | public boolean hasPermission(String permission) { 42 | return this.sender.hasPermission(permission); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bungee/src/main/java/com/azuriom/azlink/bungee/integrations/NLoginIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bungee.integrations; 2 | 3 | import com.azuriom.azlink.bungee.AzLinkBungeePlugin; 4 | import com.azuriom.azlink.common.integrations.BaseNLogin; 5 | import com.nickuc.login.api.enums.TwoFactorType; 6 | import com.nickuc.login.api.event.bungee.account.PasswordUpdateEvent; 7 | import com.nickuc.login.api.event.bungee.auth.RegisterEvent; 8 | import com.nickuc.login.api.event.bungee.twofactor.TwoFactorAddEvent; 9 | import net.md_5.bungee.api.connection.ProxiedPlayer; 10 | import net.md_5.bungee.api.plugin.Listener; 11 | import net.md_5.bungee.event.EventHandler; 12 | 13 | import java.net.InetAddress; 14 | import java.net.InetSocketAddress; 15 | import java.net.SocketAddress; 16 | 17 | public class NLoginIntegration extends BaseNLogin implements Listener { 18 | 19 | public NLoginIntegration(AzLinkBungeePlugin plugin) { 20 | super(plugin.getPlugin()); 21 | } 22 | 23 | @EventHandler 24 | public void onEmailAdded(TwoFactorAddEvent event) { 25 | if (event.getType() != TwoFactorType.EMAIL) { 26 | return; 27 | } 28 | 29 | handleEmailUpdated(event.getPlayerId(), event.getPlayerName(), event.getAccount()); 30 | } 31 | 32 | @EventHandler 33 | public void onRegister(RegisterEvent event) { 34 | ProxiedPlayer player = event.getPlayer(); 35 | SocketAddress socketAddress = player.getSocketAddress(); 36 | InetAddress address = socketAddress instanceof InetSocketAddress 37 | ? ((InetSocketAddress) socketAddress).getAddress() : null; 38 | 39 | handleRegister(player.getUniqueId(), player.getName(), event.getPassword(), address); 40 | } 41 | 42 | @EventHandler 43 | public void onPasswordUpdate(PasswordUpdateEvent event) { 44 | handleUpdatePassword(event.getPlayerId(), event.getPlayerName(), event.getNewPassword()); 45 | } 46 | 47 | public static void register(AzLinkBungeePlugin plugin) { 48 | if (ensureApiVersion(plugin)) { 49 | plugin.getProxy().getPluginManager().registerListener(plugin, new NLoginIntegration(plugin)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /bungee/src/main/java/com/azuriom/azlink/bungee/integrations/SkinsRestorerIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.bungee.integrations; 2 | 3 | import com.azuriom.azlink.bungee.AzLinkBungeePlugin; 4 | import com.azuriom.azlink.common.integrations.BaseSkinsRestorer; 5 | import net.md_5.bungee.api.connection.ProxiedPlayer; 6 | import net.md_5.bungee.api.event.PostLoginEvent; 7 | import net.md_5.bungee.api.plugin.Listener; 8 | import net.md_5.bungee.event.EventHandler; 9 | 10 | public class SkinsRestorerIntegration 11 | extends BaseSkinsRestorer implements Listener { 12 | 13 | public SkinsRestorerIntegration(AzLinkBungeePlugin plugin) { 14 | super(plugin.getPlugin(), ProxiedPlayer.class); 15 | } 16 | 17 | @EventHandler 18 | public void onPlayerJoin(PostLoginEvent event) { 19 | ProxiedPlayer player = event.getPlayer(); 20 | 21 | handleJoin(player.getName(), player); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bungee/src/main/resources/bungee-config.yml: -------------------------------------------------------------------------------- 1 | # When enabled, new users registered with the nLogin plugin will automatically be registered on the website 2 | # WARNING: You need nLogin version 10.2.43 or newer! 3 | # If you are using multiples servers, you should use the same database for nLogin to prevent users 4 | # from registering multiple times 5 | nlogin-integration: false 6 | 7 | # When enabled, if SkinsRestorer is installed, and the SkinAPI plugin is present on the website, 8 | # the player's skin will be updated to the website's skin 9 | skinsrestorer-integration: false 10 | -------------------------------------------------------------------------------- /bungee/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: AzLink 2 | version: ${pluginVersion} 3 | author: Azuriom Team 4 | description: The plugin to link your Azuriom website with your server. 5 | main: com.azuriom.azlink.bungee.AzLinkBungeePlugin 6 | softdepend: [nLogin, SkinsRestorer] 7 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://repo.codemc.io/repository/maven-public/' } 3 | maven { url 'https://repo.nickuc.com/maven-releases/' } 4 | } 5 | 6 | dependencies { 7 | compileOnly 'org.slf4j:slf4j-api:1.7.36' 8 | compileOnly 'com.google.code.gson:gson:2.13.1' 9 | compileOnly 'io.netty:netty-all:4.1.42.Final' 10 | compileOnly 'net.skinsrestorer:skinsrestorer-api:15.0.2' 11 | compileOnly 'com.nickuc.login:nlogin-api:10.3' 12 | 13 | testImplementation 'org.junit.jupiter:junit-jupiter:5.12.2' 14 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 15 | } 16 | 17 | test { 18 | useJUnitPlatform() 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/AzLinkPlatform.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common; 2 | 3 | import com.azuriom.azlink.common.command.CommandSender; 4 | import com.azuriom.azlink.common.data.PlatformData; 5 | import com.azuriom.azlink.common.data.WorldData; 6 | import com.azuriom.azlink.common.logger.LoggerAdapter; 7 | import com.azuriom.azlink.common.platform.PlatformInfo; 8 | import com.azuriom.azlink.common.platform.PlatformType; 9 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.util.Optional; 16 | import java.util.stream.Stream; 17 | 18 | public interface AzLinkPlatform { 19 | 20 | AzLinkPlugin getPlugin(); 21 | 22 | LoggerAdapter getLoggerAdapter(); 23 | 24 | SchedulerAdapter getSchedulerAdapter(); 25 | 26 | PlatformType getPlatformType(); 27 | 28 | PlatformInfo getPlatformInfo(); 29 | 30 | String getPluginVersion(); 31 | 32 | Path getDataDirectory(); 33 | 34 | Stream getOnlinePlayers(); 35 | 36 | int getMaxPlayers(); 37 | 38 | default Optional getWorldData() { 39 | return Optional.empty(); 40 | } 41 | 42 | void dispatchConsoleCommand(String command); 43 | 44 | default PlatformData getPlatformData() { 45 | return new PlatformData(getPlatformType(), getPlatformInfo()); 46 | } 47 | 48 | default void saveResource(Path target, String name) throws IOException { 49 | if (Files.exists(target)) { 50 | return; 51 | } 52 | 53 | if (!Files.isDirectory(target.getParent())) { 54 | Files.createDirectory(target.getParent()); 55 | } 56 | 57 | try (InputStream in = getClass().getClassLoader().getResourceAsStream(name)) { 58 | Files.copy(in, target); 59 | } 60 | } 61 | 62 | default void prepareDataAsync() { 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/AzLinkPlugin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common; 2 | 3 | import com.azuriom.azlink.common.command.AzLinkCommand; 4 | import com.azuriom.azlink.common.command.CommandSender; 5 | import com.azuriom.azlink.common.config.PluginConfig; 6 | import com.azuriom.azlink.common.data.PlatformData; 7 | import com.azuriom.azlink.common.data.PlayerData; 8 | import com.azuriom.azlink.common.data.ServerData; 9 | import com.azuriom.azlink.common.data.SystemData; 10 | import com.azuriom.azlink.common.data.WorldData; 11 | import com.azuriom.azlink.common.http.client.HttpClient; 12 | import com.azuriom.azlink.common.http.server.HttpServer; 13 | import com.azuriom.azlink.common.http.server.NettyHttpServer; 14 | import com.azuriom.azlink.common.logger.LoggerAdapter; 15 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 16 | import com.azuriom.azlink.common.tasks.FetcherTask; 17 | import com.azuriom.azlink.common.users.UserManager; 18 | import com.azuriom.azlink.common.utils.SystemUtils; 19 | import com.azuriom.azlink.common.utils.UpdateChecker; 20 | import com.google.gson.Gson; 21 | import com.google.gson.GsonBuilder; 22 | 23 | import java.io.BufferedReader; 24 | import java.io.BufferedWriter; 25 | import java.io.IOException; 26 | import java.nio.file.Files; 27 | import java.nio.file.NoSuchFileException; 28 | import java.nio.file.Path; 29 | import java.time.Duration; 30 | import java.time.LocalDateTime; 31 | import java.time.temporal.ChronoUnit; 32 | import java.util.List; 33 | import java.util.concurrent.CompletableFuture; 34 | import java.util.concurrent.TimeUnit; 35 | import java.util.stream.Collectors; 36 | 37 | public class AzLinkPlugin { 38 | 39 | private static final Gson GSON = new Gson(); 40 | private static final Gson GSON_PRETTY_PRINT = new GsonBuilder().setPrettyPrinting().create(); 41 | 42 | private final HttpClient httpClient = new HttpClient(this); 43 | private final UserManager userManager = new UserManager(this); 44 | 45 | private final AzLinkCommand command = new AzLinkCommand(this); 46 | 47 | private final FetcherTask fetcherTask = new FetcherTask(this); 48 | 49 | private final AzLinkPlatform platform; 50 | 51 | private PluginConfig config = new PluginConfig(null, null); 52 | private HttpServer httpServer; 53 | private Path configFile; 54 | 55 | private boolean logCpuError = true; 56 | 57 | public AzLinkPlugin(AzLinkPlatform platform) { 58 | this.platform = platform; 59 | } 60 | 61 | public void init() { 62 | this.configFile = this.platform.getDataDirectory().resolve("config.json"); 63 | 64 | try (BufferedReader reader = Files.newBufferedReader(this.configFile)) { 65 | this.config = GSON.fromJson(reader, PluginConfig.class); 66 | } catch (NoSuchFileException e) { 67 | // ignore, not setup yet 68 | } catch (IOException e) { 69 | getLogger().error("Error while loading configuration", e); 70 | return; 71 | } 72 | 73 | this.httpServer = createHttpServer(); 74 | 75 | // Add a random start delay to prevent important load on shared web hosts 76 | // caused by many servers sending request at the same time 77 | LocalDateTime start = LocalDateTime.now() 78 | .truncatedTo(ChronoUnit.MINUTES) 79 | .plusMinutes(1) 80 | .plusSeconds(1 + (long) (Math.random() * 30)); 81 | long startDelay = Duration.between(LocalDateTime.now(), start).toMillis(); 82 | long repeatDelay = TimeUnit.MINUTES.toMillis(1); 83 | 84 | getScheduler().scheduleAsyncRepeating(this.fetcherTask, startDelay, repeatDelay, TimeUnit.MILLISECONDS); 85 | 86 | if (!this.config.isValid()) { 87 | getLogger().warn("Invalid configuration, please use '/azlink' to setup the plugin."); 88 | return; 89 | } 90 | 91 | if (this.config.hasInstantCommands() && this.httpServer != null) { 92 | this.httpServer.start(); 93 | } 94 | 95 | if (this.config.hasUpdatesCheck()) { 96 | UpdateChecker updateChecker = new UpdateChecker(this); 97 | 98 | getScheduler().executeAsync(updateChecker::checkUpdates); 99 | } 100 | 101 | this.httpClient.verifyStatus() 102 | .thenRun(() -> getLogger().info("Successfully connected to " + this.config.getSiteUrl())) 103 | .exceptionally(ex -> { 104 | getLogger().warn("Unable to verify the website connection: " + ex.getMessage()); 105 | 106 | return null; 107 | }); 108 | } 109 | 110 | public void restartHttpServer() { 111 | if (this.httpServer != null) { 112 | this.httpServer.stop(); 113 | } 114 | 115 | this.httpServer = createHttpServer(); 116 | 117 | this.httpServer.start(); 118 | } 119 | 120 | public void shutdown() { 121 | getLogger().info("Shutting down scheduler"); 122 | 123 | try { 124 | getScheduler().shutdown(); 125 | } catch (Exception e) { 126 | getLogger().warn("Error while shutting down scheduler", e); 127 | } 128 | 129 | if (this.httpServer != null) { 130 | getLogger().info("Stopping HTTP server"); 131 | this.httpServer.stop(); 132 | } 133 | } 134 | 135 | public void saveConfig() throws IOException { 136 | if (!Files.isDirectory(this.platform.getDataDirectory())) { 137 | Files.createDirectories(this.platform.getDataDirectory()); 138 | } 139 | 140 | try (BufferedWriter writer = Files.newBufferedWriter(this.configFile)) { 141 | GSON_PRETTY_PRINT.toJson(this.config, writer); 142 | } 143 | } 144 | 145 | public AzLinkCommand getCommand() { 146 | return this.command; 147 | } 148 | 149 | public ServerData getServerData(boolean fullData) { 150 | List players = this.platform.getOnlinePlayers() 151 | .map(CommandSender::toData) 152 | .collect(Collectors.toList()); 153 | int max = this.platform.getMaxPlayers(); 154 | 155 | double cpuUsage = getCpuUsage(); 156 | 157 | String version = this.platform.getPluginVersion(); 158 | SystemData system = fullData ? new SystemData(SystemUtils.getMemoryUsage(), cpuUsage) : null; 159 | WorldData world = fullData ? this.platform.getWorldData().orElse(null) : null; 160 | PlatformData platformData = this.platform.getPlatformData(); 161 | 162 | return new ServerData(platformData, version, players, max, system, world, fullData); 163 | } 164 | 165 | public CompletableFuture fetch() { 166 | return this.fetcherTask.fetch(); 167 | } 168 | 169 | public LoggerAdapter getLogger() { 170 | return this.platform.getLoggerAdapter(); 171 | } 172 | 173 | public SchedulerAdapter getScheduler() { 174 | return this.platform.getSchedulerAdapter(); 175 | } 176 | 177 | public PluginConfig getConfig() { 178 | return this.config; 179 | } 180 | 181 | public AzLinkPlatform getPlatform() { 182 | return this.platform; 183 | } 184 | 185 | public HttpClient getHttpClient() { 186 | return this.httpClient; 187 | } 188 | 189 | public UserManager getUserManager() { 190 | return this.userManager; 191 | } 192 | 193 | protected HttpServer createHttpServer() { 194 | return new NettyHttpServer(this); 195 | } 196 | 197 | private double getCpuUsage() { 198 | try { 199 | return SystemUtils.getCpuUsage(); 200 | } catch (Throwable t) { 201 | if (this.logCpuError) { 202 | this.logCpuError = false; 203 | 204 | getLogger().warn("Error while retrieving CPU usage", t); 205 | } 206 | } 207 | return -1; 208 | } 209 | 210 | public static Gson getGson() { 211 | return GSON; 212 | } 213 | 214 | public static Gson getGsonPrettyPrint() { 215 | return GSON_PRETTY_PRINT; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/command/AzLinkCommand.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.command; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.data.UserInfo; 5 | import com.azuriom.azlink.common.users.MoneyAction; 6 | 7 | import java.io.IOException; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.stream.Collectors; 14 | 15 | public class AzLinkCommand { 16 | 17 | private static final List COMPLETIONS = Arrays.asList("status", "setup", "fetch", "money", "port"); 18 | 19 | protected final AzLinkPlugin plugin; 20 | 21 | public AzLinkCommand(AzLinkPlugin plugin) { 22 | this.plugin = plugin; 23 | } 24 | 25 | public void execute(CommandSender sender, String[] args) { 26 | if (!sender.hasPermission("azlink.admin")) { 27 | String version = this.plugin.getPlatform().getPluginVersion(); 28 | sender.sendMessage("&9AzLink v" + version + "&7. Website: &9https://azuriom.com"); 29 | sender.sendMessage("&cYou don't have the permission to use this command."); 30 | return; 31 | } 32 | 33 | if (args.length == 0) { 34 | sendUsage(sender); 35 | return; 36 | } 37 | 38 | if (args[0].equalsIgnoreCase("setup")) { 39 | if (args.length < 3) { 40 | sender.sendMessage("&6You must first add this server in your Azuriom admin dashboard, in the 'Servers' section."); 41 | return; 42 | } 43 | 44 | plugin.getScheduler().executeAsync(() -> setup(sender, args[1], args[2])); 45 | return; 46 | } 47 | 48 | if (args[0].equalsIgnoreCase("status")) { 49 | showStatus(sender); 50 | return; 51 | } 52 | 53 | if (args[0].equalsIgnoreCase("money")) { 54 | try { 55 | editMoney(sender, args); 56 | } catch (NumberFormatException e) { 57 | sender.sendMessage("&c'" + args[3] + "' is not a valid number !"); 58 | } 59 | return; 60 | } 61 | 62 | if (args[0].equalsIgnoreCase("fetch")) { 63 | this.plugin.fetch() 64 | .thenRun(() -> sender.sendMessage("&6Data has been fetched successfully.")) 65 | .exceptionally(ex -> { 66 | sender.sendMessage("&cUnable to fetch data: " + ex.getMessage()); 67 | 68 | return null; 69 | }); 70 | return; 71 | } 72 | 73 | if (args[0].equalsIgnoreCase("port")) { 74 | if (args.length < 2) { 75 | sender.sendMessage("&cUsage: /azlink port "); 76 | return; 77 | } 78 | 79 | int port; 80 | 81 | try { 82 | port = Integer.parseInt(args[1]); 83 | } catch (NumberFormatException e) { 84 | sender.sendMessage("&c'" + args[1] + "' is not a valid port !"); 85 | return; 86 | } 87 | 88 | if (port < 1 || port > 65535) { 89 | sender.sendMessage("&cThe port must be between 1 and 65535"); 90 | return; 91 | } 92 | 93 | this.plugin.getConfig().setHttpPort(port); 94 | 95 | try { 96 | this.plugin.restartHttpServer(); 97 | 98 | sender.sendMessage("&aHTTP server started on port " + port); 99 | } catch (Exception e) { 100 | String info = e.getMessage() + " - " + e.getClass().getName(); 101 | sender.sendMessage("&cAn error occurred while starting the HTTP server: " + info); 102 | this.plugin.getLogger().error("Error while starting the HTTP server", e); 103 | return; 104 | } 105 | 106 | saveConfig(sender); 107 | 108 | return; 109 | } 110 | 111 | sendUsage(sender); 112 | } 113 | 114 | public List tabComplete(CommandSender sender, String[] args) { 115 | if (!sender.hasPermission("azlink.admin")) { 116 | return Collections.emptyList(); 117 | } 118 | 119 | if (args.length == 1) { 120 | return COMPLETIONS.stream() 121 | .filter(s -> startsWithIgnoreCase(s, args[0])) 122 | .collect(Collectors.toList()); 123 | } 124 | 125 | if (args.length == 2 && args[0].equalsIgnoreCase("money")) { 126 | return Arrays.stream(MoneyAction.values()) 127 | .map(Enum::toString) 128 | .filter(s -> startsWithIgnoreCase(s, args[1])) 129 | .collect(Collectors.toList()); 130 | } 131 | 132 | if (args.length == 3 && args[0].equalsIgnoreCase("money")) { 133 | return this.plugin.getPlatform().getOnlinePlayers() 134 | .map(CommandSender::getName) 135 | .filter(name -> startsWithIgnoreCase(name, args[2])) 136 | .collect(Collectors.toList()); 137 | } 138 | 139 | return Collections.emptyList(); 140 | } 141 | 142 | public void editMoney(CommandSender sender, String[] args) throws NumberFormatException { 143 | MoneyAction action; 144 | 145 | if (args.length < 4 || (action = MoneyAction.fromString(args[1])) == null) { 146 | sender.sendMessage("&cUsage: /azlink money "); 147 | return; 148 | } 149 | 150 | double amount = Double.parseDouble(args[3]); 151 | Optional user = this.plugin.getUserManager().getUserByName(args[2]); 152 | 153 | if (amount <= 0) { 154 | sender.sendMessage("&cThe amount must be positive."); 155 | return; 156 | } 157 | 158 | if (!user.isPresent()) { 159 | sender.sendMessage("&cUnable to find player '" + args[2] + "', please try again in few seconds or use '/azlink fetch'."); 160 | return; 161 | } 162 | 163 | this.plugin.getUserManager().editMoney(user.get(), action, amount) 164 | .thenAccept(u -> { 165 | sender.sendMessage("&aMoney has been edited successfully."); 166 | sender.sendMessage("&aNew balance: " + u.getMoney()); 167 | }) 168 | .exceptionally(ex -> { 169 | sender.sendMessage("&cUnable to edit money: " + ex.getMessage()); 170 | 171 | return null; 172 | }); 173 | } 174 | 175 | public String getUsage() { 176 | return "Usage: /azlink [" + String.join("|", COMPLETIONS) + "]"; 177 | } 178 | 179 | private void sendUsage(CommandSender sender) { 180 | String version = this.plugin.getPlatform().getPluginVersion(); 181 | sender.sendMessage("&9AzLink v" + version + "&7. Website: &9https://azuriom.com"); 182 | sender.sendMessage("&8- /azlink setup"); 183 | sender.sendMessage("&8- /azlink port "); 184 | sender.sendMessage("&8- /azlink status"); 185 | sender.sendMessage("&8- /azlink money "); 186 | } 187 | 188 | private void setup(CommandSender sender, String url, String key) { 189 | if (url.endsWith("/")) { 190 | url = url.substring(0, url.length() - 1); 191 | } 192 | 193 | if (startsWithIgnoreCase(url, "http:")) { 194 | sender.sendMessage("&6You should use https to improve security!"); 195 | } 196 | 197 | this.plugin.getConfig().setSiteKey(key); 198 | this.plugin.getConfig().setSiteUrl(url); 199 | 200 | showStatus(sender) 201 | .thenRun(() -> saveConfig(sender)) 202 | .thenRun(this.plugin::restartHttpServer); 203 | } 204 | 205 | private void saveConfig(CommandSender sender) { 206 | try { 207 | this.plugin.saveConfig(); 208 | } catch (IOException e) { 209 | String info = e.getMessage() + " - " + e.getClass().getName(); 210 | sender.sendMessage("&cAn error occurred while saving config: " + info); 211 | this.plugin.getLogger().error("Error while saving config", e); 212 | } 213 | } 214 | 215 | private CompletableFuture showStatus(CommandSender sender) { 216 | return this.plugin.getHttpClient() 217 | .verifyStatus() 218 | .thenRun(() -> sender.sendMessage("&aLinked to the website successfully.")) 219 | .thenRun(this.plugin::fetch) 220 | .whenComplete((v, ex) -> { 221 | if (ex != null) { 222 | sender.sendMessage("&cUnable to connect to the website: " + ex.getMessage()); 223 | } 224 | }); 225 | } 226 | 227 | private static boolean startsWithIgnoreCase(String string, String prefix) { 228 | if (string.length() < prefix.length()) { 229 | return false; 230 | } 231 | 232 | return string.regionMatches(true, 0, prefix, 0, prefix.length()); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/command/CommandSender.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.command; 2 | 3 | import com.azuriom.azlink.common.data.PlayerData; 4 | 5 | import java.util.UUID; 6 | 7 | public interface CommandSender { 8 | 9 | String getName(); 10 | 11 | UUID getUuid(); 12 | 13 | void sendMessage(String message); 14 | 15 | boolean hasPermission(String permission); 16 | 17 | default PlayerData toData() { 18 | return new PlayerData(getName(), getUuid()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/config/PluginConfig.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.config; 2 | 3 | import com.azuriom.azlink.common.http.server.HttpServer; 4 | 5 | public class PluginConfig { 6 | 7 | private String siteKey; 8 | private String siteUrl; 9 | private boolean instantCommands = true; 10 | private int httpPort = HttpServer.DEFAULT_PORT; 11 | private boolean checkUpdates = true; 12 | 13 | public PluginConfig() { 14 | this(null, null); 15 | } 16 | 17 | public PluginConfig(String siteKey, String siteUrl) { 18 | this.siteKey = siteKey; 19 | this.siteUrl = siteUrl; 20 | } 21 | 22 | public String getSiteKey() { 23 | return this.siteKey; 24 | } 25 | 26 | public void setSiteKey(String siteKey) { 27 | this.siteKey = siteKey; 28 | } 29 | 30 | public String getSiteUrl() { 31 | return this.siteUrl; 32 | } 33 | 34 | public void setSiteUrl(String siteUrl) { 35 | this.siteUrl = siteUrl; 36 | } 37 | 38 | public boolean hasInstantCommands() { 39 | return this.instantCommands; 40 | } 41 | 42 | public int getHttpPort() { 43 | return this.httpPort; 44 | } 45 | 46 | public void setHttpPort(int httpPort) { 47 | this.httpPort = httpPort; 48 | } 49 | 50 | public boolean hasUpdatesCheck() { 51 | return this.checkUpdates; 52 | } 53 | 54 | public boolean isValid() { 55 | return this.siteKey != null && !this.siteKey.isEmpty() && this.siteUrl != null && !this.siteUrl.isEmpty(); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "PluginConfig{siteKey='" + this.siteKey + 61 | "', siteUrl='" + this.siteUrl + 62 | "', httpPort=" + this.httpPort + '}'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/data/PlatformData.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.data; 2 | 3 | import com.azuriom.azlink.common.platform.PlatformInfo; 4 | import com.azuriom.azlink.common.platform.PlatformType; 5 | 6 | public class PlatformData { 7 | 8 | private final PlatformType type; 9 | private final String name; 10 | private final String version; 11 | 12 | public PlatformData(PlatformType type, PlatformInfo info) { 13 | this.type = type; 14 | this.name = info.getName(); 15 | this.version = info.getVersion(); 16 | } 17 | 18 | public PlatformType getType() { 19 | return this.type; 20 | } 21 | 22 | public String getName() { 23 | return this.name; 24 | } 25 | 26 | public String getVersion() { 27 | return this.version; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/data/PlayerData.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.data; 2 | 3 | import java.util.UUID; 4 | 5 | public class PlayerData { 6 | 7 | private final String name; 8 | private final UUID uuid; 9 | 10 | public PlayerData(String name, UUID uuid) { 11 | this.name = name; 12 | this.uuid = uuid; 13 | } 14 | 15 | public String getName() { 16 | return this.name; 17 | } 18 | 19 | public UUID getUuid() { 20 | return this.uuid; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/data/ServerData.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.data; 2 | 3 | import java.util.List; 4 | 5 | public class ServerData { 6 | 7 | private final PlatformData platform; 8 | private final String version; 9 | 10 | private final List players; 11 | private final int maxPlayers; 12 | 13 | private final SystemData system; 14 | private final WorldData worlds; 15 | 16 | private final boolean full; 17 | 18 | public ServerData(PlatformData platform, String version, List players, int maxPlayers, SystemData system, WorldData worlds, boolean full) { 19 | this.platform = platform; 20 | this.version = version; 21 | this.players = players; 22 | this.maxPlayers = maxPlayers; 23 | this.system = system; 24 | this.worlds = worlds; 25 | this.full = full; 26 | } 27 | 28 | public PlatformData getPlatform() { 29 | return this.platform; 30 | } 31 | 32 | public String getVersion() { 33 | return this.version; 34 | } 35 | 36 | public List getPlayers() { 37 | return this.players; 38 | } 39 | 40 | public int getMaxPlayers() { 41 | return this.maxPlayers; 42 | } 43 | 44 | public SystemData getSystem() { 45 | return this.system; 46 | } 47 | 48 | public WorldData getWorlds() { 49 | return this.worlds; 50 | } 51 | 52 | public boolean isFull() { 53 | return this.full; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/data/SystemData.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.data; 2 | 3 | public class SystemData { 4 | 5 | private final double ram; 6 | private final double cpu; 7 | 8 | public SystemData(double ram, double cpu) { 9 | this.ram = ram; 10 | this.cpu = cpu; 11 | } 12 | 13 | public double getRam() { 14 | return this.ram; 15 | } 16 | 17 | public double getCpu() { 18 | return this.cpu; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/data/UserInfo.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.data; 2 | 3 | public class UserInfo { 4 | 5 | private final int id; 6 | private final String name; 7 | private double money; 8 | 9 | public UserInfo(int id, String name, double money) { 10 | this.id = id; 11 | this.name = name; 12 | this.money = money; 13 | } 14 | 15 | public int getId() { 16 | return id; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public void setMoney(double money) { 24 | this.money = money; 25 | } 26 | 27 | public double getMoney() { 28 | return money; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/data/WebsiteResponse.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.data; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class WebsiteResponse { 9 | 10 | private final Map> commands = new HashMap<>(); 11 | private final List users = new ArrayList<>(); 12 | 13 | public WebsiteResponse(Map> commands) { 14 | this.commands.putAll(commands); 15 | } 16 | 17 | public Map> getCommands() { 18 | return this.commands; 19 | } 20 | 21 | public List getUsers() { 22 | return users; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/data/WorldData.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.data; 2 | 3 | public class WorldData { 4 | 5 | private final double tps; 6 | private final int chunks; 7 | private final int entities; 8 | 9 | public WorldData(double tps, int chunks, int entities) { 10 | this.tps = tps; 11 | this.chunks = chunks; 12 | this.entities = entities; 13 | } 14 | 15 | public double getTps() { 16 | return this.tps; 17 | } 18 | 19 | public int getChunks() { 20 | return this.chunks; 21 | } 22 | 23 | public int getEntities() { 24 | return this.entities; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/http/client/HttpClient.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.http.client; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.data.ServerData; 5 | import com.azuriom.azlink.common.data.UserInfo; 6 | import com.azuriom.azlink.common.data.WebsiteResponse; 7 | import com.azuriom.azlink.common.users.EditMoneyResult; 8 | import com.google.gson.JsonObject; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStreamReader; 13 | import java.io.OutputStream; 14 | import java.net.HttpURLConnection; 15 | import java.net.InetAddress; 16 | import java.net.URI; 17 | import java.net.URL; 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.UUID; 20 | import java.util.concurrent.CompletableFuture; 21 | import java.util.concurrent.CompletionException; 22 | 23 | public class HttpClient { 24 | 25 | private static final int CONNECT_TIMEOUT = 5000; // 5 seconds 26 | private static final int READ_TIMEOUT = 5000; // 5 seconds 27 | 28 | private final AzLinkPlugin plugin; 29 | 30 | public HttpClient(AzLinkPlugin plugin) { 31 | this.plugin = plugin; 32 | } 33 | 34 | public CompletableFuture verifyStatus() { 35 | return request(RequestMethod.GET, "/azlink", null); 36 | } 37 | 38 | public CompletableFuture registerUser(String name, String email, UUID uuid, String password, InetAddress address) { 39 | JsonObject params = new JsonObject(); 40 | params.addProperty("name", name); 41 | params.addProperty("email", email); 42 | params.addProperty("game_id", uuid.toString()); 43 | params.addProperty("password", password); 44 | params.addProperty("ip", address != null ? address.getHostAddress() : null); 45 | 46 | return request(RequestMethod.POST, "/azlink/register", params); 47 | } 48 | 49 | public CompletableFuture updateEmail(UUID uuid, String email) { 50 | JsonObject params = new JsonObject(); 51 | params.addProperty("game_id", uuid.toString()); 52 | params.addProperty("email", email); 53 | 54 | return request(RequestMethod.POST, "/azlink/email", params); 55 | } 56 | 57 | public CompletableFuture updatePassword(UUID uuid, String password) { 58 | JsonObject params = new JsonObject(); 59 | params.addProperty("game_id", uuid.toString()); 60 | params.addProperty("password", password); 61 | 62 | return request(RequestMethod.POST, "/azlink/password", params); 63 | } 64 | 65 | public CompletableFuture editMoney(UserInfo user, String action, double amount) { 66 | String endpoint = "/azlink/user/" + user.getId() + "/money/" + action; 67 | JsonObject params = new JsonObject(); 68 | params.addProperty("amount", amount); 69 | 70 | return request(RequestMethod.POST, endpoint, params, EditMoneyResult.class); 71 | } 72 | 73 | public CompletableFuture postData(ServerData data) { 74 | return request(RequestMethod.POST, "/azlink", data, WebsiteResponse.class); 75 | } 76 | 77 | public CompletableFuture request(RequestMethod method, String endpoint, Object params) { 78 | return request(method, endpoint, params, Void.class); 79 | } 80 | 81 | public CompletableFuture request(RequestMethod method, String endpoint, Object params, Class clazz) { 82 | String body = AzLinkPlugin.getGson().toJson(params); 83 | 84 | return CompletableFuture.supplyAsync(() -> { 85 | try { 86 | return rawRequest(method, endpoint, body, clazz); 87 | } catch (IOException e) { 88 | throw new CompletionException(e); 89 | } 90 | }, this.plugin.getScheduler().asyncExecutor()); 91 | } 92 | 93 | private T rawRequest(RequestMethod method, String endpoint, String body, Class clazz) 94 | throws IOException { 95 | HttpURLConnection conn = prepareConnection(method, endpoint); 96 | 97 | if (method != RequestMethod.GET && body != null && !body.isEmpty()) { 98 | conn.setDoOutput(true); 99 | 100 | try (OutputStream out = conn.getOutputStream()) { 101 | out.write(body.getBytes(StandardCharsets.UTF_8)); 102 | } 103 | } 104 | 105 | int status = conn.getResponseCode(); 106 | 107 | if (status >= 400) { 108 | String info = status == 401 || status == 403 109 | ? ". Try to do again the link command given on the admin panel." : ""; 110 | 111 | throw new IOException("Unexpected HTTP error " + status + info); 112 | } 113 | 114 | if (status >= 300) { 115 | String dest = conn.getHeaderField("Location"); 116 | 117 | throw new IOException("Unexpected redirect status - " + status + ": " + dest); 118 | } 119 | 120 | if (clazz == null || clazz == Void.class) { 121 | return null; 122 | } 123 | 124 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { 125 | T response = AzLinkPlugin.getGson().fromJson(reader, clazz); 126 | 127 | if (response == null) { 128 | throw new IllegalStateException("Empty JSON response from API."); 129 | } 130 | 131 | return response; 132 | } 133 | } 134 | 135 | private HttpURLConnection prepareConnection(RequestMethod method, String endpoint) throws IOException { 136 | String baseUrl = this.plugin.getConfig().getSiteUrl(); 137 | String version = this.plugin.getPlatform().getPluginVersion(); 138 | String token = this.plugin.getConfig().getSiteKey(); 139 | URL url = URI.create(baseUrl + "/api" + endpoint).toURL(); 140 | 141 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 142 | conn.setUseCaches(false); 143 | conn.setInstanceFollowRedirects(false); // POST requests are redirected as GET requests 144 | conn.setConnectTimeout(CONNECT_TIMEOUT); 145 | conn.setReadTimeout(READ_TIMEOUT); 146 | conn.setRequestMethod(method.name()); 147 | conn.addRequestProperty("Accept", "application/json"); 148 | conn.addRequestProperty("Azuriom-Link-Token", token); 149 | conn.addRequestProperty("Content-Type", "application/json; charset=utf-8"); 150 | conn.addRequestProperty("User-Agent", "AzLink java v" + version); 151 | 152 | return conn; 153 | } 154 | 155 | public enum RequestMethod { 156 | GET, POST, PATCH, PUT, DELETE 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/http/server/HttpChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.http.server; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.ChannelPipeline; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.http.HttpObjectAggregator; 8 | import io.netty.handler.codec.http.HttpRequestDecoder; 9 | import io.netty.handler.codec.http.HttpResponseEncoder; 10 | 11 | public class HttpChannelInitializer extends ChannelInitializer { 12 | 13 | private final AzLinkPlugin plugin; 14 | 15 | public HttpChannelInitializer(AzLinkPlugin plugin) { 16 | this.plugin = plugin; 17 | } 18 | 19 | @Override 20 | protected void initChannel(SocketChannel channel) { 21 | ChannelPipeline pipeline = channel.pipeline(); 22 | pipeline.addLast("decoder", new HttpRequestDecoder()); 23 | pipeline.addLast("aggregator", new HttpObjectAggregator(1024 * 1024)); 24 | pipeline.addLast("encoder", new HttpResponseEncoder()); 25 | pipeline.addLast("handler", new HttpHandler(this.plugin)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/http/server/HttpHandler.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.http.server; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.utils.Hash; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 9 | import io.netty.handler.codec.http.FullHttpRequest; 10 | import io.netty.handler.codec.http.FullHttpResponse; 11 | import io.netty.handler.codec.http.HttpMethod; 12 | import io.netty.handler.codec.http.HttpResponseStatus; 13 | import io.netty.handler.codec.http.HttpVersion; 14 | 15 | import java.nio.charset.StandardCharsets; 16 | 17 | public class HttpHandler extends SimpleChannelInboundHandler { 18 | 19 | private final AzLinkPlugin plugin; 20 | 21 | public HttpHandler(AzLinkPlugin plugin) { 22 | this.plugin = plugin; 23 | } 24 | 25 | @Override 26 | @SuppressWarnings("deprecation") // Request#uri and Request#method don't exits on old Netty versions 27 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { 28 | String uri = request.getUri(); 29 | HttpMethod method = request.getMethod(); 30 | 31 | if (!uri.equals("/")) { 32 | close(ctx, writeResponse(HttpResponseStatus.NOT_FOUND, "Error: Not Found")); 33 | return; 34 | } 35 | 36 | if (method == HttpMethod.GET) { 37 | close(ctx, writeResponse(HttpResponseStatus.OK, "Status: OK")); 38 | return; 39 | } 40 | 41 | if (method == HttpMethod.POST) { 42 | if (!this.plugin.getConfig().isValid()) { 43 | close(ctx, writeResponse(HttpResponseStatus.SERVICE_UNAVAILABLE, "Error: Invalid configuration")); 44 | return; 45 | } 46 | 47 | String siteKeyHash = Hash.SHA_256.hash(this.plugin.getConfig().getSiteKey()); 48 | 49 | if (!siteKeyHash.equals(request.headers().get("Authorization"))) { 50 | close(ctx, writeResponse(HttpResponseStatus.FORBIDDEN, "Error: Invalid authorization")); 51 | return; 52 | } 53 | 54 | this.plugin.fetch(); 55 | 56 | close(ctx, writeResponse(HttpResponseStatus.OK, "Status: OK")); 57 | 58 | return; 59 | } 60 | 61 | close(ctx, writeResponse(HttpResponseStatus.METHOD_NOT_ALLOWED, "Error: Method Not Allowed")); 62 | } 63 | 64 | private FullHttpResponse writeResponse(HttpResponseStatus status, String content) { 65 | DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status); 66 | response.content().writeBytes(content.getBytes(StandardCharsets.UTF_8)); 67 | return response; 68 | } 69 | 70 | private void close(ChannelHandlerContext ctx, FullHttpResponse response) { 71 | ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/http/server/HttpServer.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.http.server; 2 | 3 | public interface HttpServer { 4 | 5 | int DEFAULT_PORT = 25588; 6 | 7 | void start(); 8 | 9 | void stop(); 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/http/server/NettyHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.http.server; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | public class NettyHttpServer implements HttpServer { 14 | 15 | private final AzLinkPlugin plugin; 16 | 17 | private final EventLoopGroup bossGroup; 18 | private final EventLoopGroup workerGroup; 19 | 20 | private Channel channel; 21 | 22 | public NettyHttpServer(AzLinkPlugin plugin) { 23 | this.plugin = plugin; 24 | 25 | this.bossGroup = new NioEventLoopGroup(1); 26 | this.workerGroup = new NioEventLoopGroup(); 27 | } 28 | 29 | @Override 30 | public void start() { 31 | this.plugin.getLogger().info("Starting HTTP server"); 32 | 33 | int port = this.plugin.getConfig().getHttpPort(); 34 | 35 | if (port < 1 || port > 65535) { 36 | port = HttpServer.DEFAULT_PORT; 37 | } 38 | 39 | InetSocketAddress address = new InetSocketAddress(port); 40 | 41 | new ServerBootstrap() 42 | .channel(NioServerSocketChannel.class) 43 | .group(this.bossGroup, this.workerGroup) 44 | .childHandler(new HttpChannelInitializer(this.plugin)) 45 | .bind(address) 46 | .addListener((ChannelFutureListener) future -> { 47 | if (!future.isSuccess()) { 48 | this.plugin.getLogger().error("Unable to start the HTTP server on " + address, future.cause()); 49 | return; 50 | } 51 | 52 | this.channel = future.channel(); 53 | 54 | this.plugin.getLogger().info("HTTP server started on " + future.channel().localAddress()); 55 | }); 56 | } 57 | 58 | @Override 59 | public void stop() { 60 | try { 61 | if (this.channel != null) { 62 | this.channel.close().syncUninterruptibly(); 63 | } 64 | } catch (Exception e) { 65 | this.plugin.getLogger().error("An error occurred while stopping HTTP server", e); 66 | } 67 | 68 | this.bossGroup.shutdownGracefully(); 69 | this.workerGroup.shutdownGracefully(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/integrations/BaseNLogin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.integrations; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlatform; 4 | import com.azuriom.azlink.common.AzLinkPlugin; 5 | import com.nickuc.login.api.nLoginAPI; 6 | 7 | import java.net.InetAddress; 8 | import java.util.UUID; 9 | 10 | public class BaseNLogin { 11 | 12 | protected final AzLinkPlugin plugin; 13 | 14 | public BaseNLogin(AzLinkPlugin plugin) { 15 | this.plugin = plugin; 16 | 17 | this.plugin.getLogger().info("nLogin integration enabled."); 18 | } 19 | 20 | protected void handleEmailUpdated(UUID uuid, String name, String email) { 21 | this.plugin.getHttpClient() 22 | .updateEmail(uuid, email) 23 | .exceptionally(ex -> { 24 | this.plugin.getLogger().error("Unable to update email for " + name, ex); 25 | 26 | return null; 27 | }); 28 | } 29 | 30 | protected void handleRegister(UUID uuid, String name, String password, InetAddress address) { 31 | this.plugin.getHttpClient() 32 | .registerUser(name, null, uuid, password, address) 33 | .exceptionally(ex -> { 34 | this.plugin.getLogger().error("Unable to register " + name, ex); 35 | 36 | return null; 37 | }); 38 | } 39 | 40 | protected void handleUpdatePassword(UUID uuid, String name, String password) { 41 | this.plugin.getHttpClient() 42 | .updatePassword(uuid, password) 43 | .exceptionally(ex -> { 44 | this.plugin.getLogger().error("Unable to update password for " + name, ex); 45 | 46 | return null; 47 | }); 48 | } 49 | 50 | protected static boolean ensureApiVersion(AzLinkPlatform platform) { 51 | if (nLoginAPI.getApi().getApiVersion() < 5) { 52 | platform.getPlugin().getLogger().warn("nLogin integration requires API v5 or higher"); 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/integrations/BaseSkinsRestorer.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.integrations; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import net.skinsrestorer.api.SkinsRestorer; 5 | import net.skinsrestorer.api.SkinsRestorerProvider; 6 | import net.skinsrestorer.api.connections.model.MineSkinResponse; 7 | import net.skinsrestorer.api.exception.DataRequestException; 8 | import net.skinsrestorer.api.exception.MineSkinException; 9 | 10 | public class BaseSkinsRestorer

{ 11 | 12 | private final Class

playerClass; 13 | protected final AzLinkPlugin plugin; 14 | 15 | public BaseSkinsRestorer(AzLinkPlugin plugin, Class

playerClass) { 16 | this.plugin = plugin; 17 | this.playerClass = playerClass; 18 | 19 | this.plugin.getLogger().info("SkinsRestorer integration enabled."); 20 | } 21 | 22 | protected void handleJoin(String playerName, P player) { 23 | String baseUrl = this.plugin.getConfig().getSiteUrl(); 24 | 25 | if (baseUrl == null) { 26 | return; 27 | } 28 | 29 | this.plugin.getScheduler().executeAsync(() -> { 30 | try { 31 | String url = baseUrl + "/api/skin-api/skins/" + playerName; 32 | SkinsRestorer skins = SkinsRestorerProvider.get(); 33 | MineSkinResponse res = skins.getMineSkinAPI().genSkin(url, null); 34 | 35 | skins.getSkinApplier(this.playerClass).applySkin(player, res.getProperty()); 36 | } catch (DataRequestException | MineSkinException e) { 37 | this.plugin.getLogger().warn("Unable to apply skin for " + playerName + ": " + e.getMessage()); 38 | } 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/logger/JavaLoggerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.logger; 2 | 3 | import java.util.logging.Level; 4 | import java.util.logging.Logger; 5 | 6 | public class JavaLoggerAdapter implements LoggerAdapter { 7 | 8 | private final Logger logger; 9 | 10 | public JavaLoggerAdapter(Logger logger) { 11 | this.logger = logger; 12 | } 13 | 14 | @Override 15 | public void info(String message) { 16 | this.logger.info(message); 17 | } 18 | 19 | @Override 20 | public void info(String message, Throwable throwable) { 21 | this.logger.log(Level.INFO, message, throwable); 22 | } 23 | 24 | @Override 25 | public void warn(String message) { 26 | this.logger.warning(message); 27 | } 28 | 29 | @Override 30 | public void warn(String message, Throwable throwable) { 31 | this.logger.log(Level.WARNING, message, throwable); 32 | } 33 | 34 | @Override 35 | public void error(String message) { 36 | this.logger.severe(message); 37 | } 38 | 39 | @Override 40 | public void error(String message, Throwable throwable) { 41 | this.logger.log(Level.SEVERE, message, throwable); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/logger/LoggerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.logger; 2 | 3 | public interface LoggerAdapter { 4 | 5 | void info(String message); 6 | 7 | void info(String message, Throwable throwable); 8 | 9 | void warn(String message); 10 | 11 | void warn(String message, Throwable throwable); 12 | 13 | void error(String message); 14 | 15 | void error(String message, Throwable throwable); 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/logger/Slf4jLoggerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.logger; 2 | 3 | import org.slf4j.Logger; 4 | 5 | public class Slf4jLoggerAdapter implements LoggerAdapter { 6 | 7 | private final Logger logger; 8 | 9 | public Slf4jLoggerAdapter(Logger logger) { 10 | this.logger = logger; 11 | } 12 | 13 | @Override 14 | public void info(String message) { 15 | this.logger.info(message); 16 | } 17 | 18 | @Override 19 | public void info(String message, Throwable throwable) { 20 | this.logger.info(message, throwable); 21 | } 22 | 23 | @Override 24 | public void warn(String message) { 25 | this.logger.warn(message); 26 | } 27 | 28 | @Override 29 | public void warn(String message, Throwable throwable) { 30 | this.logger.warn(message, throwable); 31 | } 32 | 33 | @Override 34 | public void error(String message) { 35 | this.logger.error(message); 36 | } 37 | 38 | @Override 39 | public void error(String message, Throwable throwable) { 40 | this.logger.error(message, throwable); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/platform/PlatformInfo.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.platform; 2 | 3 | import java.util.Objects; 4 | 5 | public class PlatformInfo { 6 | 7 | private final String name; 8 | private final String version; 9 | 10 | public PlatformInfo(String name, String version) { 11 | this.name = Objects.requireNonNull(name, "name"); 12 | this.version = Objects.requireNonNull(version, "version"); 13 | } 14 | 15 | public String getName() { 16 | return this.name; 17 | } 18 | 19 | public String getVersion() { 20 | return this.version; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "PlatformInfo{name='" + this.name + "', version='" + this.version + "'}"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/platform/PlatformType.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.platform; 2 | 3 | public enum PlatformType { 4 | 5 | BUKKIT("Bukkit"), 6 | BUNGEE("BungeeCord"), 7 | SPONGE("Sponge"), 8 | VELOCITY("Velocity"), 9 | NUKKIT("Nukkit"); 10 | 11 | private final String name; 12 | 13 | PlatformType(String name) { 14 | this.name = name; 15 | } 16 | 17 | public String getName() { 18 | return this.name; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/scheduler/CancellableTask.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.scheduler; 2 | 3 | @FunctionalInterface 4 | public interface CancellableTask { 5 | 6 | void cancel(); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/scheduler/JavaSchedulerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.scheduler; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.ScheduledExecutorService; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class JavaSchedulerAdapter implements SchedulerAdapter { 10 | 11 | private final ScheduledExecutorService scheduler; 12 | private final Executor syncExecutor; 13 | private final Executor asyncExecutor; 14 | 15 | public JavaSchedulerAdapter(Executor syncExecutor) { 16 | this(createScheduler(), syncExecutor); 17 | } 18 | 19 | public JavaSchedulerAdapter(Executor syncExecutor, Executor asyncExecutor) { 20 | this(createScheduler(), syncExecutor, asyncExecutor); 21 | } 22 | 23 | public JavaSchedulerAdapter(ScheduledExecutorService scheduler, Executor syncExecutor) { 24 | this(scheduler, syncExecutor, scheduler); 25 | } 26 | 27 | public JavaSchedulerAdapter(ScheduledExecutorService scheduler, Executor syncExecutor, Executor asyncExecutor) { 28 | this.scheduler = scheduler; 29 | this.syncExecutor = syncExecutor; 30 | this.asyncExecutor = asyncExecutor; 31 | } 32 | 33 | @Override 34 | public Executor syncExecutor() { 35 | return this.syncExecutor; 36 | } 37 | 38 | @Override 39 | public Executor asyncExecutor() { 40 | return this.asyncExecutor; 41 | } 42 | 43 | @Override 44 | public CancellableTask scheduleAsyncLater(Runnable runnable, long delay, TimeUnit unit) { 45 | return new CancellableFuture(this.scheduler.schedule(runnable, delay, unit)); 46 | } 47 | 48 | @Override 49 | public CancellableTask scheduleAsyncRepeating(Runnable runnable, long delay, long interval, TimeUnit unit) { 50 | return new CancellableFuture(this.scheduler.scheduleAtFixedRate(runnable, delay, interval, unit)); 51 | } 52 | 53 | @Override 54 | public void shutdown() throws Exception { 55 | this.scheduler.shutdown(); 56 | 57 | this.scheduler.awaitTermination(5, TimeUnit.SECONDS); 58 | } 59 | 60 | private static ScheduledExecutorService createScheduler() { 61 | return Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder() 62 | .name("azlink-scheduler") 63 | .daemon()); 64 | } 65 | 66 | private static class CancellableFuture implements CancellableTask { 67 | 68 | private final Future future; 69 | 70 | public CancellableFuture(Future future) { 71 | this.future = future; 72 | } 73 | 74 | @Override 75 | public void cancel() { 76 | this.future.cancel(false); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/scheduler/SchedulerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.scheduler; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | public interface SchedulerAdapter { 7 | 8 | default void executeSync(Runnable runnable) { 9 | syncExecutor().execute(runnable); 10 | } 11 | 12 | default void executeAsync(Runnable runnable) { 13 | asyncExecutor().execute(runnable); 14 | } 15 | 16 | Executor syncExecutor(); 17 | 18 | Executor asyncExecutor(); 19 | 20 | CancellableTask scheduleAsyncLater(Runnable runnable, long delay, TimeUnit unit); 21 | 22 | CancellableTask scheduleAsyncRepeating(Runnable runnable, long delay, long interval, TimeUnit unit); 23 | 24 | default void shutdown() throws Exception { 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/scheduler/ThreadFactoryBuilder.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.scheduler; 2 | 3 | import java.util.Objects; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.ThreadFactory; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | /** 9 | * Simple ThreadFactory builder implementation. 10 | * Inspired by the Guava ThreadFactoryBuilder. 11 | */ 12 | public class ThreadFactoryBuilder implements ThreadFactory { 13 | 14 | private final AtomicInteger threadCount = new AtomicInteger(); 15 | 16 | private final ThreadFactory originalThreadFactory; 17 | 18 | private String name; 19 | private int priority; 20 | private boolean daemon; 21 | 22 | public ThreadFactoryBuilder() { 23 | this(Executors.defaultThreadFactory()); 24 | } 25 | 26 | public ThreadFactoryBuilder(ThreadFactory threadFactory) { 27 | this.originalThreadFactory = Objects.requireNonNull(threadFactory, "threadFactory"); 28 | } 29 | 30 | public ThreadFactoryBuilder name(String name) { 31 | this.name = Objects.requireNonNull(name, "name"); 32 | return this; 33 | } 34 | 35 | public ThreadFactoryBuilder priority(int priority) { 36 | if (priority > Thread.MAX_PRIORITY || priority < Thread.MIN_PRIORITY) { 37 | throw new IllegalArgumentException("Priority must be between " + Thread.MIN_PRIORITY + " and " + Thread.MAX_PRIORITY + " : " + priority); 38 | } 39 | 40 | this.priority = priority; 41 | return this; 42 | } 43 | 44 | public ThreadFactoryBuilder daemon(boolean daemon) { 45 | this.daemon = daemon; 46 | return this; 47 | } 48 | 49 | public ThreadFactoryBuilder daemon() { 50 | return daemon(true); 51 | } 52 | 53 | @Override 54 | public Thread newThread(Runnable runnable) { 55 | Thread thread = this.originalThreadFactory.newThread(runnable); 56 | 57 | if (this.name != null) { 58 | thread.setName(this.name.replace("%t", Integer.toString(this.threadCount.getAndIncrement()))); 59 | } 60 | 61 | if (this.priority > 0) { 62 | thread.setPriority(this.priority); 63 | } 64 | 65 | if (this.daemon) { 66 | thread.setDaemon(true); 67 | } 68 | 69 | return thread; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.tasks; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.command.CommandSender; 5 | import com.azuriom.azlink.common.data.UserInfo; 6 | import com.azuriom.azlink.common.data.WebsiteResponse; 7 | 8 | import java.time.Instant; 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | import java.util.Locale; 12 | import java.util.Map; 13 | import java.util.concurrent.CompletableFuture; 14 | import java.util.concurrent.Executor; 15 | import java.util.stream.Collectors; 16 | 17 | public class FetcherTask implements Runnable { 18 | 19 | private final AzLinkPlugin plugin; 20 | 21 | private Instant lastFullDataSent = Instant.MIN; 22 | private Instant lastRequest = Instant.MIN; 23 | 24 | public FetcherTask(AzLinkPlugin plugin) { 25 | this.plugin = plugin; 26 | } 27 | 28 | @Override 29 | public void run() { 30 | fetch().exceptionally(ex -> { 31 | this.plugin.getLogger().error("Unable to send data to the website: " + ex.getMessage()); 32 | 33 | return null; 34 | }); 35 | } 36 | 37 | public CompletableFuture fetch() { 38 | Instant now = Instant.now(); 39 | 40 | if (!this.plugin.getConfig().isValid() 41 | || this.lastRequest.isAfter(now.minusSeconds(5))) { 42 | return CompletableFuture.completedFuture(null); 43 | } 44 | 45 | this.plugin.getPlatform().prepareDataAsync(); 46 | this.lastRequest = now; 47 | 48 | Executor sync = this.plugin.getScheduler().syncExecutor(); 49 | Executor async = this.plugin.getScheduler().asyncExecutor(); 50 | 51 | LocalDateTime currentTime = LocalDateTime.now(); 52 | boolean sendFullData = currentTime.getMinute() % 15 == 0 53 | && this.lastFullDataSent.isBefore(now.minusSeconds(60)); 54 | 55 | return CompletableFuture.supplyAsync(() -> this.plugin.getServerData(sendFullData), sync) 56 | .thenComposeAsync(this.plugin.getHttpClient()::postData, async) 57 | .thenAcceptAsync(res -> handleResponse(res, sendFullData), sync); 58 | } 59 | 60 | private void handleResponse(WebsiteResponse response, boolean sendFullData) { 61 | if (response == null) { 62 | return; 63 | } 64 | 65 | if (response.getUsers() != null) { 66 | for (UserInfo user : response.getUsers()) { 67 | this.plugin.getUserManager().addUser(user); 68 | } 69 | } 70 | 71 | if (response.getCommands().isEmpty()) { 72 | return; 73 | } 74 | 75 | dispatchCommands(response.getCommands()); 76 | 77 | if (sendFullData) { 78 | this.lastFullDataSent = Instant.now(); 79 | } 80 | } 81 | 82 | private void dispatchCommands(Map> commands) { 83 | this.plugin.getLogger().info("Dispatching commands to " + commands.size() + " players."); 84 | 85 | Map players = this.plugin.getPlatform() 86 | .getOnlinePlayers() 87 | .collect(Collectors.toMap(cs -> cs.getName().toLowerCase(Locale.ROOT), p -> p, (p1, p2) -> { 88 | String player1 = p1.getName() + " (" + p1.getUuid() + ')'; 89 | String player2 = p2.getName() + " (" + p2.getUuid() + ')'; 90 | this.plugin.getLogger().warn("Duplicate players names: " + player1 + " / " + player2); 91 | return p1; 92 | })); 93 | 94 | for (Map.Entry> entry : commands.entrySet()) { 95 | String playerName = entry.getKey(); 96 | CommandSender player = players.get(playerName.toLowerCase(Locale.ROOT)); 97 | 98 | if (player != null) { 99 | playerName = player.getName(); 100 | } 101 | 102 | for (String command : entry.getValue()) { 103 | command = command.replace("{player}", playerName) 104 | .replace("{uuid}", player != null ? player.getUuid().toString() : "?"); 105 | 106 | this.plugin.getLogger().info("Dispatching command for player " + playerName + ": " + command); 107 | 108 | this.plugin.getPlatform().dispatchConsoleCommand(command); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/tasks/TpsTask.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.tasks; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public class TpsTask implements Runnable { 6 | 7 | private static final long TPS_TIME = TimeUnit.SECONDS.toNanos(20); 8 | 9 | private double tps = 20; 10 | private int currentTick = 0; 11 | 12 | private long lastTickTime = 0; 13 | 14 | @Override 15 | public void run() { 16 | this.currentTick++; 17 | 18 | if (this.currentTick % 20 != 0) { 19 | return; 20 | } 21 | 22 | long currentNanoTime = System.nanoTime(); 23 | 24 | if (this.lastTickTime == 0) { 25 | this.lastTickTime = currentNanoTime; 26 | return; 27 | } 28 | 29 | this.tps = TPS_TIME / (double) (currentNanoTime - this.lastTickTime); 30 | 31 | this.lastTickTime = currentNanoTime; 32 | } 33 | 34 | public double getTps() { 35 | return this.tps; 36 | } 37 | 38 | public int getCurrentTick() { 39 | return this.currentTick; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/users/EditMoneyResult.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.users; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class EditMoneyResult { 6 | 7 | @SerializedName("old_balance") 8 | private final double oldBalance; 9 | @SerializedName("new_balance") 10 | private final double newBalance; 11 | 12 | public EditMoneyResult(double oldBalance, double newBalance) { 13 | this.oldBalance = oldBalance; 14 | this.newBalance = newBalance; 15 | } 16 | 17 | public double getOldBalance() { 18 | return oldBalance; 19 | } 20 | 21 | public double getNewBalance() { 22 | return newBalance; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/users/MoneyAction.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.users; 2 | 3 | import java.util.Locale; 4 | 5 | public enum MoneyAction { 6 | ADD, REMOVE, SET; 7 | 8 | @Override 9 | public String toString() { 10 | return name().toLowerCase(Locale.ROOT); 11 | } 12 | 13 | public static MoneyAction fromString(String action) { 14 | try { 15 | return MoneyAction.valueOf(action.toUpperCase(Locale.ROOT)); 16 | } catch (IllegalArgumentException e) { 17 | return null; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/users/UserManager.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.users; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.data.UserInfo; 5 | 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | public class UserManager { 12 | 13 | private final Map usersByName = new ConcurrentHashMap<>(); 14 | private final AzLinkPlugin plugin; 15 | 16 | public UserManager(AzLinkPlugin plugin) { 17 | this.plugin = plugin; 18 | } 19 | 20 | public Optional getUserByName(String name) { 21 | return Optional.ofNullable(this.usersByName.get(name)); 22 | } 23 | 24 | public void addUser(UserInfo user) { 25 | this.usersByName.put(user.getName(), user); 26 | } 27 | 28 | public CompletableFuture editMoney(UserInfo user, MoneyAction action, double amount) { 29 | return this.plugin.getHttpClient().editMoney(user, action.toString(), amount) 30 | .thenApply(result -> { 31 | user.setMoney(result.getNewBalance()); 32 | return user; 33 | }); 34 | } 35 | 36 | /** 37 | * @deprecated Use {@link #editMoney(UserInfo, MoneyAction, double)} instead. 38 | */ 39 | @Deprecated 40 | public CompletableFuture editMoney(UserInfo user, String action, double amount) { 41 | return editMoney(user, MoneyAction.valueOf(action.toUpperCase()), amount); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/utils/Hash.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.utils; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | /** 8 | * Simple class to create md5 and sha hash. 9 | * 10 | *

md5 and sha1 shouldn't be used anymore.

11 | */ 12 | public enum Hash { 13 | 14 | @Deprecated 15 | MD5("MD5"), 16 | @Deprecated 17 | SHA_1("SHA-1"), 18 | SHA_256("SHA-256"), 19 | SHA_384("SHA-384"), 20 | SHA_512("SHA-512"); 21 | 22 | private final String name; 23 | 24 | Hash(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getName() { 29 | return this.name; 30 | } 31 | 32 | public String hash(String text) { 33 | try { 34 | MessageDigest digest = MessageDigest.getInstance(this.name); 35 | byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8)); 36 | StringBuilder result = new StringBuilder(2 * hash.length); 37 | 38 | for (byte b : hash) { 39 | String hex = Integer.toHexString(b & 0xff); 40 | 41 | (hex.length() > 1 ? result : result.append('0')).append(hex); 42 | } 43 | 44 | return result.toString(); 45 | } catch (NoSuchAlgorithmException e) { 46 | throw new UnsupportedOperationException(this.name + " is not supported on this platform", e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/utils/SystemUtils.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.utils; 2 | 3 | import javax.management.MBeanServer; 4 | import javax.management.MalformedObjectNameException; 5 | import javax.management.ObjectName; 6 | import java.lang.management.ManagementFactory; 7 | 8 | public final class SystemUtils { 9 | 10 | private static final MBeanServer BEAN_SERVER; 11 | private static final ObjectName OS_OBJECT; 12 | 13 | static { 14 | try { 15 | BEAN_SERVER = ManagementFactory.getPlatformMBeanServer(); 16 | OS_OBJECT = ObjectName.getInstance("java.lang:type=OperatingSystem"); 17 | } catch (MalformedObjectNameException e) { 18 | throw new RuntimeException(e); 19 | } 20 | } 21 | 22 | private SystemUtils() { 23 | throw new UnsupportedOperationException(); 24 | } 25 | 26 | public static double getCpuUsage() throws Exception { 27 | return (double) BEAN_SERVER.getAttribute(OS_OBJECT, "ProcessCpuLoad") * 100.0; 28 | } 29 | 30 | public static double getMemoryUsage() { 31 | return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024.0 / 1024.0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/com/azuriom/azlink/common/utils/UpdateChecker.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common.utils; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.net.URI; 11 | import java.net.URL; 12 | import java.util.Objects; 13 | 14 | public class UpdateChecker { 15 | 16 | private static final String RELEASE_URL = "https://api.github.com/repos/Azuriom/AzLink/releases/latest"; 17 | 18 | private final AzLinkPlugin plugin; 19 | 20 | public UpdateChecker(AzLinkPlugin plugin) { 21 | this.plugin = plugin; 22 | } 23 | 24 | public static int compareVersions(String version1, String version2) 25 | throws NumberFormatException { 26 | Objects.requireNonNull(version1, "version1"); 27 | Objects.requireNonNull(version2, "version2"); 28 | 29 | String[] version1Parts = parseVersion(version1).split("\\."); 30 | String[] version2Parts = parseVersion(version2).split("\\."); 31 | int maxLength = Math.max(version1Parts.length, version2Parts.length); 32 | 33 | for (int i = 0; i < maxLength; i++) { 34 | int v1 = i < version1Parts.length ? Integer.parseInt(version1Parts[i]) : 0; 35 | int v2 = i < version2Parts.length ? Integer.parseInt(version2Parts[i]) : 0; 36 | 37 | if (v1 != v2) { 38 | return Integer.compare(v1, v2); 39 | } 40 | } 41 | 42 | return 0; 43 | } 44 | 45 | private static String parseVersion(String version) { 46 | return version.replace("v", "").replace("-SNAPSHOT", ""); 47 | } 48 | 49 | public void checkUpdates() { 50 | try { 51 | URL url = URI.create(RELEASE_URL).toURL(); 52 | 53 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) { 54 | JsonObject json = AzLinkPlugin.getGson().fromJson(reader, JsonObject.class); 55 | JsonElement lastVersionJson = json.get("tag_name"); 56 | 57 | if (lastVersionJson == null) { 58 | return; 59 | } 60 | 61 | String currentVersion = this.plugin.getPlatform().getPluginVersion(); 62 | String lastVersion = lastVersionJson.getAsString(); 63 | 64 | if (compareVersions(lastVersion, currentVersion) > 0) { 65 | this.plugin.getLogger().warn("A new update of AzLink is available: " + lastVersion); 66 | this.plugin.getLogger().warn("You can download it on https://azuriom.com/azlink"); 67 | } 68 | } 69 | } catch (IOException e) { 70 | this.plugin.getLogger().warn("Failed to check for updates: " + e.getMessage()); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /common/src/test/java/com/azuriom/azlink/common/HashTest.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common; 2 | 3 | import com.azuriom.azlink.common.utils.Hash; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.MethodSource; 7 | 8 | import java.util.stream.Stream; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | class HashTest { 13 | 14 | @ParameterizedTest 15 | @MethodSource("hashTestData") 16 | void testSha256(String[] data) { 17 | assertEquals(data[1], Hash.SHA_256.hash(data[0])); 18 | } 19 | 20 | @ParameterizedTest 21 | @MethodSource("hashTestData") 22 | void testSha384(String[] data) { 23 | assertEquals(data[2], Hash.SHA_384.hash(data[0])); 24 | } 25 | 26 | @ParameterizedTest 27 | @MethodSource("hashTestData") 28 | void testSha512(String[] data) { 29 | assertEquals(data[3], Hash.SHA_512.hash(data[0])); 30 | } 31 | 32 | private static Stream hashTestData() { 33 | String[] test1 = { 34 | "H5vBfLcF3vqaCo8", 35 | "e936363862ca96e0866afaa51ce622959a626938b1328a6a03921941989922fb", 36 | "ad7e3d46584ebcfff0c420e0b7e40b1c612f0f46d94054af2dcbf0f65a148ec47e56461ec93073f6b35c24aafd8906dc", 37 | "754d79ec450e8f1090aba4b0c25e9a0602d351561cb6f39c902ad3a9e779dbaf2d7ae194dde3d35492530f77566fc90d0137027f3fdf8fa560c8d19ed73767ec", 38 | }; 39 | String[] test2 = { 40 | "YCJLMo7uX5t7WxG", 41 | "35d729f6ab4c9abe9409bbdca2896eba1bc4608dab2e0f80fece72a67002866b", 42 | "1e49c84bc3b4d827fafef3fe7ef34966d5884ebe1bf814d6ec71d05cb530889fe196ed8a184c82377b566c8bfd95ebdd", 43 | "c4c940c69bf0cbd24057d409be393373e26593d5b0ac8117c49c929a17131defe321dea2589664e75c438e0bf0635074a91ee3f4fbb3d3e3b211f9771587fdee", 44 | }; 45 | String[] test3 = { 46 | "g3kmfbpwfHdDFQL", 47 | "cfa211834f280d238965a6d4afa15c8851b3286ee0cec146dd6a598e389568e3", 48 | "96b714d99c039829328275f3b90fa5abe304e4d78ed8114292ab0d16e9c1e636c57d763aae11f8490b2cd8216c5b64c2", 49 | "370ca5636ecbabe0f992dae196fe79cadeddea736a712a3cca5c9417bdffc252e26636e6e1d0a08c35ee10f28f5c88fa165935e0d5bc8ce480a14ee6b8950f61", 50 | }; 51 | 52 | return Stream.of(test1, test2, test3).map(s -> Arguments.of((Object) s)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common/src/test/java/com/azuriom/azlink/common/ThreadFactoryBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common; 2 | 3 | import com.azuriom.azlink.common.scheduler.ThreadFactoryBuilder; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class ThreadFactoryBuilderTest { 9 | 10 | private final Runnable voidRunnable = () -> {}; 11 | 12 | @Test 13 | void testThreadFactoryBuilder() { 14 | ThreadFactoryBuilder factory = new ThreadFactoryBuilder().name("thread-factory-test-%t").priority(2); 15 | 16 | Thread thread0 = factory.newThread(this.voidRunnable); 17 | Thread thread1 = factory.newThread(this.voidRunnable); 18 | 19 | assertEquals("thread-factory-test-0", thread0.getName()); 20 | assertEquals("thread-factory-test-1", thread1.getName()); 21 | assertEquals(2, thread1.getPriority()); 22 | assertFalse(thread0.isDaemon()); 23 | 24 | ThreadFactoryBuilder daemonFactory = factory.daemon(); 25 | 26 | Thread daemonThread = daemonFactory.newThread(this.voidRunnable); 27 | 28 | assertTrue(daemonThread.isDaemon()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/test/java/com/azuriom/azlink/common/UpdateCheckerTest.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.common; 2 | 3 | import com.azuriom.azlink.common.utils.UpdateChecker; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.CsvSource; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | class UpdateCheckerTest { 10 | 11 | @ParameterizedTest 12 | @CsvSource({ 13 | "0, 1.2, 1.2.0", 14 | "0, 1.2.1, 1.2.1", 15 | "1, 1.2.1, 1.2.0", 16 | "1, 1.2.1, 1.2", 17 | "1, 1.2.1, 0.8.1", 18 | "-1, 0.9, 1.0.1", 19 | "-1, 1.0.1, 1.10.0", 20 | "-1, 1.0.1, 1.10.0", 21 | "-1, 1.1.2, 1.2.0", 22 | }) 23 | void testCompareVersions(int expected, String ver1, String ver2) { 24 | assertEquals(expected, UpdateChecker.compareVersions(ver1, ver2)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2G 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azuriom/AzLink/423f91b0b45277b7e4a50fbe600e0e64dcaa27ae/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | org.gradle.wrapper.GradleWrapperMain \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /nukkit/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://repo.opencollab.dev/maven-releases/' } 3 | maven { url 'https://repo.opencollab.dev/maven-snapshots/' } 4 | } 5 | 6 | dependencies { 7 | implementation project(':azlink-common') 8 | compileOnly 'cn.nukkit:nukkit:1.0-SNAPSHOT' 9 | } 10 | 11 | processResources { 12 | filesMatching('*.yml') { 13 | expand 'pluginVersion': project.version 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /nukkit/src/main/java/com/azuriom/azlink/nukkit/AzLinkNukkitPlugin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.nukkit; 2 | 3 | import cn.nukkit.command.PluginCommand; 4 | import cn.nukkit.command.PluginIdentifiableCommand; 5 | import cn.nukkit.plugin.PluginBase; 6 | import com.azuriom.azlink.common.AzLinkPlatform; 7 | import com.azuriom.azlink.common.AzLinkPlugin; 8 | import com.azuriom.azlink.common.command.CommandSender; 9 | import com.azuriom.azlink.common.data.WorldData; 10 | import com.azuriom.azlink.common.logger.LoggerAdapter; 11 | import com.azuriom.azlink.common.platform.PlatformInfo; 12 | import com.azuriom.azlink.common.platform.PlatformType; 13 | import com.azuriom.azlink.common.scheduler.JavaSchedulerAdapter; 14 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 15 | import com.azuriom.azlink.common.tasks.TpsTask; 16 | import com.azuriom.azlink.nukkit.command.NukkitCommandExecutor; 17 | import com.azuriom.azlink.nukkit.command.NukkitCommandSender; 18 | import com.azuriom.azlink.nukkit.utils.NukkitLoggerAdapter; 19 | 20 | import java.nio.file.Path; 21 | import java.util.Optional; 22 | import java.util.stream.Stream; 23 | 24 | public final class AzLinkNukkitPlugin extends PluginBase implements AzLinkPlatform { 25 | 26 | private final SchedulerAdapter scheduler = new JavaSchedulerAdapter( 27 | r -> getServer().getScheduler().scheduleTask(this, r), 28 | r -> getServer().getScheduler().scheduleTask(this, r, true) 29 | ); 30 | 31 | private final TpsTask tpsTask = new TpsTask(); 32 | 33 | private AzLinkPlugin plugin; 34 | private LoggerAdapter logger; 35 | 36 | @Override 37 | public void onLoad() { 38 | this.logger = new NukkitLoggerAdapter(getLogger()); 39 | } 40 | 41 | @Override 42 | public void onEnable() { 43 | this.plugin = new AzLinkPlugin(this); 44 | this.plugin.init(); 45 | 46 | PluginIdentifiableCommand command = getCommand("azlink"); 47 | ((PluginCommand) command).setExecutor(new NukkitCommandExecutor(this.plugin)); 48 | 49 | getServer().getScheduler().scheduleDelayedRepeatingTask(this, this.tpsTask, 0, 1); 50 | } 51 | 52 | @Override 53 | public void onDisable() { 54 | if (this.plugin != null) { 55 | this.plugin.shutdown(); 56 | } 57 | } 58 | 59 | @Override 60 | public AzLinkPlugin getPlugin() { 61 | return this.plugin; 62 | } 63 | 64 | @Override 65 | public LoggerAdapter getLoggerAdapter() { 66 | return this.logger; 67 | } 68 | 69 | @Override 70 | public SchedulerAdapter getSchedulerAdapter() { 71 | return this.scheduler; 72 | } 73 | 74 | @Override 75 | public PlatformInfo getPlatformInfo() { 76 | return new PlatformInfo(getServer().getName(), getServer().getVersion()); 77 | } 78 | 79 | @Override 80 | public PlatformType getPlatformType() { 81 | return PlatformType.NUKKIT; 82 | } 83 | 84 | @Override 85 | public String getPluginVersion() { 86 | return getDescription().getVersion(); 87 | } 88 | 89 | @Override 90 | public Optional getWorldData() { 91 | int loadedChunks = getServer().getLevels().values().stream() 92 | .mapToInt(w -> w.getChunks().size()) 93 | .sum(); 94 | 95 | int entities = getServer().getLevels().values().stream() 96 | .mapToInt(w -> w.getEntities().length) 97 | .sum(); 98 | 99 | return Optional.of(new WorldData(this.tpsTask.getTps(), loadedChunks, entities)); 100 | } 101 | 102 | @Override 103 | public Path getDataDirectory() { 104 | return getDataFolder().toPath(); 105 | } 106 | 107 | @Override 108 | public Stream getOnlinePlayers() { 109 | return getServer().getOnlinePlayers().values().stream().map(NukkitCommandSender::new); 110 | } 111 | 112 | @Override 113 | public int getMaxPlayers() { 114 | return getServer().getMaxPlayers(); 115 | } 116 | 117 | @Override 118 | public void dispatchConsoleCommand(String command) { 119 | getServer().dispatchCommand(getServer().getConsoleSender(), command); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /nukkit/src/main/java/com/azuriom/azlink/nukkit/command/NukkitCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.nukkit.command; 2 | 3 | import cn.nukkit.command.Command; 4 | import cn.nukkit.command.CommandExecutor; 5 | import cn.nukkit.command.CommandSender; 6 | import com.azuriom.azlink.common.AzLinkPlugin; 7 | import com.azuriom.azlink.common.command.AzLinkCommand; 8 | 9 | public class NukkitCommandExecutor extends AzLinkCommand implements CommandExecutor { 10 | 11 | public NukkitCommandExecutor(AzLinkPlugin plugin) { 12 | super(plugin); 13 | } 14 | 15 | @Override 16 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 17 | execute(new NukkitCommandSender(sender), args); 18 | 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /nukkit/src/main/java/com/azuriom/azlink/nukkit/command/NukkitCommandSender.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.nukkit.command; 2 | 3 | import cn.nukkit.Player; 4 | import cn.nukkit.utils.TextFormat; 5 | import com.azuriom.azlink.common.command.CommandSender; 6 | 7 | import java.util.UUID; 8 | 9 | public class NukkitCommandSender implements CommandSender { 10 | 11 | private final cn.nukkit.command.CommandSender commandSender; 12 | 13 | public NukkitCommandSender(cn.nukkit.command.CommandSender commandSender) { 14 | this.commandSender = commandSender; 15 | } 16 | 17 | @Override 18 | public String getName() { 19 | return this.commandSender.getName(); 20 | } 21 | 22 | @Override 23 | public UUID getUuid() { 24 | if (this.commandSender instanceof Player) { 25 | return ((Player) this.commandSender).getUniqueId(); 26 | } 27 | 28 | return UUID.nameUUIDFromBytes(getName().getBytes()); 29 | } 30 | 31 | @Override 32 | public void sendMessage(String message) { 33 | this.commandSender.sendMessage(TextFormat.colorize(message)); 34 | } 35 | 36 | @Override 37 | public boolean hasPermission(String permission) { 38 | return this.commandSender.hasPermission(permission); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nukkit/src/main/java/com/azuriom/azlink/nukkit/utils/NukkitLoggerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.nukkit.utils; 2 | 3 | import cn.nukkit.utils.Logger; 4 | import com.azuriom.azlink.common.logger.LoggerAdapter; 5 | 6 | public class NukkitLoggerAdapter implements LoggerAdapter { 7 | 8 | private final Logger logger; 9 | 10 | public NukkitLoggerAdapter(Logger logger) { 11 | this.logger = logger; 12 | } 13 | 14 | @Override 15 | public void info(String message) { 16 | this.logger.info(message); 17 | } 18 | 19 | @Override 20 | public void info(String message, Throwable throwable) { 21 | this.logger.info(message, throwable); 22 | } 23 | 24 | @Override 25 | public void warn(String message) { 26 | this.logger.warning(message); 27 | } 28 | 29 | @Override 30 | public void warn(String message, Throwable throwable) { 31 | this.logger.warning(message, throwable); 32 | } 33 | 34 | @Override 35 | public void error(String message) { 36 | this.logger.error(message); 37 | } 38 | 39 | @Override 40 | public void error(String message, Throwable throwable) { 41 | this.logger.error(message, throwable); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /nukkit/src/main/resources/nukkit.yml: -------------------------------------------------------------------------------- 1 | name: AzLink 2 | main: com.azuriom.azlink.nukkit.AzLinkNukkitPlugin 3 | version: ${pluginVersion} 4 | author: Azuriom Team 5 | description: The plugin to link your Azuriom website with your server 6 | website: https://azuriom.com 7 | api: ["1.0.0"] 8 | commands: 9 | azlink: 10 | description: Manage the AzLink plugin. 11 | usage: /azlink [status|setup|fetch|port] 12 | aliases: [azuriomlink] 13 | 14 | permissions: 15 | azlink.admin: 16 | description: Allows you to use the /azlink command. 17 | default: op 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'azlink' 2 | 3 | def modules = [ 4 | 'common', 5 | 'bukkit', 6 | 'bungee', 7 | 'sponge', 8 | 'sponge-legacy', 9 | 'velocity', 10 | 'nukkit', 11 | 'universal', 12 | 'universal-legacy', 13 | ] 14 | 15 | modules.each { 16 | include ":azlink-${it}" 17 | project(":azlink-${it}").projectDir = file(it) 18 | } 19 | -------------------------------------------------------------------------------- /sponge-legacy/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'net.kyori.blossom' version '1.3.1' 3 | } 4 | 5 | repositories { 6 | maven { url 'https://repo.spongepowered.org/repository/maven-public/' } 7 | } 8 | 9 | dependencies { 10 | implementation project(':azlink-common') 11 | compileOnly 'org.spongepowered:spongeapi:7.4.0' 12 | annotationProcessor 'org.spongepowered:spongeapi:7.4.0' 13 | } 14 | 15 | blossom { 16 | replaceToken '${pluginVersion}', project.version, 'src/main/java/com/azuriom/azlink/sponge/legacy/AzLinkSpongePlugin.java' 17 | } 18 | -------------------------------------------------------------------------------- /sponge-legacy/src/main/java/com/azuriom/azlink/sponge/legacy/AzLinkSpongePlugin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge.legacy; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlatform; 4 | import com.azuriom.azlink.common.AzLinkPlugin; 5 | import com.azuriom.azlink.common.command.CommandSender; 6 | import com.azuriom.azlink.common.data.WorldData; 7 | import com.azuriom.azlink.common.logger.LoggerAdapter; 8 | import com.azuriom.azlink.common.logger.Slf4jLoggerAdapter; 9 | import com.azuriom.azlink.common.platform.PlatformInfo; 10 | import com.azuriom.azlink.common.platform.PlatformType; 11 | import com.azuriom.azlink.common.scheduler.JavaSchedulerAdapter; 12 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 13 | import com.azuriom.azlink.common.tasks.TpsTask; 14 | import com.azuriom.azlink.sponge.legacy.command.SpongeCommandExecutor; 15 | import com.azuriom.azlink.sponge.legacy.command.SpongeCommandSender; 16 | import com.google.common.collect.Iterables; 17 | import com.google.inject.Inject; 18 | import org.slf4j.Logger; 19 | import org.spongepowered.api.Game; 20 | import org.spongepowered.api.Platform; 21 | import org.spongepowered.api.config.ConfigDir; 22 | import org.spongepowered.api.event.Listener; 23 | import org.spongepowered.api.event.game.state.GamePreInitializationEvent; 24 | import org.spongepowered.api.event.game.state.GameStoppedEvent; 25 | import org.spongepowered.api.plugin.Dependency; 26 | import org.spongepowered.api.plugin.Plugin; 27 | import org.spongepowered.api.plugin.PluginContainer; 28 | import org.spongepowered.api.scheduler.SpongeExecutorService; 29 | import org.spongepowered.api.scheduler.Task; 30 | 31 | import java.nio.file.Path; 32 | import java.util.Optional; 33 | import java.util.stream.Stream; 34 | 35 | @Plugin( 36 | id = "azlink", 37 | name = "AzLink", 38 | version = "${pluginVersion}", 39 | description = "The plugin to link your Azuriom website with your server.", 40 | url = "https://azuriom.com", 41 | authors = "Azuriom Team", 42 | dependencies = @Dependency(id = Platform.API_ID) 43 | ) 44 | public final class AzLinkSpongePlugin implements AzLinkPlatform { 45 | 46 | private final TpsTask tpsTask = new TpsTask(); 47 | 48 | private final Game game; 49 | private final Path configDirectory; 50 | private final LoggerAdapter logger; 51 | 52 | private SchedulerAdapter scheduler; 53 | private AzLinkPlugin plugin; 54 | 55 | @Inject 56 | public AzLinkSpongePlugin(Game game, @ConfigDir(sharedRoot = false) Path configDirectory, Logger logger) { 57 | this.game = game; 58 | this.configDirectory = configDirectory; 59 | this.logger = new Slf4jLoggerAdapter(logger); 60 | } 61 | 62 | @Listener 63 | public void onGamePreInitialization(GamePreInitializationEvent event) { 64 | this.scheduler = initScheduler(); 65 | 66 | this.plugin = new AzLinkPlugin(this); 67 | this.plugin.init(); 68 | 69 | this.game.getCommandManager().register(this, new SpongeCommandExecutor(this.plugin), "azlink", "azuriomlink"); 70 | 71 | Task.builder().intervalTicks(1).execute(this.tpsTask).submit(this); 72 | } 73 | 74 | @Listener 75 | public void onGameStop(GameStoppedEvent event) { 76 | if (this.plugin != null) { 77 | this.plugin.shutdown(); 78 | } 79 | } 80 | 81 | @Override 82 | public AzLinkPlugin getPlugin() { 83 | return this.plugin; 84 | } 85 | 86 | @Override 87 | public LoggerAdapter getLoggerAdapter() { 88 | return this.logger; 89 | } 90 | 91 | @Override 92 | public SchedulerAdapter getSchedulerAdapter() { 93 | return this.scheduler; 94 | } 95 | 96 | @Override 97 | public PlatformType getPlatformType() { 98 | return PlatformType.SPONGE; 99 | } 100 | 101 | @Override 102 | public PlatformInfo getPlatformInfo() { 103 | Platform platform = this.game.getPlatform(); 104 | PluginContainer version = platform.getContainer(Platform.Component.IMPLEMENTATION); 105 | 106 | return new PlatformInfo(version.getName(), version.getVersion().orElse("unknown")); 107 | } 108 | 109 | @Override 110 | public String getPluginVersion() { 111 | return "${pluginVersion}"; 112 | } 113 | 114 | @Override 115 | public Path getDataDirectory() { 116 | return this.configDirectory; 117 | } 118 | 119 | @Override 120 | public Optional getWorldData() { 121 | int loadedChunks = this.game.getServer().getWorlds().stream() 122 | .mapToInt(w -> Iterables.size(w.getLoadedChunks())) 123 | .sum(); 124 | 125 | int entities = this.game.getServer().getWorlds().stream() 126 | .mapToInt(w -> w.getEntities().size()) 127 | .sum(); 128 | 129 | return Optional.of(new WorldData(this.tpsTask.getTps(), loadedChunks, entities)); 130 | } 131 | 132 | @Override 133 | public Stream getOnlinePlayers() { 134 | return this.game.getServer().getOnlinePlayers().stream().map(SpongeCommandSender::new); 135 | } 136 | 137 | @Override 138 | public void dispatchConsoleCommand(String command) { 139 | this.game.getCommandManager().process(this.game.getServer().getConsole(), command); 140 | } 141 | 142 | @Override 143 | public int getMaxPlayers() { 144 | return this.game.getServer().getMaxPlayers(); 145 | } 146 | 147 | private SchedulerAdapter initScheduler() { 148 | SpongeExecutorService syncExecutor = this.game.getScheduler().createSyncExecutor(this); 149 | SpongeExecutorService asyncExecutor = this.game.getScheduler().createAsyncExecutor(this); 150 | 151 | return new JavaSchedulerAdapter(asyncExecutor, syncExecutor, asyncExecutor); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /sponge-legacy/src/main/java/com/azuriom/azlink/sponge/legacy/command/SpongeCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge.legacy.command; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.command.AzLinkCommand; 5 | import org.spongepowered.api.command.CommandCallable; 6 | import org.spongepowered.api.command.CommandResult; 7 | import org.spongepowered.api.command.CommandSource; 8 | import org.spongepowered.api.text.Text; 9 | import org.spongepowered.api.world.Location; 10 | import org.spongepowered.api.world.World; 11 | 12 | import javax.annotation.Nonnull; 13 | import javax.annotation.Nullable; 14 | import java.util.List; 15 | import java.util.Optional; 16 | 17 | public class SpongeCommandExecutor extends AzLinkCommand implements CommandCallable { 18 | 19 | public SpongeCommandExecutor(AzLinkPlugin plugin) { 20 | super(plugin); 21 | } 22 | 23 | @Override 24 | @Nonnull 25 | public CommandResult process(@Nonnull CommandSource source, String arguments) { 26 | execute(new SpongeCommandSender(source), arguments.split(" ", -1)); 27 | 28 | return CommandResult.success(); 29 | } 30 | 31 | @Override 32 | @Nonnull 33 | public List getSuggestions(@Nonnull CommandSource source, String arguments, @Nullable Location targetPosition) { 34 | return tabComplete(new SpongeCommandSender(source), arguments.split(" ", -1)); 35 | } 36 | 37 | @Override 38 | public boolean testPermission(CommandSource source) { 39 | return source.hasPermission("azlink.admin"); 40 | } 41 | 42 | @Override 43 | @Nonnull 44 | public Optional getShortDescription(@Nonnull CommandSource source) { 45 | return Optional.of(Text.of("Manage the AzLink plugin.")); 46 | } 47 | 48 | @Override 49 | @Nonnull 50 | public Optional getHelp(@Nonnull CommandSource source) { 51 | return Optional.empty(); 52 | } 53 | 54 | @Override 55 | @Nonnull 56 | public Text getUsage(@Nonnull CommandSource source) { 57 | return Text.of(getUsage()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sponge-legacy/src/main/java/com/azuriom/azlink/sponge/legacy/command/SpongeCommandSender.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge.legacy.command; 2 | 3 | import com.azuriom.azlink.common.command.CommandSender; 4 | import org.spongepowered.api.command.CommandSource; 5 | import org.spongepowered.api.text.serializer.TextSerializers; 6 | import org.spongepowered.api.util.Identifiable; 7 | 8 | import java.util.UUID; 9 | 10 | public class SpongeCommandSender implements CommandSender { 11 | 12 | private final CommandSource source; 13 | 14 | public SpongeCommandSender(CommandSource source) { 15 | this.source = source; 16 | } 17 | 18 | @Override 19 | public String getName() { 20 | return this.source.getName(); 21 | } 22 | 23 | @Override 24 | public UUID getUuid() { 25 | if (this.source instanceof Identifiable) { 26 | return ((Identifiable) this.source).getUniqueId(); 27 | } 28 | 29 | return UUID.nameUUIDFromBytes(getName().getBytes()); 30 | } 31 | 32 | @Override 33 | public void sendMessage(String message) { 34 | this.source.sendMessage(TextSerializers.FORMATTING_CODE.deserialize(message)); 35 | } 36 | 37 | @Override 38 | public boolean hasPermission(String permission) { 39 | return this.source.hasPermission(permission); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sponge/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://repo.spongepowered.org/repository/maven-public/' } 3 | } 4 | 5 | dependencies { 6 | implementation project(':azlink-common') 7 | compileOnly 'org.spongepowered:spongeapi:8.0.0' 8 | } 9 | 10 | processResources { 11 | filesMatching('**/*.json') { 12 | expand 'pluginVersion': project.version 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sponge/src/main/java/com/azuriom/azlink/sponge/AzLinkSpongePlugin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlatform; 4 | import com.azuriom.azlink.common.AzLinkPlugin; 5 | import com.azuriom.azlink.common.command.CommandSender; 6 | import com.azuriom.azlink.common.data.WorldData; 7 | import com.azuriom.azlink.common.logger.LoggerAdapter; 8 | import com.azuriom.azlink.common.platform.PlatformInfo; 9 | import com.azuriom.azlink.common.platform.PlatformType; 10 | import com.azuriom.azlink.common.scheduler.JavaSchedulerAdapter; 11 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 12 | import com.azuriom.azlink.common.tasks.TpsTask; 13 | import com.azuriom.azlink.sponge.command.SpongeCommandExecutor; 14 | import com.azuriom.azlink.sponge.command.SpongeCommandSender; 15 | import com.azuriom.azlink.sponge.integrations.SkinsRestorerIntegration; 16 | import com.azuriom.azlink.sponge.logger.Log4jLoggerAdapter; 17 | import com.google.common.collect.Iterables; 18 | import com.google.inject.Inject; 19 | import org.apache.logging.log4j.Logger; 20 | import org.spongepowered.api.Game; 21 | import org.spongepowered.api.Platform; 22 | import org.spongepowered.api.Platform.Component; 23 | import org.spongepowered.api.Server; 24 | import org.spongepowered.api.command.Command.Raw; 25 | import org.spongepowered.api.command.exception.CommandException; 26 | import org.spongepowered.api.config.ConfigDir; 27 | import org.spongepowered.api.event.Listener; 28 | import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; 29 | import org.spongepowered.api.event.lifecycle.StartedEngineEvent; 30 | import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; 31 | import org.spongepowered.api.scheduler.Task; 32 | import org.spongepowered.api.scheduler.TaskExecutorService; 33 | import org.spongepowered.api.util.Ticks; 34 | import org.spongepowered.configurate.ConfigurationNode; 35 | import org.spongepowered.configurate.hocon.HoconConfigurationLoader; 36 | import org.spongepowered.plugin.PluginContainer; 37 | import org.spongepowered.plugin.builtin.jvm.Plugin; 38 | import org.spongepowered.plugin.metadata.PluginMetadata; 39 | 40 | import java.io.IOException; 41 | import java.nio.file.Path; 42 | import java.util.Optional; 43 | import java.util.stream.Stream; 44 | 45 | @Plugin("azlink") 46 | public final class AzLinkSpongePlugin implements AzLinkPlatform { 47 | 48 | private final TpsTask tpsTask = new TpsTask(); 49 | private final PluginContainer pluginContainer; 50 | private final Game game; 51 | private final Path configDirectory; 52 | private final LoggerAdapter logger; 53 | private final AzLinkPlugin plugin; 54 | private SchedulerAdapter scheduler; 55 | private ConfigurationNode config; 56 | 57 | @Inject 58 | public AzLinkSpongePlugin(PluginContainer pluginContainer, Game game, @ConfigDir(sharedRoot = false) Path configDirectory, Logger logger) { 59 | this.pluginContainer = pluginContainer; 60 | this.game = game; 61 | this.configDirectory = configDirectory; 62 | this.logger = new Log4jLoggerAdapter(logger); 63 | this.plugin = new AzLinkPlugin(this); 64 | } 65 | 66 | @Listener 67 | public void onServerStarted(StartedEngineEvent event) { 68 | this.scheduler = this.initScheduler(); 69 | this.plugin.init(); 70 | 71 | Task task = Task.builder() 72 | .interval(Ticks.of(1)) 73 | .execute(this.tpsTask) 74 | .plugin(this.pluginContainer) 75 | .build(); 76 | 77 | event.engine().scheduler().submit(task); 78 | 79 | loadConfig(); 80 | 81 | if (this.game.pluginManager().plugin("skinsrestorer").isPresent() 82 | && this.config.node("skinsrestorer-integration").getBoolean()) { 83 | this.game.eventManager().registerListeners(this.pluginContainer, new SkinsRestorerIntegration(this)); 84 | } 85 | } 86 | 87 | @Listener 88 | public void onServerStop(StoppingEngineEvent event) { 89 | if (this.plugin != null) { 90 | this.plugin.shutdown(); 91 | } 92 | } 93 | 94 | @Listener 95 | public void onRegisterCommands(RegisterCommandEvent event) { 96 | event.register(this.pluginContainer, new SpongeCommandExecutor(this.plugin), "azlink", "azuriomlink"); 97 | } 98 | 99 | @Override 100 | public AzLinkPlugin getPlugin() { 101 | return this.plugin; 102 | } 103 | 104 | @Override 105 | public LoggerAdapter getLoggerAdapter() { 106 | return this.logger; 107 | } 108 | 109 | @Override 110 | public SchedulerAdapter getSchedulerAdapter() { 111 | return this.scheduler; 112 | } 113 | 114 | @Override 115 | public PlatformType getPlatformType() { 116 | return PlatformType.SPONGE; 117 | } 118 | 119 | @Override 120 | public PlatformInfo getPlatformInfo() { 121 | Platform platform = this.game.platform(); 122 | PluginMetadata version = platform.container(Component.IMPLEMENTATION).metadata(); 123 | 124 | return new PlatformInfo(version.name().orElse("Sponge"), version.version().getQualifier()); 125 | } 126 | 127 | @Override 128 | public String getPluginVersion() { 129 | return this.pluginContainer.metadata().version().toString(); 130 | } 131 | 132 | @Override 133 | public Path getDataDirectory() { 134 | return this.configDirectory; 135 | } 136 | 137 | @Override 138 | public Optional getWorldData() { 139 | int loadedChunks = this.game.server().worldManager() 140 | .worlds() 141 | .stream() 142 | .mapToInt(w -> Iterables.size(w.loadedChunks())) 143 | .sum(); 144 | int entities = this.game.server().worldManager() 145 | .worlds() 146 | .stream() 147 | .mapToInt(w -> w.entities().size()) 148 | .sum(); 149 | 150 | return Optional.of(new WorldData(this.tpsTask.getTps(), loadedChunks, entities)); 151 | } 152 | 153 | @Override 154 | public Stream getOnlinePlayers() { 155 | return this.game.server().onlinePlayers().stream() 156 | .map(player -> new SpongeCommandSender(player, player)); 157 | } 158 | 159 | @Override 160 | public void dispatchConsoleCommand(String command) { 161 | try { 162 | this.game.server().commandManager().process(this.game.systemSubject(), command); 163 | } catch (CommandException e) { 164 | throw new RuntimeException(e); 165 | } 166 | } 167 | 168 | @Override 169 | public int getMaxPlayers() { 170 | return this.game.server().maxPlayers(); 171 | } 172 | 173 | private SchedulerAdapter initScheduler() { 174 | TaskExecutorService syncExecutor = this.game.server().scheduler().executor(this.pluginContainer); 175 | TaskExecutorService asyncExecutor = this.game.asyncScheduler().executor(this.pluginContainer); 176 | 177 | return new JavaSchedulerAdapter(asyncExecutor, syncExecutor, asyncExecutor); 178 | } 179 | 180 | private void loadConfig() { 181 | Path configPath = this.configDirectory.resolve("azlink.conf"); 182 | 183 | try { 184 | saveResource(configPath, "azlink.conf"); 185 | 186 | this.config = HoconConfigurationLoader.builder() 187 | .path(configPath) 188 | .build() 189 | .load(); 190 | } catch (IOException e) { 191 | throw new RuntimeException("Unable to load configuration", e); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge.command; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.command.AzLinkCommand; 5 | import net.kyori.adventure.text.Component; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.spongepowered.api.command.Command.Raw; 8 | import org.spongepowered.api.command.CommandCause; 9 | import org.spongepowered.api.command.CommandCompletion; 10 | import org.spongepowered.api.command.CommandResult; 11 | import org.spongepowered.api.command.parameter.ArgumentReader.Mutable; 12 | 13 | import java.util.List; 14 | import java.util.Optional; 15 | import java.util.stream.Collectors; 16 | 17 | public class SpongeCommandExecutor extends AzLinkCommand implements Raw { 18 | 19 | public SpongeCommandExecutor(AzLinkPlugin plugin) { 20 | super(plugin); 21 | } 22 | 23 | @Override 24 | public CommandResult process(CommandCause cause, Mutable arguments) { 25 | this.execute(new SpongeCommandSender(cause), arguments.input().split(" ", -1)); 26 | 27 | return CommandResult.success(); 28 | } 29 | 30 | @Override 31 | public List complete(CommandCause cause, Mutable arguments) { 32 | String[] args = arguments.input().split(" ", -1); 33 | 34 | return this.tabComplete(new SpongeCommandSender(cause), args).stream() 35 | .map(CommandCompletion::of) 36 | .collect(Collectors.toList()); 37 | } 38 | 39 | @Override 40 | public boolean canExecute(CommandCause cause) { 41 | return cause.hasPermission("azlink.admin"); 42 | } 43 | 44 | @Override 45 | public Optional shortDescription(CommandCause cause) { 46 | return Optional.of(Component.text("Manage the AzLink plugin.")); 47 | } 48 | 49 | @Override 50 | public Optional extendedDescription(CommandCause cause) { 51 | return Optional.empty(); 52 | } 53 | 54 | @Override 55 | public Optional help(@NonNull CommandCause cause) { 56 | return shortDescription(cause); 57 | } 58 | 59 | @Override 60 | public Component usage(CommandCause cause) { 61 | return Component.text(getUsage()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sponge/src/main/java/com/azuriom/azlink/sponge/command/SpongeCommandSender.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge.command; 2 | 3 | import com.azuriom.azlink.common.command.CommandSender; 4 | import net.kyori.adventure.audience.Audience; 5 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 6 | import org.spongepowered.api.command.CommandCause; 7 | import org.spongepowered.api.service.permission.Subject; 8 | import org.spongepowered.api.util.Identifiable; 9 | import org.spongepowered.api.util.Nameable; 10 | 11 | import java.util.UUID; 12 | 13 | public class SpongeCommandSender implements CommandSender { 14 | 15 | private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder() 16 | .character('&') 17 | .extractUrls() 18 | .build(); 19 | 20 | private final Subject subject; 21 | private final Audience audience; 22 | 23 | public SpongeCommandSender(CommandCause commandCause) { 24 | this(commandCause.subject(), commandCause.audience()); 25 | } 26 | 27 | public SpongeCommandSender(Subject subject, Audience audience) { 28 | this.subject = subject; 29 | this.audience = audience; 30 | } 31 | 32 | @Override 33 | public String getName() { 34 | if (this.subject instanceof Nameable) { 35 | return ((Nameable) this.subject).name(); 36 | } 37 | 38 | return this.subject.friendlyIdentifier().orElse(this.subject.identifier()); 39 | } 40 | 41 | @Override 42 | public UUID getUuid() { 43 | if (this.subject instanceof Identifiable) { 44 | return ((Identifiable) this.subject).uniqueId(); 45 | } 46 | 47 | return UUID.nameUUIDFromBytes(this.subject.identifier().getBytes()); 48 | } 49 | 50 | @Override 51 | public void sendMessage(String message) { 52 | this.audience.sendMessage(SERIALIZER.deserialize(message)); 53 | } 54 | 55 | @Override 56 | public boolean hasPermission(String permission) { 57 | return this.subject.hasPermission(permission); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sponge/src/main/java/com/azuriom/azlink/sponge/integrations/SkinsRestorerIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge.integrations; 2 | 3 | import com.azuriom.azlink.common.integrations.BaseSkinsRestorer; 4 | import com.azuriom.azlink.sponge.AzLinkSpongePlugin; 5 | import org.spongepowered.api.entity.living.player.server.ServerPlayer; 6 | import org.spongepowered.api.event.Listener; 7 | import org.spongepowered.api.event.network.ServerSideConnectionEvent; 8 | 9 | public class SkinsRestorerIntegration 10 | extends BaseSkinsRestorer { 11 | 12 | public SkinsRestorerIntegration(AzLinkSpongePlugin plugin) { 13 | super(plugin.getPlugin(), ServerPlayer.class); 14 | } 15 | 16 | @Listener 17 | public void onPlayerJoin(ServerSideConnectionEvent.Join event) { 18 | handleJoin(event.player().name(), event.player()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sponge/src/main/java/com/azuriom/azlink/sponge/logger/Log4jLoggerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.sponge.logger; 2 | 3 | import com.azuriom.azlink.common.logger.LoggerAdapter; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | public class Log4jLoggerAdapter implements LoggerAdapter { 7 | 8 | private final Logger logger; 9 | 10 | public Log4jLoggerAdapter(Logger logger) { 11 | this.logger = logger; 12 | } 13 | 14 | @Override 15 | public void info(String message) { 16 | this.logger.info(message); 17 | } 18 | 19 | @Override 20 | public void info(String message, Throwable throwable) { 21 | this.logger.info(message, throwable); 22 | } 23 | 24 | @Override 25 | public void warn(String message) { 26 | this.logger.warn(message); 27 | } 28 | 29 | @Override 30 | public void warn(String message, Throwable throwable) { 31 | this.logger.warn(message, throwable); 32 | } 33 | 34 | @Override 35 | public void error(String message) { 36 | this.logger.error(message); 37 | } 38 | 39 | @Override 40 | public void error(String message, Throwable throwable) { 41 | this.logger.error(message, throwable); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sponge/src/main/resources/META-INF/sponge_plugins.json: -------------------------------------------------------------------------------- 1 | { 2 | "loader": { 3 | "name": "java_plain", 4 | "version": "1.0" 5 | }, 6 | "license": "MIT", 7 | "plugins": [ 8 | { 9 | "loader": "java_plain", 10 | "id": "azlink", 11 | "name": "AzLink", 12 | "version": "${pluginVersion}", 13 | "entrypoint": "com.azuriom.azlink.sponge.AzLinkSpongePlugin", 14 | "description": "The plugin to link your Azuriom website with your server.", 15 | "links": { 16 | "homepage": "https://azuriom.com", 17 | "source": "https://github.com/Azuriom/AzLink", 18 | "issues": "https://github.com/Azuriom/AzLink/issues" 19 | }, 20 | "contributors": [ 21 | { 22 | "name": "Azuriom", 23 | "description": "Developper" 24 | } 25 | ], 26 | "dependencies": [ 27 | { 28 | "id": "spongeapi", 29 | "version": "8.0.0" 30 | } 31 | ] 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /sponge/src/main/resources/azlink.conf: -------------------------------------------------------------------------------- 1 | # When enabled, if SkinsRestorer is installed, and the SkinAPI plugin is present on the website, 2 | # the player's skin will be updated to the website's skin 3 | skinsrestorer-integration = false 4 | -------------------------------------------------------------------------------- /universal-legacy/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.gradleup.shadow' version '8.3.5' 3 | } 4 | 5 | dependencies { 6 | implementation project(':azlink-common') 7 | implementation project(':azlink-bukkit') 8 | implementation project(':azlink-bungee') 9 | implementation project(':azlink-sponge') 10 | implementation project(':azlink-sponge-legacy') 11 | implementation project(':azlink-velocity') 12 | implementation project(':azlink-nukkit') 13 | 14 | implementation 'com.google.code.gson:gson:2.8.9' 15 | implementation 'io.netty:netty-handler:4.1.100.Final' 16 | implementation 'io.netty:netty-codec-http:4.1.100.Final' 17 | } 18 | 19 | shadowJar { 20 | archiveFileName = "AzLink-Legacy-${project.version}.jar" 21 | 22 | relocate 'com.google.gson', 'com.azuriom.azlink.libs.gson' 23 | relocate 'io.netty', 'com.azuriom.azlink.libs.netty' 24 | 25 | manifest { 26 | attributes 'Paperweight-Mappings-Namespace': 'mojang' 27 | } 28 | } 29 | 30 | artifacts { 31 | archives shadowJar 32 | } 33 | -------------------------------------------------------------------------------- /universal/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.gradleup.shadow' version '8.3.5' 3 | } 4 | 5 | dependencies { 6 | implementation project(':azlink-common') 7 | implementation project(':azlink-bukkit') 8 | implementation project(':azlink-bungee') 9 | implementation project(':azlink-sponge') 10 | implementation project(':azlink-sponge-legacy') 11 | implementation project(':azlink-velocity') 12 | implementation project(':azlink-nukkit') 13 | } 14 | 15 | shadowJar { 16 | archiveFileName = "AzLink-${project.version}.jar" 17 | 18 | manifest { 19 | attributes 'Paperweight-Mappings-Namespace': 'mojang' 20 | } 21 | } 22 | 23 | artifacts { 24 | archives shadowJar 25 | } 26 | -------------------------------------------------------------------------------- /velocity/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'net.kyori.blossom' version '1.3.1' 3 | } 4 | 5 | repositories { 6 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 7 | maven { url 'https://maven.elytrium.net/repo/' } 8 | maven { url 'https://repo.nickuc.com/maven-releases/' } 9 | } 10 | 11 | dependencies { 12 | implementation project(':azlink-common') 13 | compileOnly 'com.velocitypowered:velocity-api:3.1.1' 14 | compileOnly 'net.elytrium.limboapi:api:1.1.13' 15 | compileOnly 'net.elytrium:limboauth:1.1.1' 16 | compileOnly 'com.nickuc.login:nlogin-api:10.3' 17 | annotationProcessor 'com.velocitypowered:velocity-api:3.1.1' 18 | } 19 | 20 | java { 21 | // LimboAuth support 22 | disableAutoTargetJvm() 23 | } 24 | 25 | blossom { 26 | replaceToken '${pluginVersion}', project.version, 'src/main/java/com/azuriom/azlink/velocity/AzLinkVelocityPlugin.java' 27 | } 28 | -------------------------------------------------------------------------------- /velocity/src/main/java/com/azuriom/azlink/velocity/AzLinkVelocityPlugin.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.velocity; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlatform; 4 | import com.azuriom.azlink.common.AzLinkPlugin; 5 | import com.azuriom.azlink.common.command.CommandSender; 6 | import com.azuriom.azlink.common.logger.LoggerAdapter; 7 | import com.azuriom.azlink.common.logger.Slf4jLoggerAdapter; 8 | import com.azuriom.azlink.common.platform.PlatformInfo; 9 | import com.azuriom.azlink.common.platform.PlatformType; 10 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 11 | import com.azuriom.azlink.velocity.command.VelocityCommandExecutor; 12 | import com.azuriom.azlink.velocity.command.VelocityCommandSender; 13 | import com.azuriom.azlink.velocity.integrations.LimboAuthIntegration; 14 | import com.azuriom.azlink.velocity.integrations.NLoginIntegration; 15 | import com.azuriom.azlink.velocity.integrations.SkinsRestorerIntegration; 16 | import com.google.inject.Inject; 17 | import com.velocitypowered.api.event.Subscribe; 18 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 19 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 20 | import com.velocitypowered.api.plugin.Dependency; 21 | import com.velocitypowered.api.plugin.Plugin; 22 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 23 | import com.velocitypowered.api.proxy.ProxyServer; 24 | import com.velocitypowered.api.util.ProxyVersion; 25 | import ninja.leaping.configurate.ConfigurationNode; 26 | import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; 27 | import org.slf4j.Logger; 28 | 29 | import java.io.IOException; 30 | import java.nio.file.Path; 31 | import java.util.stream.Stream; 32 | 33 | @Plugin( 34 | id = "azlink", 35 | name = "AzLink", 36 | version = "${pluginVersion}", 37 | description = "The plugin to link your Azuriom website with your server.", 38 | url = "https://azuriom.com", 39 | authors = "Azuriom Team", 40 | dependencies = { 41 | @Dependency(id = "limboauth", optional = true), 42 | @Dependency(id = "nlogin", optional = true), 43 | @Dependency(id = "skinsrestorer", optional = true), 44 | } 45 | ) 46 | public final class AzLinkVelocityPlugin implements AzLinkPlatform { 47 | 48 | private final SchedulerAdapter scheduler = new VelocitySchedulerAdapter(this); 49 | 50 | private final ProxyServer proxy; 51 | private final Path dataDirectory; 52 | private final LoggerAdapter logger; 53 | 54 | private AzLinkPlugin plugin; 55 | private ConfigurationNode config; 56 | 57 | @Inject 58 | public AzLinkVelocityPlugin(ProxyServer proxy, @DataDirectory Path dataDirectory, Logger logger) { 59 | this.proxy = proxy; 60 | this.dataDirectory = dataDirectory; 61 | this.logger = new Slf4jLoggerAdapter(logger); 62 | } 63 | 64 | @Subscribe 65 | public void onProxyInitialization(ProxyInitializeEvent event) { 66 | try { 67 | Class.forName("com.velocitypowered.api.command.SimpleCommand"); 68 | } catch (ClassNotFoundException e) { 69 | this.logger.error("AzLink requires Velocity 1.1.0 or higher"); 70 | this.logger.error("You can download the latest version of Velocity on https://papermc.io/downloads/velocity"); 71 | return; 72 | } 73 | 74 | this.plugin = new AzLinkPlugin(this); 75 | this.plugin.init(); 76 | 77 | this.proxy.getCommandManager() 78 | .register("azlink", new VelocityCommandExecutor(this.plugin), "azuriomlink"); 79 | 80 | loadConfig(); 81 | 82 | if (this.proxy.getPluginManager().getPlugin("limboauth").isPresent() 83 | && this.config.getNode("limboauth-integration").getBoolean()) { 84 | this.proxy.getEventManager().register(this, new LimboAuthIntegration(this)); 85 | } 86 | 87 | if (this.proxy.getPluginManager().getPlugin("nlogin").isPresent() 88 | && this.config.getNode("nlogin-integration").getBoolean()) { 89 | NLoginIntegration.register(this); 90 | } 91 | 92 | if (this.proxy.getPluginManager().getPlugin("skinsrestorer").isPresent() 93 | && this.config.getNode("skinsrestorer-integration").getBoolean()) { 94 | this.proxy.getEventManager().register(this, new SkinsRestorerIntegration(this)); 95 | } 96 | } 97 | 98 | @Subscribe 99 | public void onProxyShutdown(ProxyShutdownEvent event) { 100 | if (this.plugin != null) { 101 | this.plugin.shutdown(); 102 | } 103 | } 104 | 105 | @Override 106 | public AzLinkPlugin getPlugin() { 107 | return this.plugin; 108 | } 109 | 110 | @Override 111 | public LoggerAdapter getLoggerAdapter() { 112 | return this.logger; 113 | } 114 | 115 | @Override 116 | public SchedulerAdapter getSchedulerAdapter() { 117 | return this.scheduler; 118 | } 119 | 120 | @Override 121 | public PlatformType getPlatformType() { 122 | return PlatformType.VELOCITY; 123 | } 124 | 125 | @Override 126 | public PlatformInfo getPlatformInfo() { 127 | ProxyVersion version = this.proxy.getVersion(); 128 | 129 | return new PlatformInfo(version.getName(), version.getVersion()); 130 | } 131 | 132 | @Override 133 | public String getPluginVersion() { 134 | return "${pluginVersion}"; 135 | } 136 | 137 | @Override 138 | public Path getDataDirectory() { 139 | return this.dataDirectory; 140 | } 141 | 142 | @Override 143 | public Stream getOnlinePlayers() { 144 | return this.proxy.getAllPlayers().stream().map(VelocityCommandSender::new); 145 | } 146 | 147 | @Override 148 | public int getMaxPlayers() { 149 | return this.proxy.getConfiguration().getShowMaxPlayers(); 150 | } 151 | 152 | @Override 153 | public void dispatchConsoleCommand(String command) { 154 | this.proxy.getCommandManager().executeAsync(this.proxy.getConsoleCommandSource(), command); 155 | } 156 | 157 | public ProxyServer getProxy() { 158 | return this.proxy; 159 | } 160 | 161 | private void loadConfig() { 162 | Path configPath = this.dataDirectory.resolve("config.yml"); 163 | 164 | try { 165 | saveResource(configPath, "velocity-config.yml"); 166 | 167 | this.config = YAMLConfigurationLoader.builder() 168 | .setPath(configPath) 169 | .build() 170 | .load(); 171 | } catch (IOException e) { 172 | throw new RuntimeException("Unable to load configuration", e); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /velocity/src/main/java/com/azuriom/azlink/velocity/VelocitySchedulerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.velocity; 2 | 3 | import com.azuriom.azlink.common.scheduler.CancellableTask; 4 | import com.azuriom.azlink.common.scheduler.SchedulerAdapter; 5 | import com.velocitypowered.api.scheduler.ScheduledTask; 6 | 7 | import java.util.concurrent.Executor; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class VelocitySchedulerAdapter implements SchedulerAdapter { 11 | 12 | private final Executor executor = this::executeAsync; 13 | 14 | private final AzLinkVelocityPlugin plugin; 15 | 16 | public VelocitySchedulerAdapter(AzLinkVelocityPlugin plugin) { 17 | this.plugin = plugin; 18 | } 19 | 20 | @Override 21 | public void executeSync(Runnable runnable) { 22 | this.executeAsync(runnable); 23 | } 24 | 25 | @Override 26 | public void executeAsync(Runnable runnable) { 27 | this.plugin.getProxy().getScheduler().buildTask(this.plugin, runnable).schedule(); 28 | } 29 | 30 | @Override 31 | public Executor syncExecutor() { 32 | return this.executor; 33 | } 34 | 35 | @Override 36 | public Executor asyncExecutor() { 37 | return this.executor; 38 | } 39 | 40 | @Override 41 | public CancellableTask scheduleAsyncLater(Runnable runnable, long delay, TimeUnit unit) { 42 | ScheduledTask task = this.plugin.getProxy().getScheduler() 43 | .buildTask(this.plugin, runnable) 44 | .delay(delay, unit) 45 | .schedule(); 46 | 47 | return new CancellableVelocityTask(task); 48 | } 49 | 50 | @Override 51 | public CancellableTask scheduleAsyncRepeating(Runnable runnable, long delay, long interval, TimeUnit unit) { 52 | ScheduledTask task = this.plugin.getProxy().getScheduler() 53 | .buildTask(this.plugin, runnable) 54 | .delay(delay, unit) 55 | .repeat(interval, unit) 56 | .schedule(); 57 | 58 | return new CancellableVelocityTask(task); 59 | } 60 | 61 | private static class CancellableVelocityTask implements CancellableTask { 62 | 63 | private final ScheduledTask task; 64 | 65 | public CancellableVelocityTask(ScheduledTask task) { 66 | this.task = task; 67 | } 68 | 69 | @Override 70 | public void cancel() { 71 | this.task.cancel(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.velocity.command; 2 | 3 | import com.azuriom.azlink.common.AzLinkPlugin; 4 | import com.azuriom.azlink.common.command.AzLinkCommand; 5 | import com.velocitypowered.api.command.SimpleCommand; 6 | 7 | import java.util.List; 8 | 9 | public class VelocityCommandExecutor extends AzLinkCommand implements SimpleCommand { 10 | 11 | public VelocityCommandExecutor(AzLinkPlugin plugin) { 12 | super(plugin); 13 | } 14 | 15 | @Override 16 | public void execute(Invocation invocation) { 17 | execute(new VelocityCommandSender(invocation.source()), invocation.arguments()); 18 | } 19 | 20 | @Override 21 | public List suggest(Invocation invocation) { 22 | return tabComplete(new VelocityCommandSender(invocation.source()), invocation.arguments()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /velocity/src/main/java/com/azuriom/azlink/velocity/command/VelocityCommandSender.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.velocity.command; 2 | 3 | import com.azuriom.azlink.common.command.CommandSender; 4 | import com.velocitypowered.api.command.CommandSource; 5 | import com.velocitypowered.api.proxy.Player; 6 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 7 | 8 | import java.util.UUID; 9 | 10 | public class VelocityCommandSender implements CommandSender { 11 | 12 | private final CommandSource source; 13 | 14 | public VelocityCommandSender(CommandSource source) { 15 | this.source = source; 16 | } 17 | 18 | @Override 19 | public String getName() { 20 | return this.source instanceof Player ? ((Player) this.source).getUsername() : "Console"; 21 | } 22 | 23 | @Override 24 | public UUID getUuid() { 25 | if (this.source instanceof Player) { 26 | return ((Player) this.source).getUniqueId(); 27 | } 28 | 29 | return UUID.nameUUIDFromBytes(getName().getBytes()); 30 | } 31 | 32 | @Override 33 | public void sendMessage(String message) { 34 | this.source.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); 35 | } 36 | 37 | @Override 38 | public boolean hasPermission(String permission) { 39 | return this.source.hasPermission(permission); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /velocity/src/main/java/com/azuriom/azlink/velocity/integrations/LimboAuthIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.velocity.integrations; 2 | 3 | import com.azuriom.azlink.velocity.AzLinkVelocityPlugin; 4 | import com.velocitypowered.api.event.Subscribe; 5 | import com.velocitypowered.api.proxy.Player; 6 | import net.elytrium.limboauth.event.PostRegisterEvent; 7 | 8 | import java.net.InetAddress; 9 | 10 | public class LimboAuthIntegration { 11 | 12 | private final AzLinkVelocityPlugin plugin; 13 | 14 | public LimboAuthIntegration(AzLinkVelocityPlugin plugin) { 15 | this.plugin = plugin; 16 | 17 | this.plugin.getLoggerAdapter().info("LimboAuth integration enabled."); 18 | } 19 | 20 | @Subscribe 21 | public void onPostRegister(PostRegisterEvent event) { 22 | Player player = event.getPlayer().getProxyPlayer(); 23 | String password = event.getPassword(); 24 | InetAddress ip = player.getRemoteAddress().getAddress(); 25 | 26 | this.plugin.getPlugin() 27 | .getHttpClient() 28 | .registerUser(player.getUsername(), null, player.getUniqueId(), password, ip) 29 | .exceptionally(ex -> { 30 | this.plugin.getLoggerAdapter().error("Unable to register " + player.getUsername(), ex); 31 | 32 | return null; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /velocity/src/main/java/com/azuriom/azlink/velocity/integrations/NLoginIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.velocity.integrations; 2 | 3 | import com.azuriom.azlink.common.integrations.BaseNLogin; 4 | import com.azuriom.azlink.velocity.AzLinkVelocityPlugin; 5 | import com.nickuc.login.api.enums.TwoFactorType; 6 | import com.nickuc.login.api.event.velocity.twofactor.TwoFactorAddEvent; 7 | import com.nickuc.login.api.event.velocity.account.PasswordUpdateEvent; 8 | import com.nickuc.login.api.event.velocity.auth.RegisterEvent; 9 | import com.velocitypowered.api.event.Subscribe; 10 | import com.velocitypowered.api.proxy.Player; 11 | 12 | import java.net.InetAddress; 13 | 14 | public class NLoginIntegration extends BaseNLogin { 15 | 16 | public NLoginIntegration(AzLinkVelocityPlugin plugin) { 17 | super(plugin.getPlugin()); 18 | } 19 | 20 | @Subscribe 21 | public void onEmailAdded(TwoFactorAddEvent event) { 22 | if (event.getType() != TwoFactorType.EMAIL) { 23 | return; 24 | } 25 | 26 | handleEmailUpdated(event.getPlayerId(), event.getPlayerName(), event.getAccount()); 27 | } 28 | 29 | @Subscribe 30 | public void onRegister(RegisterEvent event) { 31 | Player player = event.getPlayer(); 32 | InetAddress address = player.getRemoteAddress().getAddress(); 33 | 34 | handleRegister(player.getUniqueId(), player.getUsername(), event.getPassword(), address); 35 | } 36 | 37 | @Subscribe 38 | public void onPasswordUpdate(PasswordUpdateEvent event) { 39 | handleUpdatePassword(event.getPlayerId(), event.getPlayerName(), event.getNewPassword()); 40 | } 41 | 42 | public static void register(AzLinkVelocityPlugin plugin) { 43 | if (ensureApiVersion(plugin)) { 44 | plugin.getProxy().getEventManager().register(plugin, new NLoginIntegration(plugin)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /velocity/src/main/java/com/azuriom/azlink/velocity/integrations/SkinsRestorerIntegration.java: -------------------------------------------------------------------------------- 1 | package com.azuriom.azlink.velocity.integrations; 2 | 3 | 4 | import com.azuriom.azlink.common.integrations.BaseSkinsRestorer; 5 | import com.azuriom.azlink.velocity.AzLinkVelocityPlugin; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.connection.PostLoginEvent; 8 | import com.velocitypowered.api.proxy.Player; 9 | 10 | public class SkinsRestorerIntegration 11 | extends BaseSkinsRestorer { 12 | 13 | public SkinsRestorerIntegration(AzLinkVelocityPlugin plugin) { 14 | super(plugin.getPlugin(), Player.class); 15 | } 16 | 17 | @Subscribe 18 | public void onPlayerJoin(PostLoginEvent event) { 19 | handleJoin(event.getPlayer().getUsername(), event.getPlayer()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /velocity/src/main/resources/velocity-config.yml: -------------------------------------------------------------------------------- 1 | # When enabled, if SkinsRestorer is installed, and the SkinAPI plugin is present on the website, 2 | # the player's skin will be updated to the website's skin 3 | skinsrestorer-integration: false 4 | 5 | # When enabled, new users registered with the LimboAuth plugin will automatically be registered on the website 6 | # If you are using multiples servers, you should use the same database for AuthMe to prevent users 7 | # from registering multiple times 8 | limboauth-integration: false 9 | 10 | # When enabled, new users registered with the nLogin plugin will automatically be registered on the website 11 | # WARNING: You need nLogin version 10.2.43 or newer! 12 | # If you are using multiples servers, you should use the same database for nLogin to prevent users 13 | # from registering multiple times 14 | nlogin-integration: false 15 | --------------------------------------------------------------------------------