diff --git a/README.md b/README.md index c9f1789..185da32 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,14 @@ -# RedisBungee fork By Limework - -*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do it, tell mojang to implement transfer packet*. -[Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/) +# RedisBungee Limework's Fork The original project of RedisBungee is no longer maintained, so we have forked the plugin. -RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/) -to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) or [Velocity*](https://github.com/PaperMC/Velocity) proxies +RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/) +to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) +or [Velocity*](https://github.com/PaperMC/Velocity) proxies -Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40) +## Downloads -## Downloads [![](https://raw.githubusercontent.com/Prospector/badges/master/modrinth-badge-72h-padded.png)](https://modrinth.com/plugin/redisbungee) -or from github releases - -https://github.com/ProxioDev/RedisBungee/releases - -## notes -If you are looking to use Original RedisBungee without a change to internals, -with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/) -as its last version before internal changes. please note that you will not get support for any old builds unless critical bugs effecting both 0.6.5 and 0.7.0 or above. - -SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.87700/) -## Supported Redis versions -| Redis version | Supported | -|:-------------:|:---------:| -| 1.x.x | ✖ | -| 2.x.x | ✖ | -| 3.x.x | ✔ | -| 4.x.x | ✔ | -| 5.x.x | ✔ | -| 6.x.x | ✔ | -| 7.x.x | ✔ | - - -## Implementing RedisBungee in your plugin: [![RedisBungee Build](https://github.com/proxiodev/RedisBungee/actions/workflows/maven.yml/badge.svg)](https://github.com/Limework/RedisBungee/actions/workflows/maven.yml) [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) - -RedisBungee is distributed as a [Gradle](https://gradle.org/) project. - -By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) - -# Setup jitpack repository -## maven -```xml - - - jitpack.io - https://jitpack.io - - -``` -## gradle (kotlin dsl) -```kotlin -repositories { - maven("https://jitpack.io/") -} -``` - -# [BungeeCord](https://github.com/SpigotMC/BungeeCord) -add this in your project dependencies -## maven -```xml - - com.github.proxiodev.redisbungee - RedisBungee-Bungee - VERSION - provided - - - -``` -## gradle (kotlin dsl) -``` -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0") - -// USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOACTION AND REMOVE THE ABOVE STATEMENT -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0:all") { - exclude("redis.clients", "jedis") -} -``` -then in your project plugin.yml add `RedisBungee` to `depends` like this -```yaml -name: "yourplugin" -main: your.main.class -version: 1.0.0-SNAPSHOT -author: idk -depends: [ RedisBungee ] -``` - - -## [Velocity](https://github.com/PaperMC/Velocity) -## maven -```xml - - com.github.proxiodev.redisbungee - RedisBungee-Velocity - VERSION - provided - - -``` -## gradle (kotlin dsl) -``` -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0") - -// USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOACTION AND REMOVE THE ABOVE STATEMENT -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0:all") { - exclude("redis.clients", "jedis") -} -``` - -then to make your plugin depends on RedisBungee, make sure your plugin class Annotation have `@Dependency(id = "redisbungee")` like this -```java -@Plugin( - id = "myplugin", - name = "My Plugin", - version = "0.1.0-beta", - dependencies = { - @Dependency(id = "redisbungee") - } -) -public class PluginMainClass { - -} -``` -## Getting the latest commits to your code -If you want to use the latest commits without waiting for releases. -first, install it to your maven local repo -```bash -git clone https://github.com/ProxioDev/RedisBungee.git -cd RedisBungee -./gradlew publishToMavenLocal -``` -then use any of these in your project. -```xml - - com.imaginarycode.minecraft - RedisBungee-Bungee - VERSION - provided - - -``` -```xml - - com.imaginarycode.minecraft - RedisBungee-Velocity - VERSION - provided - - -``` -## Javadocs - -* API: https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc/ -* Velocity: https://ci.limework.net/RedisBungee/RedisBungee-Velocity/build/docs/javadoc/ -* Bungeecord: https://ci.limework.net/RedisBungee/RedisBungee-Bungee/build/docs/javadoc/ - -## Configuration - -**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is saved when the plugin first starts. - - -## compatibility with original RedisBungee in Bungeecord ecosystem -This fork ensures compatibility with old plugins, so it should work as drop replacement, -but since Api has been split from the platform there some changes that have to be done, so your plugin might not work if: - -* there is none at the moment, please report any findings at the issue page. - -Cluster mode compatibility in version 0.8.0: - -If you are using static legacy method `RedisBungee#getPool()` it might fail in: -* if Cluster mode is enabled, due fact its Uses different classes -* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than Jedis - ## Support open an issue with question button @@ -184,12 +19,7 @@ This project is distributed under Eclipse Public License 1.0 You can find it [here](https://github.com/proxiodev/RedisBungee/blob/master/LICENSE) -You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be found [here](https://github.com/minecrafter/RedisBungee) or spigot page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/) +You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be +found [here](https://github.com/minecrafter/RedisBungee) or spigot +page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/) - - -## YourKit - -YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). - -![YourKit](https://www.yourkit.com/images/yklogo.png) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 3af847e..fbd915e 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -1,3 +1,4 @@ +import java.time.Instant import java.io.ByteArrayOutputStream plugins { @@ -7,32 +8,25 @@ plugins { } -repositories { - mavenCentral() -} - - -val jedisVersion = "5.1.2" -val configurateVersion = "3.7.3" -val guavaVersion = "31.1-jre" - - dependencies { - api("com.google.guava:guava:$guavaVersion") - api("redis.clients:jedis:$jedisVersion") - api("com.squareup.okhttp:okhttp:2.7.5") - api("org.spongepowered:configurate-yaml:$configurateVersion") - - // tests - testImplementation("junit:junit:4.13.2") + api(libs.guava) + api(libs.jedis) + api(libs.okhttp) + api(libs.configurate) + api(libs.caffeine) + api(libs.adventure.api) + api(libs.adventure.gson) + api(libs.adventure.legacy) + api(libs.adventure.plain) + api(libs.adventure.miniMessage) } -description = "RedisBungee interafaces" +description = "RedisBungee interfaces" blossom { replaceToken("@version@", "$version") // GIT - var commit: String = "" + val commit: String; val commitStdout = ByteArrayOutputStream() rootProject.exec { standardOutput = commitStdout @@ -43,6 +37,7 @@ blossom { replaceToken("@git_commit@", commit) } + java { withJavadocJar() withSourcesJar() @@ -54,25 +49,27 @@ tasks { val options = options as StandardJavadocDocletOptions options.use() options.isDocFilesSubDirs = true + val jedisVersion = libs.jedis.get().version + val configurateVersion = libs.configurate.get().version + val guavaVersion = libs.guava.get().version + val adventureVersion = libs.guava.get().version options.links( "https://configurate.aoeu.xyz/$configurateVersion/apidocs/", // configurate "https://javadoc.io/doc/redis.clients/jedis/$jedisVersion/", // jedis - "https://guava.dev/releases/$guavaVersion/api/docs/" // guava + "https://guava.dev/releases/$guavaVersion/api/docs/", // guava + "https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine", + "https://jd.advntr.dev/api/$adventureVersion" + ) } - test { - useJUnitPlatform() - } - compileJava { options.encoding = Charsets.UTF_8.name() - options.release.set(8) + options.release.set(17) } javadoc { options.encoding = Charsets.UTF_8.name() - } processResources { filteringCharset = Charsets.UTF_8.name() diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java index ccbb95b..690fe5b 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java @@ -10,8 +10,6 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; @@ -19,6 +17,7 @@ import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import redis.clients.jedis.Jedis; @@ -40,21 +39,13 @@ import java.util.*; public abstract class AbstractRedisBungeeAPI { protected final RedisBungeePlugin plugin; private static AbstractRedisBungeeAPI abstractRedisBungeeAPI; - protected final List reservedChannels; - AbstractRedisBungeeAPI(RedisBungeePlugin plugin) { - // this does make sure that no one can place first initiated API class. + public AbstractRedisBungeeAPI(RedisBungeePlugin plugin) { + // this does make sure that no one can replace first initiated API class. if (abstractRedisBungeeAPI == null) { abstractRedisBungeeAPI = this; } - this.reservedChannels = ImmutableList.of( - "redisbungee-allservers", - "redisbungee-" + plugin.getConfiguration().getProxyId(), - "redisbungee-data" - ); - this.plugin = plugin; - } /** @@ -63,7 +54,7 @@ public abstract class AbstractRedisBungeeAPI { * @return a count of all players found */ public final int getPlayerCount() { - return plugin.getCount(); + return plugin.proxyDataManager().totalNetworkPlayers(); } /** @@ -74,7 +65,7 @@ public abstract class AbstractRedisBungeeAPI { * @return the last time a player was on, if online returns a 0 */ public final long getLastOnline(@NonNull UUID player) { - return plugin.getDataManager().getLastOnline(player); + return plugin.playerDataManager().getLastOnline(player); } /** @@ -86,7 +77,7 @@ public abstract class AbstractRedisBungeeAPI { */ @Nullable public final String getServerNameFor(@NonNull UUID player) { - return plugin.getDataManager().getServer(player); + return plugin.playerDataManager().getServerFor(player); } /** @@ -97,7 +88,7 @@ public abstract class AbstractRedisBungeeAPI { * @return a Set with all players found */ public final Set getPlayersOnline() { - return plugin.getPlayers(); + return plugin.proxyDataManager().networkPlayers(); } /** @@ -118,11 +109,11 @@ public abstract class AbstractRedisBungeeAPI { /** * Get a full list of players on all servers. * - * @return a immutable Multimap with all players found on this server + * @return a immutable Multimap with all players found on this network * @since 0.2.5 */ public final Multimap getServerToPlayers() { - return plugin.serverToPlayersCache(); + return plugin.playerDataManager().serversToPlayers(); } /** @@ -138,11 +129,11 @@ public abstract class AbstractRedisBungeeAPI { /** * Get a list of players on the specified proxy. * - * @param server a server name + * @param proxyID proxy id * @return a Set with all UUIDs found on this proxy */ - public final Set getPlayersOnProxy(@NonNull String server) { - return plugin.getPlayersOnProxy(server); + public final Set getPlayersOnProxy(@NonNull String proxyID) { + return plugin.proxyDataManager().getPlayersOn(proxyID); } /** @@ -163,7 +154,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.4 */ public final InetAddress getPlayerIp(@NonNull UUID player) { - return plugin.getDataManager().getIp(player); + return plugin.playerDataManager().getIpFor(player); } /** @@ -174,7 +165,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.3.3 */ public final String getProxy(@NonNull UUID player) { - return plugin.getDataManager().getProxy(player); + return plugin.playerDataManager().getProxyFor(player); } /** @@ -185,7 +176,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.5 */ public final void sendProxyCommand(@NonNull String command) { - plugin.sendProxyCommand("allservers", command); + sendProxyCommand("allservers", command); } /** @@ -198,19 +189,20 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.5 */ public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) { - plugin.sendProxyCommand(proxyId, command); + plugin.proxyDataManager().sendCommandTo(proxyId, command); } /** - * Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for - * PubSubMessageEvent to fire. + * Sends a message to a PubSub channel which makes PubSubMessageEvent fire. + *

+ * Note: Since 0.12.0 registering a channel api is no longer required * * @param channel The PubSub channel * @param message the message body to send * @since 0.3.3 */ public final void sendChannelMessage(@NonNull String channel, @NonNull String message) { - plugin.sendChannelMessage(channel, message); + plugin.proxyDataManager().sendChannelMessage(channel, message); } /** @@ -221,7 +213,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public final String getProxyId() { - return plugin.getConfiguration().getProxyId(); + return plugin.proxyDataManager().proxyId(); } /** @@ -245,7 +237,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public final List getAllProxies() { - return plugin.getProxiesIds(); + return plugin.proxyDataManager().proxiesIds(); } /** @@ -266,9 +258,10 @@ public abstract class AbstractRedisBungeeAPI { * * @param channels the channels to register * @since 0.3 + * @deprecated No longer required */ + @Deprecated public final void registerPubSubChannels(String... channels) { - plugin.getPubSubListener().addChannel(channels); } /** @@ -276,13 +269,10 @@ public abstract class AbstractRedisBungeeAPI { * * @param channels the channels to unregister * @since 0.3 + * @deprecated No longer required */ + @Deprecated public final void unregisterPubSubChannels(String... channels) { - for (String channel : channels) { - Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel"); - } - - plugin.getPubSubListener().removeChannel(channels); } /** @@ -355,14 +345,16 @@ public abstract class AbstractRedisBungeeAPI { /** * Kicks a player from the network + * calls {@link #getUuidFromName(String)} to get uuid * * @param playerName player name - * @param message kick message that player will see on kick + * @param message kick message that player will see on kick * @since 0.8.0 + * @deprecated */ - + @Deprecated public void kickPlayer(String playerName, String message) { - plugin.kickPlayer(playerName, message); + kickPlayer(getUuidFromName(playerName), message); } /** @@ -371,11 +363,38 @@ public abstract class AbstractRedisBungeeAPI { * @param playerUUID player name * @param message kick message that player will see on kick * @since 0.8.0 + * @deprecated */ + @Deprecated public void kickPlayer(UUID playerUUID, String message) { - plugin.kickPlayer(playerUUID, message); + kickPlayer(playerUUID, Component.text(message)); } + /** + * Kicks a player from the network + * calls {@link #getUuidFromName(String)} to get uuid + * + * @param playerName player name + * @param message kick message that player will see on kick + * @since 0.12.0 + */ + + public void kickPlayer(String playerName, Component message) { + kickPlayer(getUuidFromName(playerName), message); + } + + /** + * Kicks a player from the network + * + * @param playerUUID player name + * @param message kick message that player will see on kick + * @since 0.12.0 + */ + public void kickPlayer(UUID playerUUID, Component message) { + this.plugin.playerDataManager().kickPlayer(playerUUID, message); + } + + /** * This gives you instance of Jedis * @@ -457,6 +476,7 @@ public abstract class AbstractRedisBungeeAPI { /** * shows what mode is RedisBungee is on + * Basically what every redis mode is used like cluster or single instance. * * @return {@link RedisBungeeMode} * @since 0.8.0 diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java deleted file mode 100644 index 1bff5b5..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.net.InetAddresses; -import com.google.common.reflect.TypeToken; -import com.google.common.util.concurrent.UncheckedExecutionException; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -/** - * This class manages all the data that RedisBungee fetches from Redis, along with updates to that data. - * - * @since 0.3.3 - */ -public abstract class AbstractDataManager { - protected final RedisBungeePlugin

plugin; - private final Cache serverCache = createCache(); - private final Cache proxyCache = createCache(); - private final Cache ipCache = createCache(); - private final Cache lastOnlineCache = createCache(); - private final Gson gson = new Gson(); - - public AbstractDataManager(RedisBungeePlugin

plugin) { - this.plugin = plugin; - } - - private static Cache createCache() { - // TODO: Allow customization via cache specification, ala ServerListPlus - return CacheBuilder.newBuilder() - .maximumSize(1000) - .expireAfterWrite(1, TimeUnit.HOURS) - .build(); - } - - public String getServer(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.isPlayerOnAServer(player) ? plugin.getPlayerServerName(player) : null; - - try { - return serverCache.get(uuid, new RedisTask(plugin) { - @Override - public String unifiedJedisTask(UnifiedJedis unifiedJedis) { - return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "server"), "user not found"); - - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get server"); - throw new RuntimeException("Unable to get server for " + uuid, e); - } - } - - - public String getProxy(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.getConfiguration().getProxyId(); - - try { - return proxyCache.get(uuid, new RedisTask(plugin) { - @Override - public String unifiedJedisTask(UnifiedJedis unifiedJedis) { - return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "proxy"), "user not found"); - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get proxy"); - throw new RuntimeException("Unable to get proxy for " + uuid, e); - } - } - - public InetAddress getIp(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.getPlayerIp(player); - - try { - return ipCache.get(uuid, new RedisTask(plugin) { - @Override - public InetAddress unifiedJedisTask(UnifiedJedis unifiedJedis) { - String result = unifiedJedis.hget("player:" + uuid, "ip"); - if (result == null) - throw new NullPointerException("user not found"); - return InetAddresses.forString(result); - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get IP"); - throw new RuntimeException("Unable to get IP for " + uuid, e); - } - } - - public long getLastOnline(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return 0; - - try { - return lastOnlineCache.get(uuid, new RedisTask(plugin) { - - @Override - public Long unifiedJedisTask(UnifiedJedis unifiedJedis) { - String result = unifiedJedis.hget("player:" + uuid, "online"); - return result == null ? -1 : Long.parseLong(result); - } - }); - } catch (ExecutionException e) { - plugin.logFatal("Unable to get last time online"); - throw new RuntimeException("Unable to get last time online for " + uuid, e); - } - } - - protected void invalidate(UUID uuid) { - ipCache.invalidate(uuid); - lastOnlineCache.invalidate(uuid); - serverCache.invalidate(uuid); - proxyCache.invalidate(uuid); - } - - // Invalidate all entries related to this player, since they now lie. (call invalidate(uuid)) - public abstract void onPostLogin(PL event); - - // Invalidate all entries related to this player, since they now lie. (call invalidate(uuid)) - public abstract void onPlayerDisconnect(PD event); - - public abstract void onPubSubMessage(PS event); - - public abstract boolean handleKick(UUID target, String message); - - protected void handlePubSubMessage(String channel, String message) { - if (!channel.equals("redisbungee-data")) - return; - - // Partially deserialize the message so we can look at the action - JsonObject jsonObject = JsonParser.parseString(message).getAsJsonObject(); - - final String source = jsonObject.get("source").getAsString(); - - if (source.equals(plugin.getConfiguration().getProxyId())) - return; - - DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString()); - - switch (action) { - case JOIN: - final DataManagerMessage message1 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - proxyCache.put(message1.getTarget(), message1.getSource()); - lastOnlineCache.put(message1.getTarget(), (long) 0); - ipCache.put(message1.getTarget(), message1.getPayload().getAddress()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerJoinedNetworkEvent(message1.getTarget()); - plugin.fireEvent(event); - }); - break; - case LEAVE: - final DataManagerMessage message2 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - invalidate(message2.getTarget()); - lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerLeftNetworkEvent(message2.getTarget()); - plugin.fireEvent(event); - }); - break; - case SERVER_CHANGE: - final DataManagerMessage message3 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - serverCache.put(message3.getTarget(), message3.getPayload().getServer()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer()); - plugin.fireEvent(event); - }); - break; - case KICK: - final DataManagerMessage kickPayload = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - plugin.executeAsync(() -> handleKick(kickPayload.target, kickPayload.payload.message)); - break; - - } - } - - public static class DataManagerMessage { - private final UUID target; - private final String source; - private final Action action; // for future use! - private final T payload; - - public DataManagerMessage(UUID target, String source, Action action, T payload) { - this.target = target; - this.source = source; - this.action = action; - this.payload = payload; - } - - public UUID getTarget() { - return target; - } - - public String getSource() { - return source; - } - - public Action getAction() { - return action; - } - - public T getPayload() { - return payload; - } - - public enum Action { - JOIN, - LEAVE, - KICK, - SERVER_CHANGE - } - } - - public static abstract class Payload { - } - - public static class KickPayload extends Payload { - - private final String message; - - public KickPayload(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - } - - public static class LoginPayload extends Payload { - private final InetAddress address; - - public LoginPayload(InetAddress address) { - this.address = address; - } - - public InetAddress getAddress() { - return address; - } - } - - public static class ServerChangePayload extends Payload { - private final String server; - private final String oldServer; - - public ServerChangePayload(String server, String oldServer) { - this.server = server; - this.oldServer = oldServer; - } - - public String getServer() { - return server; - } - - public String getOldServer() { - return oldServer; - } - } - - - public static class LogoutPayload extends Payload { - private final long timestamp; - - public LogoutPayload(long timestamp) { - this.timestamp = timestamp; - } - - public long getTimestamp() { - return timestamp; - } - } -} \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java deleted file mode 100644 index 64b42ab..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - - -import com.google.common.collect.Multimap; -import com.google.common.collect.Multiset; -import com.google.common.io.ByteArrayDataOutput; -import com.google.gson.Gson; - -import java.net.InetAddress; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -public abstract class AbstractRedisBungeeListener { - - protected final RedisBungeePlugin plugin; - protected final List exemptAddresses; - protected final Gson gson = new Gson(); - - public AbstractRedisBungeeListener(RedisBungeePlugin plugin, List exemptAddresses) { - this.plugin = plugin; - this.exemptAddresses = exemptAddresses; - } - - public void onLogin(LE event) {} - - public abstract void onPostLogin(PLE event); - - public abstract void onPlayerDisconnect(PD event); - - public abstract void onServerChange(SC event); - - public abstract void onPing(PP event); - - public abstract void onPluginMessage(PM event); - - public abstract void onPubSubMessage(PS event); - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java deleted file mode 100644 index d3974a5..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - - -import redis.clients.jedis.JedisPubSub; - - -public class JedisPubSubHandler extends JedisPubSub { - - private final RedisBungeePlugin plugin; - - public JedisPubSubHandler(RedisBungeePlugin plugin) { - this.plugin = plugin; - } - - @Override - public void onMessage(final String s, final String s2) { - if (s2.trim().length() == 0) return; - plugin.executeAsync(new Runnable() { - @Override - public void run() { - Object event = plugin.createPubSubEvent(s, s2); - plugin.fireEvent(event); - } - }); - } -} \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java new file mode 100644 index 0000000..a6d600a --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.net.InetAddresses; +import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; +import org.json.JSONObject; +import redis.clients.jedis.ClusterPipeline; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.Response; +import redis.clients.jedis.UnifiedJedis; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public abstract class PlayerDataManager { + + protected final RedisBungeePlugin

plugin; + private final LoadingCache serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis); + private final LoadingCache lastServerCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastServerFromRedis); + private final LoadingCache proxyCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getProxyFromRedis); + private final LoadingCache ipCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getIpAddressFromRedis); + private final LoadingCache lastOnlineCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastOnlineFromRedis); + private final Object SERVERS_TO_PLAYERS_KEY = new Object(); + private final LoadingCache> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder); + private final UnifiedJedis unifiedJedis; + private final String proxyId; + private final String networkId; + + public PlayerDataManager(RedisBungeePlugin

plugin) { + this.plugin = plugin; + this.unifiedJedis = plugin.proxyDataManager().unifiedJedis(); + this.proxyId = plugin.proxyDataManager().proxyId(); + this.networkId = plugin.proxyDataManager().networkId(); + } + + // handle network wide + // server change + public abstract void onPlayerChangedServerNetworkEvent(SC event); + + public abstract void onNetworkPlayerQuit(NJE event); + + // local events + public abstract void onPubSubMessageEvent(PS event); + + public abstract void onServerConnectedEvent(CE event); + + public abstract void onLoginEvent(LE event); + + public abstract void onDisconnectEvent(DE event); + + + protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) { + this.serverCache.invalidate(event.getUuid()); + this.lastServerCache.invalidate(event.getUuid()); + } + + protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) { + this.proxyCache.invalidate(event.getUuid()); + this.serverCache.invalidate(event.getUuid()); + this.ipCache.invalidate(event.getUuid()); + this.lastOnlineCache.invalidate(event.getUuid()); + } + + protected void handlePubSubMessageEvent(IPubSubMessageEvent event) { + // kick api + if (event.getChannel().equals("redisbungee-kick")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(this.proxyId)) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + String message = data.getString("message"); + plugin.handlePlatformKick(uuid, COMPONENT_SERIALIZER.deserialize(message)); + return; + } + if (event.getChannel().equals("redisbungee-serverchange")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(this.proxyId)) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + String from = null; + if (data.has("from")) from = data.getString("from"); + String to = data.getString("to"); + plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to)); + return; + } + if (event.getChannel().equals("redisbungee-player-join")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(this.proxyId)) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid)); + return; + } + if (event.getChannel().equals("redisbungee-player-leave")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(this.proxyId)) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + } + + } + + protected void playerChangedServer(UUID uuid, String from, String to) { + JSONObject data = new JSONObject(); + data.put("proxy", this.proxyId); + data.put("uuid", uuid); + data.put("from", from); + data.put("to", to); + plugin.proxyDataManager().sendChannelMessage("redisbungee-serverchange", data.toString()); + plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to)); + handleServerChangeRedis(uuid, to); + } + + private final JSONComponentSerializer COMPONENT_SERIALIZER =JSONComponentSerializer.json(); + + public void kickPlayer(UUID uuid, Component message) { + if (!plugin.handlePlatformKick(uuid, message)) { // handle locally before SENDING a message + JSONObject data = new JSONObject(); + data.put("proxy", this.proxyId); + data.put("uuid", uuid); + data.put("message", COMPONENT_SERIALIZER.serialize(message)); + plugin.proxyDataManager().sendChannelMessage("redisbungee-kick", data.toString()); + } + } + + private void handleServerChangeRedis(UUID uuid, String server) { + Map data = new HashMap<>(); + data.put("server", server); + data.put("last-server", server); + unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", data); + } + + protected void addPlayer(final UUID uuid, final InetAddress inetAddress) { + Map redisData = new HashMap<>(); + redisData.put("last-online", String.valueOf(0)); + redisData.put("proxy", this.proxyId); + redisData.put("ip", inetAddress.getHostAddress()); + unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", redisData); + + JSONObject data = new JSONObject(); + data.put("proxy", this.proxyId); + data.put("uuid", uuid); + plugin.proxyDataManager().sendChannelMessage("redisbungee-player-join", data.toString()); + plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid)); + this.plugin.proxyDataManager().addPlayer(uuid); + } + + protected void removePlayer(UUID uuid) { + unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "last-online", String.valueOf(System.currentTimeMillis())); + unifiedJedis.hdel("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "server", "proxy", "ip"); + JSONObject data = new JSONObject(); + data.put("proxy", this.proxyId); + data.put("uuid", uuid); + plugin.proxyDataManager().sendChannelMessage("redisbungee-player-leave", data.toString()); + plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + this.plugin.proxyDataManager().removePlayer(uuid); + } + + + protected String getProxyFromRedis(UUID uuid) { + return unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "proxy"); + } + + protected String getServerFromRedis(UUID uuid) { + return unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "server"); + } + + protected String getLastServerFromRedis(UUID uuid) { + return unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "last-server"); + } + + protected InetAddress getIpAddressFromRedis(UUID uuid) { + String ip = unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "ip"); + if (ip == null) return null; + return InetAddresses.forString(ip); + } + + protected long getLastOnlineFromRedis(UUID uuid) { + String unixString = unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "last-online"); + if (unixString == null) return -1; + return Long.parseLong(unixString); + } + + public String getLastServerFor(UUID uuid) { + return this.lastServerCache.get(uuid); + } + public String getServerFor(UUID uuid) { + return this.serverCache.get(uuid); + } + + public String getProxyFor(UUID uuid) { + return this.proxyCache.get(uuid); + } + + public InetAddress getIpFor(UUID uuid) { + return this.ipCache.get(uuid); + } + + public long getLastOnline(UUID uuid) { + return this.lastOnlineCache.get(uuid); + } + + public Multimap serversToPlayers() { + return this.serverToPlayersCache.get(SERVERS_TO_PLAYERS_KEY); + } + + protected Multimap serversToPlayersBuilder(Object o) { + try { + return new RedisPipelineTask>(plugin) { + private final Set uuids = plugin.proxyDataManager().networkPlayers(); + private final ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + + @Override + public Multimap doPooledPipeline(Pipeline pipeline) { + HashMap> responses = new HashMap<>(); + for (UUID uuid : uuids) { + responses.put(uuid, pipeline.hget("redis-bungee::" + networkId + "::player::" + uuid + "::data", "server")); + } + pipeline.sync(); + responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); + return builder.build(); + } + + @Override + public Multimap clusterPipeline(ClusterPipeline pipeline) { + HashMap> responses = new HashMap<>(); + for (UUID uuid : uuids) { + responses.put(uuid, pipeline.hget("redis-bungee::" + networkId + "::player::" + uuid + "::data", "server")); + } + pipeline.sync(); + responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); + return builder.build(); + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java new file mode 100644 index 0000000..05e608c --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.gson.AbstractPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.DeathPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.HeartbeatPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.PubSubPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; +import redis.clients.jedis.*; +import redis.clients.jedis.params.XAddParams; +import redis.clients.jedis.params.XReadParams; +import redis.clients.jedis.resps.StreamEntry; + +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.google.common.base.Preconditions.checkArgument; + +public abstract class ProxyDataManager implements Runnable { + + private static final int MAX_ENTRIES = 10000; + + private final AtomicBoolean closed = new AtomicBoolean(false); + + private final UnifiedJedis unifiedJedis; + + // data: + // Proxy id, heartbeat (unix epoch from instant), players as int + private final ConcurrentHashMap heartbeats = new ConcurrentHashMap<>(); + + private final String networkId; + + private final String proxyId; + + private final String STREAM_ID; + + // This different from proxy id, just to detect if there is duplicate proxy using same proxy id + private final UUID dataManagerUUID = UUID.randomUUID(); + + protected final RedisBungeePlugin plugin; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(AbstractPayload.class, new AbstractPayloadSerializer()).registerTypeAdapter(HeartbeatPayload.class, new HeartbeatPayloadSerializer()).registerTypeAdapter(DeathPayload.class, new DeathPayloadSerializer()).registerTypeAdapter(PubSubPayload.class, new PubSubPayloadSerializer()).registerTypeAdapter(RunCommandPayload.class, new RunCommandPayloadSerializer()).create(); + + public ProxyDataManager(RedisBungeePlugin plugin) { + this.plugin = plugin; + this.proxyId = this.plugin.configuration().getProxyId(); + this.unifiedJedis = plugin.getSummoner().obtainResource(); + this.destroyProxyMembers(); + this.networkId = plugin.configuration().networkId(); + this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream"; + } + + public abstract Set getLocalOnlineUUIDs(); + + public Set getPlayersOn(String proxyId) { + checkArgument(proxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID"); + if (proxyId.equals(this.proxyId)) return this.getLocalOnlineUUIDs(); + if (!this.heartbeats.containsKey(proxyId)) { + return new HashSet<>(); // return empty hashset or null? + } + return getProxyMembers(proxyId); + } + + public List proxiesIds() { + return Collections.list(this.heartbeats.keys()); + } + + public synchronized void sendCommandTo(String proxyToRun, String command) { + if (isClosed()) return; + publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command)); + } + + public synchronized void sendChannelMessage(String channel, String message) { + if (isClosed()) return; + this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message)); + publishPayload(new PubSubPayload(this.proxyId, channel, message)); + } + + // call every 1 second + public synchronized void publishHeartbeat() { + if (isClosed()) return; + HeartbeatPayload.HeartbeatData heartbeatData = new HeartbeatPayload.HeartbeatData(Instant.now().getEpochSecond(), this.getLocalOnlineUUIDs().size()); + this.heartbeats.put(this.proxyId(), heartbeatData); + publishPayload(new HeartbeatPayload(this.proxyId, heartbeatData)); + } + + public Set networkPlayers() { + try { + return new RedisPipelineTask>(this.plugin) { + @Override + public Set doPooledPipeline(Pipeline pipeline) { + HashSet>> responses = new HashSet<>(); + for (String proxyId : proxiesIds()) { + responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players")); + } + pipeline.sync(); + HashSet uuids = new HashSet<>(); + for (Response> response : responses) { + for (String stringUUID : response.get()) { + uuids.add(UUID.fromString(stringUUID)); + } + } + return uuids; + } + + @Override + public Set clusterPipeline(ClusterPipeline pipeline) { + HashSet>> responses = new HashSet<>(); + for (String proxyId : proxiesIds()) { + responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players")); + } + pipeline.sync(); + HashSet uuids = new HashSet<>(); + for (Response> response : responses) { + for (String stringUUID : response.get()) { + uuids.add(UUID.fromString(stringUUID)); + } + } + return uuids; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException("unable to get network players", e); + } + + } + + public int totalNetworkPlayers() { + int players = 0; + for (HeartbeatPayload.HeartbeatData value : this.heartbeats.values()) { + players += value.players(); + } + return players; + } + + public Map eachProxyCount() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + heartbeats.forEach((proxy, data) -> builder.put(proxy, data.players())); + return builder.build(); + } + + // Call on close + private synchronized void publishDeath() { + publishPayload(new DeathPayload(this.proxyId)); + } + + private void publishPayload(AbstractPayload payload) { + Map data = new HashMap<>(); + data.put("payload", gson.toJson(payload)); + data.put("data-manager-uuid", this.dataManagerUUID.toString()); + data.put("class", payload.getClassName()); + this.unifiedJedis.xadd(STREAM_ID, XAddParams.xAddParams().maxLen(MAX_ENTRIES).id(StreamEntryID.NEW_ENTRY), data); + } + + + private void handleHeartBeat(HeartbeatPayload payload) { + String id = payload.senderProxy(); + if (!heartbeats.containsKey(id)) { + plugin.logInfo("Proxy {} has connected", id); + } + heartbeats.put(id, payload.data()); + } + + + // call every 1 minutes + public void correctionTask() { + // let's check this proxy players + Set localOnlineUUIDs = getLocalOnlineUUIDs(); + Set storedRedisUuids = getProxyMembers(this.proxyId); + + if (!localOnlineUUIDs.equals(storedRedisUuids)) { + plugin.logWarn("De-synced playerS set detected correcting...."); + Set add = new HashSet<>(localOnlineUUIDs); + Set remove = new HashSet<>(storedRedisUuids); + add.removeAll(storedRedisUuids); + remove.removeAll(localOnlineUUIDs); + for (UUID uuid : add) { + plugin.logWarn("found {} that isn't in the set, adding it to the Corrected set", uuid); + } + for (UUID uuid : remove) { + plugin.logWarn("found {} that does not belong to this proxy removing it from the corrected set", uuid); + } + try { + new RedisPipelineTask(plugin) { + @Override + public Void doPooledPipeline(Pipeline pipeline) { + Set removeString = new HashSet<>(); + for (UUID uuid : remove) { + removeString.add(uuid.toString()); + } + Set addString = new HashSet<>(); + for (UUID uuid : add) { + addString.add(uuid.toString()); + } + pipeline.srem("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", addString.toArray(new String[]{})); + pipeline.sync(); + return null; + } + + @Override + public Void clusterPipeline(ClusterPipeline pipeline) { + Set removeString = new HashSet<>(); + for (UUID uuid : remove) { + removeString.add(uuid.toString()); + } + Set addString = new HashSet<>(); + for (UUID uuid : add) { + addString.add(uuid.toString()); + } + pipeline.srem("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", addString.toArray(new String[]{})); + pipeline.sync(); + return null; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + plugin.logInfo("Player set has been corrected!"); + } + + + // handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~30 seconds + final Set deadProxies = new HashSet<>(); + for (Map.Entry stringHeartbeatDataEntry : this.heartbeats.entrySet()) { + String id = stringHeartbeatDataEntry.getKey(); + long heartbeat = stringHeartbeatDataEntry.getValue().heartbeat(); + if (Instant.now().getEpochSecond() - heartbeat > RedisUtil.PROXY_TIMEOUT) { + deadProxies.add(id); + cleanProxy(id); + } + } + try { + new RedisPipelineTask(plugin) { + @Override + public Void doPooledPipeline(Pipeline pipeline) { + for (String deadProxy : deadProxies) { + pipeline.del("redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players"); + } + pipeline.sync(); + return null; + } + + @Override + public Void clusterPipeline(ClusterPipeline pipeline) { + for (String deadProxy : deadProxies) { + pipeline.del("redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players"); + } + pipeline.sync(); + return null; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void handleProxyDeath(DeathPayload payload) { + cleanProxy(payload.senderProxy()); + } + + private void cleanProxy(String id) { + if (id.equals(this.proxyId())) { + return; + } + for (UUID uuid : getProxyMembers(id)) plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + this.heartbeats.remove(id); + plugin.logInfo("Proxy {} has disconnected", id); + } + + private void handleChannelMessage(PubSubPayload payload) { + String channel = payload.channel(); + String message = payload.message(); + this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message)); + } + + protected abstract void handlePlatformCommandExecution(String command); + + private void handleCommand(RunCommandPayload payload) { + String proxyToRun = payload.proxyToRun(); + String command = payload.command(); + if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) { + handlePlatformCommandExecution(command); + } + } + + + public void addPlayer(UUID uuid) { + this.unifiedJedis.sadd("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players", uuid.toString()); + } + + public void removePlayer(UUID uuid) { + this.unifiedJedis.srem("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players", uuid.toString()); + } + + private void destroyProxyMembers() { + unifiedJedis.del("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players"); + } + + private Set getProxyMembers(String proxyId) { + Set uuidsStrings = unifiedJedis.smembers("redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players"); + HashSet uuids = new HashSet<>(); + for (String proxyMember : uuidsStrings) { + uuids.add(UUID.fromString(proxyMember)); + } + return uuids; + } + + private StreamEntryID lastStreamEntryID; + + // polling from stream + @Override + public void run() { + while (!isClosed()) { + try { + List>> data = unifiedJedis.xread(XReadParams.xReadParams().block(0), Collections.singletonMap(STREAM_ID, lastStreamEntryID != null ? lastStreamEntryID : StreamEntryID.LAST_ENTRY)); + for (Map.Entry> datum : data) { + for (StreamEntry streamEntry : datum.getValue()) { + this.lastStreamEntryID = streamEntry.getID(); + String payloadData = streamEntry.getFields().get("payload"); + String clazz = streamEntry.getFields().get("class"); + UUID payloadDataManagerUUID = UUID.fromString(streamEntry.getFields().get("data-manager-uuid")); + + AbstractPayload unknownPayload = (AbstractPayload) gson.fromJson(payloadData, Class.forName(clazz)); + + if (unknownPayload.senderProxy().equals(this.proxyId)) { + if (!payloadDataManagerUUID.equals(this.dataManagerUUID)) { + plugin.logWarn("detected other proxy is using same ID! {} this can cause issues, please shutdown this proxy and change the id!", this.proxyId); + } + continue; + } + if (unknownPayload instanceof HeartbeatPayload payload) { + handleHeartBeat(payload); + } else if (unknownPayload instanceof DeathPayload payload) { + handleProxyDeath(payload); + } else if (unknownPayload instanceof RunCommandPayload payload) { + handleCommand(payload); + } else if (unknownPayload instanceof PubSubPayload payload) { + handleChannelMessage(payload); + } else { + plugin.logWarn("got unknown data manager payload: {}", unknownPayload.getClassName()); + } + } + } + } catch (Exception e) { + this.plugin.logFatal("an error has occurred in the stream", e); + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { + } + } + } + } + + public void close() { + closed.set(true); + this.publishDeath(); + this.heartbeats.clear(); + this.destroyProxyMembers(); + } + + public boolean isClosed() { + return closed.get(); + } + + public String proxyId() { + return proxyId; + } + + public UnifiedJedis unifiedJedis() { + return unifiedJedis; + } + + public String networkId() { + return networkId; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java deleted file mode 100644 index cd19d71..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public class PubSubListener implements Runnable { - private JedisPubSubHandler jpsh; - private final Set addedChannels = new HashSet(); - - private final RedisBungeePlugin plugin; - - public PubSubListener(RedisBungeePlugin plugin) { - this.plugin = plugin; - } - - @Override - public void run() { - RedisTask subTask = new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - jpsh = new JedisPubSubHandler(plugin); - addedChannels.add("redisbungee-" + plugin.getConfiguration().getProxyId()); - addedChannels.add("redisbungee-allservers"); - addedChannels.add("redisbungee-data"); - unifiedJedis.subscribe(jpsh, addedChannels.toArray(new String[0])); - return null; - } - }; - - try { - subTask.execute(); - } catch (Exception e) { - plugin.logWarn("PubSub error, attempting to recover in 5 secs."); - plugin.executeAsyncAfter(this, TimeUnit.SECONDS, 5); - } - } - - public void addChannel(String... channel) { - addedChannels.addAll(Arrays.asList(channel)); - jpsh.subscribe(channel); - } - - public void removeChannel(String... channel) { - Arrays.asList(channel).forEach(addedChannels::remove); - jpsh.unsubscribe(channel); - } - - public void poison() { - addedChannels.clear(); - jpsh.unsubscribe(); - } -} - diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java index a0e2471..8dc6887 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java @@ -10,28 +10,18 @@ package com.imaginarycode.minecraft.redisbungee.api; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; -import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; -import redis.clients.jedis.Protocol; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; +import net.kyori.adventure.text.Component; import java.net.InetAddress; -import java.util.*; +import java.util.UUID; import java.util.concurrent.TimeUnit; -import static com.google.common.base.Preconditions.checkArgument; - /** * This Class has all internal methods needed by every redis bungee plugin, and it can be used to implement another platforms than bungeecord or another forks of RedisBungee @@ -51,225 +41,56 @@ public interface RedisBungeePlugin

extends EventsPlatform { } - Summoner getSummoner(); - - RedisBungeeConfiguration getConfiguration(); - - int getCount(); - - default int getCurrentCount() { - return new RedisTask(this) { - @Override - public Long unifiedJedisTask(UnifiedJedis unifiedJedis) { - long total = 0; - long redisTime = getRedisTime(unifiedJedis); - Map heartBeats = unifiedJedis.hgetAll("heartbeats"); - for (Map.Entry stringStringEntry : heartBeats.entrySet()) { - String k = stringStringEntry.getKey(); - String v = stringStringEntry.getValue(); - - long heartbeatTime = Long.parseLong(v); - if (heartbeatTime + RedisUtil.PROXY_TIMEOUT >= redisTime) { - total = total + unifiedJedis.scard("proxy:" + k + ":usersOnline"); - } - } - return total; - } - }.execute().intValue(); - } - - Set getLocalPlayersAsUuidStrings(); - - AbstractDataManager getDataManager(); - - default Set getPlayers() { - return new RedisTask>(this) { - @Override - public Set unifiedJedisTask(UnifiedJedis unifiedJedis) { - ImmutableSet.Builder setBuilder = ImmutableSet.builder(); - try { - List keys = new ArrayList<>(); - for (String i : getProxiesIds()) { - keys.add("proxy:" + i + ":usersOnline"); - } - if (!keys.isEmpty()) { - Set users = unifiedJedis.sunion(keys.toArray(new String[0])); - if (users != null && !users.isEmpty()) { - for (String user : users) { - try { - setBuilder = setBuilder.add(UUID.fromString(user)); - } catch (IllegalArgumentException ignored) { - } - } - } - } - } catch (JedisConnectionException e) { - // Redis server has disappeared! - logFatal("Unable to get connection from pool - did your Redis server go away?"); - throw new RuntimeException("Unable to get all players online", e); - } - return setBuilder.build(); - } - }.execute(); - } - - AbstractRedisBungeeAPI getAbstractRedisBungeeApi(); - - UUIDTranslator getUuidTranslator(); - - Multimap serverToPlayersCache(); - - default Multimap serversToPlayers() { - return new RedisTask>(this) { - @Override - public Multimap unifiedJedisTask(UnifiedJedis unifiedJedis) { - ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); - for (String serverId : getProxiesIds()) { - Set players = unifiedJedis.smembers("proxy:" + serverId + ":usersOnline"); - for (String player : players) { - String playerServer = unifiedJedis.hget("player:" + player, "server"); - if (playerServer == null) { - continue; - } - builder.put(playerServer, UUID.fromString(player)); - } - } - return builder.build(); - } - }.execute(); - } - - default Set getPlayersOnProxy(String proxyId) { - checkArgument(getProxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID"); - return new RedisTask>(this) { - @Override - public Set unifiedJedisTask(UnifiedJedis unifiedJedis) { - Set users = unifiedJedis.smembers("proxy:" + proxyId + ":usersOnline"); - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (String user : users) { - builder.add(UUID.fromString(user)); - } - return builder.build(); - } - }.execute(); - } - - default void sendProxyCommand(String proxyId, String command) { - checkArgument(getProxiesIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid"); - sendChannelMessage("redisbungee-" + proxyId, command); - } - - List getProxiesIds(); - - default List getCurrentProxiesIds(boolean lagged) { - return new RedisTask>(this) { - @Override - public List unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - long time = getRedisTime(unifiedJedis); - ImmutableList.Builder servers = ImmutableList.builder(); - Map heartbeats = unifiedJedis.hgetAll("heartbeats"); - for (Map.Entry entry : heartbeats.entrySet()) { - try { - long stamp = Long.parseLong(entry.getValue()); - if (lagged ? time >= stamp + RedisUtil.PROXY_TIMEOUT : time <= stamp + RedisUtil.PROXY_TIMEOUT) { - servers.add(entry.getKey()); - } else if (time > stamp + RedisUtil.PROXY_TIMEOUT) { - logWarn(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat."); - unifiedJedis.hdel("heartbeats", entry.getKey()); - } - } catch (NumberFormatException ignored) { - } - } - return servers.build(); - } catch (JedisConnectionException e) { - logFatal("Unable to fetch server IDs"); - e.printStackTrace(); - return Collections.singletonList(getConfiguration().getProxyId()); - } - } - }.execute(); - } - - PubSubListener getPubSubListener(); - - default void sendChannelMessage(String channel, String message) { - new RedisTask(this) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - unifiedJedis.publish(channel, message); - } catch (JedisConnectionException e) { - // Redis server has disappeared! - logFatal("Unable to get connection from pool - did your Redis server go away?"); - throw new RuntimeException("Unable to publish channel message", e); - } - return null; - } - }.execute(); - } - - void executeAsync(Runnable runnable); - - void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time); - - boolean isOnlineMode(); - void logInfo(String msg); + void logInfo(String format, Object... object); + void logWarn(String msg); + void logWarn(String format, Object... object); + void logFatal(String msg); + void logFatal(String format, Throwable throwable); + + RedisBungeeConfiguration configuration(); + + LangConfiguration langConfiguration(); + + Summoner getSummoner(); + + RedisBungeeMode getRedisBungeeMode(); + + AbstractRedisBungeeAPI getAbstractRedisBungeeApi(); + + ProxyDataManager proxyDataManager(); + + PlayerDataManager playerDataManager(); + + UUIDTranslator getUuidTranslator(); + + boolean isOnlineMode(); + P getPlayer(UUID uuid); P getPlayer(String name); UUID getPlayerUUID(String player); + String getPlayerName(UUID player); + boolean handlePlatformKick(UUID uuid, Component message); + String getPlayerServerName(P player); boolean isPlayerOnAServer(P player); InetAddress getPlayerIp(P player); - default void sendProxyCommand(String cmd) { - sendProxyCommand(getConfiguration().getProxyId(), cmd); - } + void executeAsync(Runnable runnable); - default Long getRedisTime(UnifiedJedis unifiedJedis) { - List data = (List) unifiedJedis.sendCommand(Protocol.Command.TIME); - List times = new ArrayList<>(); - data.forEach((o) -> times.add(new String((byte[])o))); - return getRedisTime(times); - } - default long getRedisTime(List timeRes) { - return Long.parseLong(timeRes.get(0)); - } + void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time); - default void kickPlayer(UUID playerUniqueId, String message) { - // first handle on origin proxy if player not found publish the payload - if (!getDataManager().handleKick(playerUniqueId, message)) { - new RedisTask(this) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PayloadUtils.kickPlayerPayload(playerUniqueId, message, unifiedJedis); - return null; - } - }.execute(); - } - } - - default void kickPlayer(String playerName, String message) { - // fetch the uuid from name - UUID playerUUID = getUuidTranslator().getTranslatedUuid(playerName, true); - kickPlayer(playerUUID, message); - } - - RedisBungeeMode getRedisBungeeMode(); - - void updateProxiesIds(); } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java new file mode 100644 index 0000000..87aaa11 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.config; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * This language support implementation is temporarily + * until I come up with better system but for now we will use Maps instead :/ + * Todo: possible usage of adventure api + */ +public class LangConfiguration { + + private interface RegistrableMessages { + + void register(String id, Locale locale, String miniMessage); + + void test(Locale locale); + + default void throwError(Locale locale, String where) { + throw new IllegalStateException("Language system in `" + where + "` found missing entries for " + locale.toString()); + } + + } + + public static class Messages implements RegistrableMessages{ + + private final Map LOGGED_IN_FROM_OTHER_LOCATION; + private final Map ALREADY_LOGGED_IN; + private final Map SERVER_CONNECTING; + private final Map SERVER_NOT_FOUND; + + private final Locale defaultLocale; + + public Messages(Locale defaultLocale) { + LOGGED_IN_FROM_OTHER_LOCATION = new HashMap<>(); + ALREADY_LOGGED_IN = new HashMap<>(); + SERVER_CONNECTING = new HashMap<>(); + SERVER_NOT_FOUND = new HashMap<>(); + this.defaultLocale = defaultLocale; + } + + public void register(String id, Locale locale, String miniMessage) { + switch (id) { + case "server-not-found" -> SERVER_NOT_FOUND.put(locale, miniMessage); + case "server-connecting" -> SERVER_CONNECTING.put(locale, miniMessage); + case "logged-in-other-location" -> LOGGED_IN_FROM_OTHER_LOCATION.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); + case "already-logged-in" -> ALREADY_LOGGED_IN.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); + } + } + + public Component alreadyLoggedIn(Locale locale) { + if (ALREADY_LOGGED_IN.containsKey(locale)) return ALREADY_LOGGED_IN.get(locale); + return ALREADY_LOGGED_IN.get(defaultLocale); + } + + // there is no way to know whats client locale during login so just default to use default locale MESSAGES. + public Component alreadyLoggedIn() { + return this.alreadyLoggedIn(this.defaultLocale); + } + + public Component loggedInFromOtherLocation(Locale locale) { + if (LOGGED_IN_FROM_OTHER_LOCATION.containsKey(locale)) return LOGGED_IN_FROM_OTHER_LOCATION.get(locale); + return LOGGED_IN_FROM_OTHER_LOCATION.get(defaultLocale); + } + + // there is no way to know what's client locale during login so just default to use default locale MESSAGES. + public Component loggedInFromOtherLocation() { + return this.loggedInFromOtherLocation(this.defaultLocale); + } + + public Component serverConnecting(Locale locale, String server) { + String miniMessage; + if (SERVER_CONNECTING.containsKey(locale)) { + miniMessage = SERVER_CONNECTING.get(locale); + } else { + miniMessage = SERVER_CONNECTING.get(defaultLocale); + } + return MiniMessage.miniMessage().deserialize(miniMessage, Placeholder.parsed("server", server)); + } + + public Component serverConnecting(String server) { + return this.serverConnecting(this.defaultLocale, server); + } + + public Component serverNotFound(Locale locale, String server) { + String miniMessage; + if (SERVER_NOT_FOUND.containsKey(locale)) { + miniMessage = SERVER_NOT_FOUND.get(locale); + } else { + miniMessage = SERVER_NOT_FOUND.get(defaultLocale); + } + return MiniMessage.miniMessage().deserialize(miniMessage, Placeholder.parsed("server", server)); + } + + public Component serverNotFound(String server) { + return this.serverNotFound(this.defaultLocale, server); + } + + + // tests locale if set CORRECTLY or just throw if not + public void test(Locale locale) { + if (!(LOGGED_IN_FROM_OTHER_LOCATION.containsKey(locale) && ALREADY_LOGGED_IN.containsKey(locale) && SERVER_CONNECTING.containsKey(locale) && SERVER_NOT_FOUND.containsKey(locale))) { + throwError(locale, "messages"); + } + } + + } + + private final Component redisBungeePrefix; + + private final Locale defaultLanguage; + + private final boolean useClientLanguage; + + private final Messages messages; + + public LangConfiguration(Component redisBungeePrefix, Locale defaultLanguage, boolean useClientLanguage, Messages messages) { + this.redisBungeePrefix = redisBungeePrefix; + this.defaultLanguage = defaultLanguage; + this.useClientLanguage = useClientLanguage; + this.messages = messages; + } + + public Component redisBungeePrefix() { + return redisBungeePrefix; + } + + public Locale defaultLanguage() { + return defaultLanguage; + } + + public boolean useClientLanguage() { + return useClientLanguage; + } + + public Messages messages() { + return messages; + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index 2e595f4..f76fb39 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -11,42 +11,39 @@ package com.imaginarycode.minecraft.redisbungee.api.config; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; import com.google.common.net.InetAddresses; +import javax.annotation.Nullable; import java.net.InetAddress; -import java.util.HashMap; import java.util.List; public class RedisBungeeConfiguration { - public enum MessageType { - LOGGED_IN_OTHER_LOCATION, - ALREADY_LOGGED_IN - } - - private final ImmutableMap messages; - public static final int CONFIG_VERSION = 1; private final String proxyId; private final List exemptAddresses; - private final boolean registerLegacyCommands; - private final boolean overrideBungeeCommands; + private final boolean kickWhenOnline; - private final boolean restoreOldKickBehavior; + private final boolean handleReconnectToLastServer; + private final boolean handleMotd; - public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, ImmutableMap messages, boolean restoreOldKickBehavior) { + private final CommandsConfiguration commandsConfiguration; + private final String networkId; + + + public RedisBungeeConfiguration(String networkId, String proxyId, List exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, CommandsConfiguration commandsConfiguration) { this.proxyId = proxyId; - this.messages = messages; ImmutableList.Builder addressBuilder = ImmutableList.builder(); for (String s : exemptAddresses) { addressBuilder.add(InetAddresses.forString(s)); } this.exemptAddresses = addressBuilder.build(); - this.registerLegacyCommands = registerLegacyCommands; - this.overrideBungeeCommands = overrideBungeeCommands; - this.restoreOldKickBehavior = restoreOldKickBehavior; + this.kickWhenOnline = kickWhenOnline; + this.handleReconnectToLastServer = handleReconnectToLastServer; + this.handleMotd = handleMotd; + this.commandsConfiguration = commandsConfiguration; + this.networkId = networkId; } + public String getProxyId() { return proxyId; } @@ -55,19 +52,37 @@ public class RedisBungeeConfiguration { return exemptAddresses; } - public boolean doRegisterLegacyCommands() { - return registerLegacyCommands; + public boolean kickWhenOnline() { + return kickWhenOnline; } - public boolean doOverrideBungeeCommands() { - return overrideBungeeCommands; + public boolean handleMotd() { + return this.handleMotd; } - public ImmutableMap getMessages() { - return messages; + public boolean handleReconnectToLastServer() { + return this.handleReconnectToLastServer; } - public boolean restoreOldKickBehavior() { - return restoreOldKickBehavior; + public record CommandsConfiguration(boolean redisbungeeEnabled, boolean redisbungeeLegacyEnabled, + @Nullable LegacySubCommandsConfiguration legacySubCommandsConfiguration) { + + } + + public record LegacySubCommandsConfiguration(boolean findEnabled, boolean glistEnabled, boolean ipEnabled, + boolean lastseenEnabled, boolean plistEnabled, boolean pproxyEnabled, + boolean sendtoallEnabled, boolean serveridEnabled, + boolean serveridsEnabled, boolean installFind, boolean installGlist, boolean installIp, + boolean installLastseen, boolean installPlist, boolean installPproxy, + boolean installSendtoall, boolean installServerid, + boolean installServerids) { + } + + public CommandsConfiguration commandsConfiguration() { + return commandsConfiguration; + } + + public String networkId() { + return networkId; } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java similarity index 54% rename from RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java rename to RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java index b92365e..a73b6ef 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java @@ -8,13 +8,13 @@ * http://www.eclipse.org/legal/epl-v10.html */ -package com.imaginarycode.minecraft.redisbungee.api.config; +package com.imaginarycode.minecraft.redisbungee.api.config.loaders; -import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; @@ -26,35 +26,29 @@ import redis.clients.jedis.*; import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.providers.PooledConnectionProvider; -import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.*; -public interface ConfigLoader { +public interface ConfigLoader extends GenericConfigLoader { - default void loadConfig(RedisBungeePlugin plugin, File dataFolder) throws IOException { - loadConfig(plugin, dataFolder.toPath()); - } + int CONFIG_VERSION = 2; default void loadConfig(RedisBungeePlugin plugin, Path dataFolder) throws IOException { - Path configFile = createConfigFile(dataFolder); + Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml"); final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build(); ConfigurationNode node = yamlConfigurationFileLoader.load(); - if (node.getNode("config-version").getInt(0) != RedisBungeeConfiguration.CONFIG_VERSION) { - handleOldConfig(dataFolder); + if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) { + handleOldConfig(dataFolder, "config.yml", "config.yml"); node = yamlConfigurationFileLoader.load(); } final boolean useSSL = node.getNode("useSSL").getBoolean(false); - final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false); - final boolean registerLegacyCommands = node.getNode("register-legacy-commands").getBoolean(false); - final boolean restoreOldKickBehavior = node.getNode("disable-kick-when-online").getBoolean(false); + final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true); String redisPassword = node.getNode("redis-password").getString(""); String redisUsername = node.getNode("redis-username").getString(""); - String proxyId = node.getNode("proxy-id").getString("test-1"); + String networkId = node.getNode("network-id").getString("main"); + String proxyId = node.getNode("proxy-id").getString("proxy-1"); + final int maxConnections = node.getNode("max-redis-connections").getInt(10); List exemptAddresses; try { @@ -71,10 +65,19 @@ public interface ConfigLoader { if ((redisUsername.isEmpty() || redisUsername.equals("none"))) { redisUsername = null; } - - if (useSSL) { - plugin.logInfo("Using ssl"); + // env var + String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID"); + if (proxyIdFromEnv != null) { + plugin.logInfo("Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID", proxyId, proxyIdFromEnv); + proxyId = proxyIdFromEnv; } + + String networkIdFromEnv = System.getenv("REDISBUNGEE_NETWORK_ID"); + if (networkIdFromEnv != null) { + plugin.logInfo("Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID", networkId, networkIdFromEnv); + networkId = networkIdFromEnv; + } + // Configuration sanity checks. if (proxyId == null || proxyId.isEmpty()) { String genId = UUID.randomUUID().toString(); @@ -86,9 +89,62 @@ public interface ConfigLoader { } else { plugin.logInfo("Loaded proxy id " + proxyId); } - RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, getMessagesFromPath(createMessagesFile(dataFolder)), restoreOldKickBehavior); + + if (networkId.isEmpty()) { + networkId = "main"; + plugin.logWarn("network id was empty and replaced with 'main'"); + } + + plugin.logInfo("Loaded network id " + networkId); + + + + boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean(); + boolean handleMotd = node.getNode("handle-motd").getBoolean(true); + plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer); + plugin.logInfo("handle motd: {}", handleMotd); + + + // commands + boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(true); + boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(false); + + boolean glistEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled").getBoolean(false); + boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(false); + boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(false); + boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(false); + boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(false); + boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(false); + boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(false); + boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(false); + boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(false); + + boolean installGlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install").getBoolean(false); + boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(false); + boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(false); + boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(false); + boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(false); + boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(false); + boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(false); + boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(false); + boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(false); + + + RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(networkId, proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, new RedisBungeeConfiguration.CommandsConfiguration( + redisBungeeEnabled, redisBungeeLegacyEnabled, + new RedisBungeeConfiguration.LegacySubCommandsConfiguration( + findEnabled, glistEnabled, ipEnabled, + lastseenEnabled, pListEnabled, pproxyEnabled, + sendToAllEnabled, serverIdEnabled, serverIdsEnabled, + installFind, installGlist, installIp, + installLastseen, installPlist, installPproxy, + installSendToAll, installServerid, installServerIds) + )); Summoner summoner; RedisBungeeMode redisBungeeMode; + if (useSSL) { + plugin.logInfo("Using ssl"); + } if (node.getNode("cluster-mode-enabled").getBoolean(false)) { plugin.logInfo("RedisBungee MODE: CLUSTER"); Set hostAndPortSet = new HashSet<>(); @@ -115,7 +171,7 @@ public interface ConfigLoader { throw new RuntimeException("No redis server specified"); } JedisPool jedisPool = null; - if (node.getNode("enable-jedis-pool-compatibility").getBoolean(true)) { + if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3)); config.setBlockWhenExhausted(true); @@ -134,53 +190,5 @@ public interface ConfigLoader { void onConfigLoad(RedisBungeeConfiguration configuration, Summoner summoner, RedisBungeeMode mode); - default ImmutableMap getMessagesFromPath(Path path) throws IOException { - final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(path).build(); - ConfigurationNode node = yamlConfigurationFileLoader.load(); - HashMap messages = new HashMap<>(); - messages.put(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION, node.getNode("logged-in-other-location").getString("§cLogged in from another location.")); - messages.put(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN, node.getNode("already-logged-in").getString("§cYou are already logged in!")); - return ImmutableMap.copyOf(messages); - } - - default Path createMessagesFile(Path dataFolder) throws IOException { - if (Files.notExists(dataFolder)) { - Files.createDirectory(dataFolder); - } - Path file = dataFolder.resolve("messages.yml"); - if (Files.notExists(file)) { - try (InputStream in = getClass().getClassLoader().getResourceAsStream("messages.yml")) { - Files.createFile(file); - assert in != null; - Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING); - } - } - return file; - } - - default Path createConfigFile(Path dataFolder) throws IOException { - if (Files.notExists(dataFolder)) { - Files.createDirectory(dataFolder); - } - Path file = dataFolder.resolve("config.yml"); - if (Files.notExists(file)) { - try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) { - Files.createFile(file); - assert in != null; - Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING); - } - } - return file; - } - - default void handleOldConfig(Path dataFolder) throws IOException { - Path oldConfigFolder = dataFolder.resolve("old_config"); - if (Files.notExists(oldConfigFolder)) { - Files.createDirectory(oldConfigFolder); - } - Path oldConfigPath = dataFolder.resolve("config.yml"); - Files.move(oldConfigPath, oldConfigFolder.resolve(UUID.randomUUID() + "_config.yml")); - createConfigFile(dataFolder); - } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java new file mode 100644 index 0000000..78a5d29 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.config.loaders; + +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.time.Instant; + + +public interface GenericConfigLoader { + + // CHANGES on every reboot + String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond(); + + default Path createConfigFile(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException { + if (Files.notExists(dataFolder)) { + Files.createDirectory(dataFolder); + } + Path file = dataFolder.resolve(configFile); + if (Files.notExists(file) && defaultResourceID != null) { + try (InputStream in = getClass().getClassLoader().getResourceAsStream(defaultResourceID)) { + Files.createFile(file); + assert in != null; + Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING); + } + } + return file; + } + + default void handleOldConfig(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException { + Path oldConfigFolder = dataFolder.resolve("old_config"); + if (Files.notExists(oldConfigFolder)) { + Files.createDirectory(oldConfigFolder); + } + Path randomStoreConfigDirectory = oldConfigFolder.resolve(RANDOM_OLD); + if (Files.notExists(randomStoreConfigDirectory)) { + Files.createDirectory(randomStoreConfigDirectory); + } + Path oldConfigPath = dataFolder.resolve(configFile); + + Files.move(oldConfigPath, randomStoreConfigDirectory.resolve(configFile)); + createConfigFile(dataFolder, configFile, defaultResourceID); + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java new file mode 100644 index 0000000..ad30c42 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.config.loaders; + +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Locale; + +public interface LangConfigLoader extends GenericConfigLoader { + + int CONFIG_VERSION = 1; + + default void loadLangConfig(RedisBungeePlugin plugin, Path dataFolder) throws IOException { + Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml"); + final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build(); + ConfigurationNode node = yamlConfigurationFileLoader.load(); + if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) { + handleOldConfig(dataFolder, "lang.yml", "lang.yml"); + node = yamlConfigurationFileLoader.load(); + } + // MINI message serializer + MiniMessage miniMessage = MiniMessage.miniMessage(); + + Component prefix = miniMessage.deserialize(node.getNode("prefix").getString("[RedisBungee]")); + Locale defaultLocale = Locale.forLanguageTag(node.getNode("default-locale").getString("en-us")); + boolean useClientLocale = node.getNode("use-client-locale").getBoolean(true); + LangConfiguration.Messages messages = new LangConfiguration.Messages(defaultLocale); + node.getNode("messages").getChildrenMap().forEach((key, childNode) -> childNode.getChildrenMap().forEach((childKey, childChildNode) -> { + messages.register(key.toString(), Locale.forLanguageTag(childKey.toString()), childChildNode.getString()); + })); + messages.test(defaultLocale); + + onLangConfigLoad(new LangConfiguration(prefix, defaultLocale, useClientLocale, messages)); + } + + + void onLangConfigLoad(LangConfiguration langConfiguration); + + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java index 099c075..79dabfa 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java @@ -17,7 +17,6 @@ import java.util.UUID; * * @author Ham1255 * @since 0.7.0 - * */ public interface EventsPlatform { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java new file mode 100644 index 0000000..e41ee5f --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java @@ -0,0 +1,24 @@ + +package com.imaginarycode.minecraft.redisbungee.api.payloads; + +public abstract class AbstractPayload { + + private final String senderProxy; + + public AbstractPayload(String proxyId) { + this.senderProxy = proxyId; + } + + public AbstractPayload(String senderProxy, String className) { + this.senderProxy = senderProxy; + } + + public String senderProxy() { + return senderProxy; + } + + public String getClassName() { + return getClass().getName(); + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java new file mode 100644 index 0000000..6769ff2 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +import java.lang.reflect.Type; + +public class AbstractPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public AbstractPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + return new AbstractPayload(jsonObject.get("proxy").getAsString(), jsonObject.get("class").getAsString()) { + }; + } + + @Override + public JsonElement serialize(AbstractPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java new file mode 100644 index 0000000..399071a --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class DeathPayload extends AbstractPayload { + public DeathPayload(String proxyId) { + super(proxyId); + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java new file mode 100644 index 0000000..02268fd --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class HeartbeatPayload extends AbstractPayload { + + public record HeartbeatData(long heartbeat, int players) { + + } + + private final HeartbeatData data; + + public HeartbeatPayload(String proxyId, HeartbeatData data) { + super(proxyId); + this.data = data; + } + + public HeartbeatData data() { + return data; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java new file mode 100644 index 0000000..eaa9092 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class PubSubPayload extends AbstractPayload { + + private final String channel; + private final String message; + + + public PubSubPayload(String proxyId, String channel, String message) { + super(proxyId); + this.channel = channel; + this.message = message; + } + + public String channel() { + return channel; + } + + public String message() { + return message; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java new file mode 100644 index 0000000..6374e5c --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class RunCommandPayload extends AbstractPayload { + + + private final String proxyToRun; + + private final String command; + + + public RunCommandPayload(String proxyId, String proxyToRun, String command) { + super(proxyId); + this.proxyToRun = proxyToRun; + this.command = command; + } + + public String proxyToRun() { + return proxyToRun; + } + + public String command() { + return command; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java new file mode 100644 index 0000000..d77dd51 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload; + +import java.lang.reflect.Type; + +public class DeathPayloadSerializer implements JsonSerializer, JsonDeserializer { + + private static final Gson gson = new Gson(); + + + @Override + public DeathPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + return new DeathPayload(senderProxy); + } + + @Override + public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java new file mode 100644 index 0000000..1f301f2 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload; + +import java.lang.reflect.Type; + +public class HeartbeatPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public HeartbeatPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + long heartbeat = jsonObject.get("heartbeat").getAsLong(); + int players = jsonObject.get("players").getAsInt(); + return new HeartbeatPayload(senderProxy, new HeartbeatPayload.HeartbeatData(heartbeat, players)); + } + + @Override + public JsonElement serialize(HeartbeatPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("heartbeat", new JsonPrimitive(src.data().heartbeat())); + jsonObject.add("players", new JsonPrimitive(src.data().players())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java new file mode 100644 index 0000000..01d66a5 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload; + +import java.lang.reflect.Type; + +public class PubSubPayloadSerializer implements JsonSerializer, JsonDeserializer { + + private static final Gson gson = new Gson(); + + + @Override + public PubSubPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + String channel = jsonObject.get("channel").getAsString(); + String message = jsonObject.get("message").getAsString(); + return new PubSubPayload(senderProxy, channel, message); + } + + @Override + public JsonElement serialize(PubSubPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("channel", new JsonPrimitive(src.channel())); + jsonObject.add("message", context.serialize(src.message())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java new file mode 100644 index 0000000..2a7de33 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload; + +import java.lang.reflect.Type; + +public class RunCommandPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public RunCommandPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + String proxyToRun = jsonObject.get("proxy-to-run").getAsString(); + String command = jsonObject.get("command").getAsString(); + return new RunCommandPayload(senderProxy, proxyToRun, command); + } + + @Override + public JsonElement serialize(RunCommandPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("proxy-to-run", new JsonPrimitive(src.proxyToRun())); + jsonObject.add("command", context.serialize(src.command())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java index 99d8e19..14c2514 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java @@ -17,7 +17,7 @@ import java.io.IOException; import java.time.Duration; public class JedisClusterSummoner implements Summoner { - public final ClusterConnectionProvider clusterConnectionProvider; + private final ClusterConnectionProvider clusterConnectionProvider; public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) { this.clusterConnectionProvider = clusterConnectionProvider; @@ -35,6 +35,8 @@ public class JedisClusterSummoner implements Summoner { @Override public JedisCluster obtainResource() { - return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(30000)); + return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(10)); } + + } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java index 84eb85a..5e09859 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java @@ -11,9 +11,7 @@ package com.imaginarycode.minecraft.redisbungee.api.summoners; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.JedisPooled; import redis.clients.jedis.providers.ClusterConnectionProvider; -import redis.clients.jedis.providers.PooledConnectionProvider; import java.time.Duration; diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java index 6b511e7..36beac5 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java @@ -10,6 +10,8 @@ package com.imaginarycode.minecraft.redisbungee.api.summoners; +import redis.clients.jedis.UnifiedJedis; + import java.io.Closeable; @@ -18,9 +20,8 @@ import java.io.Closeable; * * @author Ham1255 * @since 0.7.0 - * */ -public interface Summoner

extends Closeable { +public interface Summoner

extends Closeable { P obtainResource(); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java deleted file mode 100644 index 669ba8c..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class HeartbeatTask extends RedisTask{ - - public final static TimeUnit REPEAT_INTERVAL_TIME_UNIT = TimeUnit.SECONDS; - public final static int INTERVAL = 1; - private final AtomicInteger globalPlayerCount; - - public HeartbeatTask(RedisBungeePlugin plugin, AtomicInteger globalPlayerCount) { - super(plugin); - this.globalPlayerCount = globalPlayerCount; - } - - - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - long redisTime = plugin.getRedisTime(unifiedJedis); - unifiedJedis.hset("heartbeats", plugin.getConfiguration().getProxyId(), String.valueOf(redisTime)); - } catch (JedisConnectionException e) { - // Redis server has disappeared! - plugin.logFatal("Unable to update heartbeat - did your Redis server go away?"); - e.printStackTrace(); - return null; - } - try { - plugin.updateProxiesIds(); - globalPlayerCount.set(plugin.getCurrentCount()); - } catch (Throwable e) { - plugin.logFatal("Unable to update data - did your Redis server go away?"); - e.printStackTrace(); - } - return null; - } - - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java deleted file mode 100644 index 8a2986f..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; -import redis.clients.jedis.Protocol; -import redis.clients.jedis.UnifiedJedis; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -public class InitialUtils { - - public static void checkRedisVersion(RedisBungeePlugin plugin) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - // This is more portable than INFO

- String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO)); - for (String s : info.split("\r\n")) { - if (s.startsWith("redis_version:")) { - String version = s.split(":")[1]; - plugin.logInfo("Redis server version: " + version); - if (!RedisUtil.isRedisVersionRight(version)) { - plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis."); - throw new RuntimeException("Unsupported Redis version detected"); - } - long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); - if (uuidCacheSize > 750000) { - plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains"); - } - break; - } - } - return null; - } - }.execute(); - } - - - public static void checkIfRecovering(RedisBungeePlugin plugin, Path dataFolder) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - Path crashFile = dataFolder.resolve("restarted_from_crash.txt"); - if (Files.exists(crashFile)) { - try { - Files.delete(crashFile); - } catch (IOException e) { - throw new RuntimeException(e); - } - plugin.logInfo("crash file was deleted continuing RedisBungee startup "); - } else if (unifiedJedis.hexists("heartbeats", plugin.getConfiguration().getProxyId())) { - try { - long value = Long.parseLong(unifiedJedis.hget("heartbeats", plugin.getConfiguration().getProxyId())); - long redisTime = plugin.getRedisTime(unifiedJedis); - - if (redisTime < value + RedisUtil.PROXY_TIMEOUT) { - logImposter(plugin); - throw new RuntimeException("Possible impostor instance!"); - } - } catch (NumberFormatException ignored) { - } - } - return null; - } - }.execute(); - } - - private static void logImposter(RedisBungeePlugin plugin) { - plugin.logFatal("You have launched a possible impostor Velocity / Bungeecord instance. Another instance is already running."); - plugin.logFatal("For data consistency reasons, RedisBungee will now disable itself."); - plugin.logFatal("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check."); - } - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java deleted file mode 100644 index c13742e..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.UnifiedJedis; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public abstract class IntegrityCheckTask extends RedisTask { - - public static int INTERVAL = 30; - public static TimeUnit TIMEUNIT = TimeUnit.SECONDS; - - - public IntegrityCheckTask(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - Set players = plugin.getLocalPlayersAsUuidStrings(); - Set playersInRedis = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline"); - List lagged = plugin.getCurrentProxiesIds(true); - - // Clean up lagged players. - for (String s : lagged) { - Set laggedPlayers = unifiedJedis.smembers("proxy:" + s + ":usersOnline"); - unifiedJedis.del("proxy:" + s + ":usersOnline"); - if (!laggedPlayers.isEmpty()) { - plugin.logInfo("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)..."); - for (String laggedPlayer : laggedPlayers) { - PlayerUtils.cleanUpPlayer(laggedPlayer, unifiedJedis, true); - } - } - } - - Set absentLocally = new HashSet<>(playersInRedis); - absentLocally.removeAll(players); - Set absentInRedis = new HashSet<>(players); - absentInRedis.removeAll(playersInRedis); - - for (String member : absentLocally) { - boolean found = false; - for (String proxyId : plugin.getProxiesIds()) { - if (proxyId.equals(plugin.getConfiguration().getProxyId())) continue; - if (unifiedJedis.sismember("proxy:" + proxyId + ":usersOnline", member)) { - // Just clean up the set. - found = true; - break; - } - } - if (!found) { - PlayerUtils.cleanUpPlayer(member, unifiedJedis, false); - plugin.logWarn("Player found in set that was not found locally and globally: " + member); - } else { - unifiedJedis.srem("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline", member); - plugin.logWarn("Player found in set that was not found locally, but is on another proxy: " + member); - } - } - // due unifiedJedis does not support pipelined. - //Pipeline pipeline = jedis.pipelined(); - - for (String player : absentInRedis) { - // Player not online according to Redis but not BungeeCord. - handlePlatformPlayer(player, unifiedJedis); - } - } catch (Throwable e) { - plugin.logFatal("Unable to fix up stored player data"); - e.printStackTrace(); - } - return null; - } - - - public abstract void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis); - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java new file mode 100644 index 0000000..21a5d29 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.tasks; + +import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import redis.clients.jedis.*; + +public abstract class RedisPipelineTask extends RedisTask { + + + public RedisPipelineTask(AbstractRedisBungeeAPI api) { + super(api); + } + + public RedisPipelineTask(RedisBungeePlugin plugin) { + super(plugin); + } + + + @Override + public T unifiedJedisTask(UnifiedJedis unifiedJedis) { + if (unifiedJedis instanceof JedisPooled pooled) { + try (Pipeline pipeline = pooled.pipelined()) { + return doPooledPipeline(pipeline); + } + } else if (unifiedJedis instanceof JedisCluster jedisCluster) { + try (ClusterPipeline pipeline = jedisCluster.pipelined()) { + return clusterPipeline(pipeline); + } + } + + return null; + } + + public abstract T doPooledPipeline(Pipeline pipeline); + + public abstract T clusterPipeline(ClusterPipeline pipeline); + + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java index eb1b416..9a6da17 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java @@ -11,11 +11,11 @@ package com.imaginarycode.minecraft.redisbungee.api.tasks; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import redis.clients.jedis.UnifiedJedis; import java.util.concurrent.Callable; @@ -27,23 +27,22 @@ import java.util.concurrent.Callable; public abstract class RedisTask implements Runnable, Callable { protected final Summoner summoner; - protected final AbstractRedisBungeeAPI api; - protected RedisBungeePlugin plugin; + + protected final RedisBungeeMode mode; @Override public V call() throws Exception { - return execute(); + return this.execute(); } public RedisTask(AbstractRedisBungeeAPI api) { - this.api = api; this.summoner = api.getSummoner(); + this.mode = api.getMode(); } public RedisTask(RedisBungeePlugin plugin) { - this.plugin = plugin; - this.api = plugin.getAbstractRedisBungeeApi(); - this.summoner = api.getSummoner(); + this.summoner = plugin.getSummoner(); + this.mode = plugin.getRedisBungeeMode(); } public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis); @@ -53,22 +52,16 @@ public abstract class RedisTask implements Runnable, Callable { this.execute(); } - public V execute(){ + public V execute() { // JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its single instance anyway. - if (api.getMode() == RedisBungeeMode.SINGLE) { + if (mode == RedisBungeeMode.SINGLE) { JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner; return this.unifiedJedisTask(jedisSummoner.obtainResource()); - } else if (api.getMode() == RedisBungeeMode.CLUSTER) { + } else if (mode == RedisBungeeMode.CLUSTER) { JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner; return this.unifiedJedisTask(jedisClusterSummoner.obtainResource()); } return null; } - public RedisBungeePlugin getPlugin() { - if (plugin == null) { - throw new NullPointerException("Plugin is null in the task"); - } - return plugin; - } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java deleted file mode 100644 index a3fdbcc..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; - -import java.util.Set; - -public class ShutdownUtils { - - public static void shutdownCleanup(RedisBungeePlugin plugin) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hdel("heartbeats", plugin.getConfiguration().getProxyId()); - if (unifiedJedis.scard("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline") > 0) { - Set players = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline"); - for (String member : players) - PlayerUtils.cleanUpPlayer(member, unifiedJedis, true); - } - return null; - } - }.execute(); - } - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/UUIDCleanupTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/UUIDCleanupTask.java new file mode 100644 index 0000000..6e080c4 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/UUIDCleanupTask.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.tasks; + +import com.google.gson.Gson; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.CachedUUIDEntry; +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.exceptions.JedisException; + +import java.util.ArrayList; + + +public class UUIDCleanupTask extends RedisTask{ + + private final Gson gson = new Gson(); + private final RedisBungeePlugin plugin; + + public UUIDCleanupTask(RedisBungeePlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + // this code is inspired from https://github.com/minecrafter/redisbungeeclean + @Override + public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { + try { + final long number = unifiedJedis.hlen("uuid-cache"); + plugin.logInfo("Found {} entries", number); + ArrayList fieldsToRemove = new ArrayList<>(); + unifiedJedis.hgetAll("uuid-cache").forEach((field, data) -> { + CachedUUIDEntry cachedUUIDEntry = gson.fromJson(data, CachedUUIDEntry.class); + if (cachedUUIDEntry.expired()) { + fieldsToRemove.add(field); + } + }); + if (!fieldsToRemove.isEmpty()) { + unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0])); + } + plugin.logInfo("deleted {} entries", fieldsToRemove.size()); + } catch (JedisException e) { + plugin.logFatal("There was an error fetching information", e); + } + return null; + } + + +} \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java new file mode 100644 index 0000000..8ebf8d0 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.util; + +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; +import redis.clients.jedis.Protocol; +import redis.clients.jedis.UnifiedJedis; + + +public class InitialUtils { + + public static void checkRedisVersion(RedisBungeePlugin plugin) { + new RedisTask(plugin) { + @Override + public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { + // This is more portable than INFO
+ String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO)); + for (String s : info.split("\r\n")) { + if (s.startsWith("redis_version:")) { + String version = s.split(":")[1]; + plugin.logInfo("Redis server version: " + version); + if (!RedisUtil.isRedisVersionRight(version)) { + plugin.logFatal("Your version of Redis (" + version + ") is not at least version " + RedisUtil.MAJOR_VERSION + "." + RedisUtil.MINOR_VERSION + " RedisBungee requires a newer version of Redis."); + throw new RuntimeException("Unsupported Redis version detected"); + } + long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); + if (uuidCacheSize > 750000) { + plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains"); + } + break; + } + } + return null; + } + }.execute(); + } + + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java index 9e4bd92..7e337db 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java @@ -5,6 +5,10 @@ import com.google.common.annotations.VisibleForTesting; @VisibleForTesting public class RedisUtil { public final static int PROXY_TIMEOUT = 30; + + public static final int MAJOR_VERSION = 6; + public static final int MINOR_VERSION = 2; + public static boolean isRedisVersionRight(String redisVersion) { String[] args = redisVersion.split("\\."); if (args.length < 2) { @@ -12,7 +16,10 @@ public class RedisUtil { } int major = Integer.parseInt(args[0]); int minor = Integer.parseInt(args[1]); - return major >= 3 && minor >= 0; + + if (major > MAJOR_VERSION) return true; + return major == MAJOR_VERSION && minor >= MINOR_VERSION; + } // Ham1255: i am keeping this if some plugin uses this *IF* diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java deleted file mode 100644 index fa4290e..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.io; - -import com.google.common.io.ByteStreams; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - - -public class IOUtil { - public static String readInputStreamAsString(InputStream is) { - String string; - try { - string = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new AssertionError(e); - } - return string; - } -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java deleted file mode 100644 index 36e9b78..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.payload; - -import com.google.gson.Gson; -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.UUID; - -public class PayloadUtils { - private static final Gson gson = new Gson(); - - public static void playerJoinPayload(UUID uuid, UnifiedJedis unifiedJedis, InetAddress inetAddress) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.JOIN, - new AbstractDataManager.LoginPayload(inetAddress)))); - } - - - public static void playerQuitPayload(String uuid, UnifiedJedis unifiedJedis, long timestamp) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - UUID.fromString(uuid), AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.LEAVE, - new AbstractDataManager.LogoutPayload(timestamp)))); - } - - - - public static void playerServerChangePayload(UUID uuid, UnifiedJedis unifiedJedis, String newServer, String oldServer) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.SERVER_CHANGE, - new AbstractDataManager.ServerChangePayload(newServer, oldServer)))); - } - - - public static void kickPlayerPayload(UUID uuid, String message, UnifiedJedis unifiedJedis) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.KICK, - new AbstractDataManager.KickPayload(message)))); - } -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java deleted file mode 100644 index 820ad34..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.player; - -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerJoinPayload; -import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerQuitPayload; - -public class PlayerUtils { - - public static void cleanUpPlayer(String uuid, UnifiedJedis rsc, boolean firePayload) { - final long timestamp = System.currentTimeMillis(); - final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid, rsc); - rsc.srem("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid); - if (!isKickedFromOtherLocation) { - rsc.hdel("player:" + uuid, "server", "ip", "proxy"); - rsc.hset("player:" + uuid, "online", String.valueOf(timestamp)); - } - if (firePayload && !isKickedFromOtherLocation) { - playerQuitPayload(uuid, rsc, timestamp); - } - } - - public static void setKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) { - // set anything for sake of exists check. then expire it after 2 seconds. should be great? - unifiedJedis.set("kicked-other-location::" + uuid, "0"); - unifiedJedis.expire("kicked-other-location::" + uuid, 2); - } - - public static boolean isKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) { - return unifiedJedis.exists("kicked-other-location::" + uuid); - } - - - public static void createPlayer(UUID uuid, UnifiedJedis unifiedJedis, String currentServer, InetAddress hostname, boolean fireEvent) { - final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid.toString(), unifiedJedis); - Map playerData = new HashMap<>(4); - playerData.put("online", "0"); - playerData.put("ip", hostname.getHostName()); - playerData.put("proxy", AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId()); - if (currentServer != null) { - playerData.put("server", currentServer); - } - unifiedJedis.sadd("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid.toString()); - unifiedJedis.hset("player:" + uuid, playerData); - if (fireEvent && !isKickedFromOtherLocation) { - playerJoinPayload(uuid, unifiedJedis, hostname); - } - } - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java similarity index 97% rename from RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java rename to RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java index 7ee9cc5..71df20b 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java @@ -7,7 +7,7 @@ import com.google.common.io.ByteArrayDataOutput; import java.util.Collection; import java.util.Map; -public class Serializations { +public class MultiMapSerialization { public static void serializeMultiset(Multiset collection, ByteArrayDataOutput output) { output.writeInt(collection.elementSet().size()); @@ -36,4 +36,5 @@ public class Serializations { output.writeUTF(o.toString()); } } + } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java index 69eb689..3bcd38c 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java @@ -22,38 +22,38 @@ import java.util.List; import java.util.UUID; public class NameFetcher { - private static OkHttpClient httpClient; - private static final Gson gson = new Gson(); + private static OkHttpClient httpClient; + private static final Gson gson = new Gson(); - public static void setHttpClient(OkHttpClient httpClient) { - NameFetcher.httpClient = httpClient; - } + public static void setHttpClient(OkHttpClient httpClient) { + NameFetcher.httpClient = httpClient; + } - public static List nameHistoryFromUuid(UUID uuid) throws IOException { - String name = getName(uuid); - if (name == null) return Collections.emptyList(); - return Collections.singletonList(name); - } + public static List nameHistoryFromUuid(UUID uuid) throws IOException { + String name = getName(uuid); + if (name == null) return Collections.emptyList(); + return Collections.singletonList(name); + } - public static String getName(UUID uuid) throws IOException { - String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString(); - Request request = new Request.Builder() - .addHeader("User-Agent", "RedisBungee-ProxioDev") - .url(url) - .get() - .build(); - ResponseBody body = httpClient.newCall(request).execute().body(); - String response = body.string(); - body.close(); + public static String getName(UUID uuid) throws IOException { + String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString(); + Request request = new Request.Builder() + .addHeader("User-Agent", "RedisBungee-ProxioDev") + .url(url) + .get() + .build(); + ResponseBody body = httpClient.newCall(request).execute().body(); + String response = body.string(); + body.close(); - JsonObject json = gson.fromJson(response, JsonObject.class); - if (!json.has("success") || !json.get("success").getAsBoolean()) return null; - if (!json.has("data")) return null; - JsonObject data = json.getAsJsonObject("data"); - if (!data.has("player")) return null; - JsonObject player = data.getAsJsonObject("player"); - if (!player.has("username")) return null; + JsonObject json = gson.fromJson(response, JsonObject.class); + if (!json.has("success") || !json.get("success").getAsBoolean()) return null; + if (!json.has("data")) return null; + JsonObject data = json.getAsJsonObject("data"); + if (!data.has("player")) return null; + JsonObject player = data.getAsJsonObject("player"); + if (!player.has("username")) return null; - return player.get("username").getAsString(); - } + return player.get("username").getAsString(); + } } \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java index 7453074..acccf40 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java @@ -14,13 +14,15 @@ import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; - import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; import org.checkerframework.checker.nullness.qual.NonNull; import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.exceptions.JedisException; -import java.util.*; +import java.util.Calendar; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 85ef226..786efd9 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -1,7 +1,14 @@ # RedisBungee configuration file. -# Get Redis from http://redis.io/ +# Notice: +# Redis 7.2.4 is last free and open source Redis version after license change +# https://download.redis.io/releases/redis-7.2.4.tar.gz which you have to compile yourself, +# unless your package manager still provide it. +# Here is The alternatives +# - 'ValKey' By linux foundation https://valkey.io/download/ +# - 'KeyDB' by Snapchat inc https://docs.keydb.dev/docs/download/ -# The Redis server you use. + +# The 'Redis', 'ValKey', 'KeyDB' server you will use. # these settings are ignored when cluster mode is enabled. redis-server: 127.0.0.1 redis-port: 6379 @@ -12,7 +19,7 @@ cluster-mode-enabled: false # FORMAT: # redis-cluster-servers: -# - host: 127.0.0.1 +# - host: 127.0.0.1` # port: 2020 # - host: 127.0.0.1 # port: 2021 @@ -25,11 +32,10 @@ redis-cluster-servers: - host: 127.0.0.1 port: 6379 -# THIS FEATURE IS REDIS V6+ # OPTIONAL: if your redis uses acl usernames set the username here. leave empty for no username. redis-username: "" -# OPTIONAL but recommended: If your Redis server uses AUTH, set the password required. +# OPTIONAL but recommended: If your Redis server uses AUTH, set the required password. redis-password: "" # Maximum connections that will be maintained to the Redis server. @@ -37,44 +43,100 @@ redis-password: "" # inefficient plugins or a lot of players. max-redis-connections: 10 -# since redis can support ssl by version 6 you can use ssl / tls in redis bungee too! +# since redis can support ssl by version 6 you can use SSL/TLS in redis bungee too! # but there is more configuration needed to work see https://github.com/ProxioDev/RedisBungee/issues/18 # Keep note that SSL/TLS connections will decrease redis performance so use it when needed. useSSL: false -# An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank. -proxy-id: "test-1" +# An identifier for this network, which helps to separate redisbungee instances on same redis instance. +# You can use environment variable 'REDISBUNGEE_NETWORK_ID' to override +network-id: "main" -# since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool. +# An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank. +# You can set Environment variable 'REDISBUNGEE_PROXY_ID' to override +proxy-id: "proxy-1" + +# since RedisBungee Internally now uses UnifiedJedis instead of Jedis, JedisPool. # which will break compatibility with old plugins that uses RedisBungee JedisPool -# so to mitigate this issue, we will instruct RedisBungee to init an JedisPool for compatibility reasons. -# enabled by default -# ignored when cluster mode is enabled -enable-jedis-pool-compatibility: true +# so to mitigate this issue, RedisBungee will create an JedisPool for compatibility reasons. +# disabled by default +# Automatically disabled when cluster mode is enabled +enable-jedis-pool-compatibility: false + # max connections for the compatibility pool compatibility-max-connections: 3 -# Register redis bungee legacy commands -# if this disabled override-bungee-commands will be ignored -register-legacy-commands: false +# restore old login behavior before 0.9.0 update +# enabled by default +# when true: when player login and there is old player with same uuid it will get disconnected as result and new player will log in +# when false: when a player login but login will fail because old player is still connected. +kick-when-online: true -# Whether or not RedisBungee should install its version of regular BungeeCord commands. -# Often, the RedisBungee commands are desired, but in some cases someone may wish to -# override the commands using another plugin. -# -# If you are just denying access to the commands, RedisBungee uses the default BungeeCord -# permissions - just deny them and access will be denied. -# -# Please note that with build 787+, most commands overridden by RedisBungee were moved to -# modules, and these must be disabled or overridden yourself. -override-bungee-commands: false +# enabled by default +# this option tells RedisBungee handle motd and set online count, when motd is requested +# you can disable this when you want to handle motd yourself, use RedisBungee api to get total players when needed :) +handle-motd: true # A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic # restart scripts. +# Automatically disabled if handle-motd is disabled. exempt-ip-addresses: [] -# restore old login when online behavior before 0.9.0 update -disable-kick-when-online: false +# disabled by default +# RedisBungee will attempt to connect player to last server that was stored. +reconnect-to-last-server: false + +# For redis bungee legacy commands +# either can be run using '/rbl glist' for example +# or if 'install' is set to true '/glist' can be used. +# 'install' also overrides the proxy installed commands +# +# In legacy commands each command got it own permissions since they had it own permission pre new command system, +# so it's also applied to subcommands in '/rbl'. +commands: + # Permission redisbungee.legacy.use + redisbungee-legacy: + enabled: false + subcommands: + # Permission redisbungee.command.glist + glist: + enabled: false + install: false + # Permission redisbungee.command.find + find: + enabled: false + install: false + # Permission redisbungee.command.lastseen + lastseen: + enabled: false + install: false + # Permission redisbungee.command.ip + ip: + enabled: false + install: false + # Permission redisbungee.command.pproxy + pproxy: + enabled: false + install: false + # Permission redisbungee.command.sendtoall + sendtoall: + enabled: false + install: false + # Permission redisbungee.command.serverid + serverid: + enabled: false + install: false + # Permission redisbungee.command.serverids + serverids: + enabled: false + install: false + # Permission redisbungee.command.plist + plist: + enabled: false + install: false + # Permission redisbungee.command.use + redisbungee: + enabled: true # Config version DO NOT CHANGE!!!! -config-version: 1 +config-version: 2 diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml new file mode 100644 index 0000000..817a053 --- /dev/null +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -0,0 +1,55 @@ +# this config file is for messages / Languages +# use MiniMessage format https://docs.advntr.dev/minimessage/format.html +# for colors etc... Legacy chat color is not supported. + +# Language codes used in minecraft from the minecraft wiki +# example: en-us for american english and ar-sa for arabic + +# all codes can be obtained from link below +# from the colum Locale Code -> In-game +# NOTE: minecraft wiki shows languages like this `en_us` in config it should be `en-us` +# https://minecraft.wiki/w/Language + +# example: +# lets assume we want to add arabic language. +# messages: +# logged-in-other-location: +# en-us: "You logged in from another location!" +# ar-sa: "لقد اتصلت من مكان اخر" + + +# RedisBungee Prefix if ever used. +prefix: "[RedisBungee]" + +# en-us is american English, Which is the default language used when a language for a message isn't defined. +# Warning: IF THE set default locale wasn't defined in the config for all messages, plugin will not load. +# set the Default locale +default-locale: en-us + +# send language based on client sent settings +# if you don't have languages configured For client Language +# it will default to language that has been set above +# NOTE: due minecraft protocol not sending player settings during login, +# some of the messages like logged-in-other-location will +# skip translation and use default locale that has been set in default-locale. +use-client-locale: true + +# messages that are used during login, and connecting to Last server +messages: + logged-in-other-location: + en-us: "You logged in from another location!" + pt-br: "Você está logado em outra localização!" + already-logged-in: + en-us: "You are already logged in!" + pt-br: "Você já está logado!" + server-not-found: + # placeholder displays server name in the message. + en-us: "unable to connect you to the last server, because server was not found." + pt-br: "falha ao conectar você ao último servidor, porque o servidor não foi encontrado." + server-connecting: + # placeholder displays server name in the message. + en-us: "Connecting you to ..." + pt-br: "Conectando você a ..." + +# DO NOT CHANGE!!!!! +config-version: 1 diff --git a/RedisBungee-API/src/main/resources/messages.yml b/RedisBungee-API/src/main/resources/messages.yml deleted file mode 100644 index a1b1853..0000000 --- a/RedisBungee-API/src/main/resources/messages.yml +++ /dev/null @@ -1,2 +0,0 @@ -logged-in-other-location: "§cYou logged in from another location!" -already-logged-in: "§cYou are already logged in!" \ No newline at end of file diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 78cf66f..217be17 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -5,18 +5,17 @@ plugins { id("xyz.jpenilla.run-waterfall") version "2.0.0" } - -repositories { - mavenCentral() - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } // bungeecord -} -val bungeecordApiVersion = "1.19-R0.1-SNAPSHOT" dependencies { api(project(":RedisBungee-API")) - compileOnly("net.md-5:bungeecord-api:$bungeecordApiVersion") { + compileOnly(libs.platform.bungeecord) { exclude("com.google.guava", "guava") exclude("com.google.code.gson", "gson") + exclude("net.kyori","adventure-api") } + implementation(libs.adventure.platforms.bungeecord) + implementation(libs.adventure.gson) + implementation(libs.acf.bungeecord) + implementation(project(":RedisBungee-Commands")) } description = "RedisBungee Bungeecord implementation" @@ -40,11 +39,13 @@ tasks { options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) } runWaterfall { - waterfallVersion("1.19") + waterfallVersion("1.20") + environment["REDISBUNGEE_PROXY_ID"] = "bungeecord-1" + environment["REDISBUNGEE_NETWORK_ID"] = "dev" } compileJava { options.encoding = Charsets.UTF_8.name() - options.release.set(8) + options.release.set(17) } javadoc { options.encoding = Charsets.UTF_8.name() @@ -73,6 +74,10 @@ tasks { relocate("com.google.gson", "com.imaginarycode.minecraft.redisbungee.internal.com.google.gson") relocate("com.google.j2objc", "com.imaginarycode.minecraft.redisbungee.internal.com.google.j2objc") relocate("com.google.thirdparty", "com.imaginarycode.minecraft.redisbungee.internal.com.google.thirdparty") + relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") + // acf shade + relocate("co.aikar.commands", "com.imaginarycode.minecraft.redisbungee.internal.acf.commands") + } } diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeCommandPlatformHelper.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeCommandPlatformHelper.java new file mode 100644 index 0000000..019d06f --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeCommandPlatformHelper.java @@ -0,0 +1,17 @@ +package com.imaginarycode.minecraft.redisbungee; + +import co.aikar.commands.BungeeCommandIssuer; +import co.aikar.commands.CommandIssuer; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; + +public class BungeeCommandPlatformHelper extends CommandPlatformHelper { + + @Override + public void sendMessage(CommandIssuer issuer, Component component) { + BungeeCommandIssuer bIssuer = (BungeeCommandIssuer) issuer; + bIssuer.getIssuer().sendMessage(BungeeComponentSerializer.get().serialize(component)); + } + +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java deleted file mode 100644 index dea9185..0000000 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.PlayerDisconnectEvent; -import net.md_5.bungee.api.event.PostLoginEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; - -import java.util.UUID; - -public class BungeeDataManager extends AbstractDataManager implements Listener { - - public BungeeDataManager(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - @EventHandler - public void onPostLogin(PostLoginEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @EventHandler - public void onPlayerDisconnect(PlayerDisconnectEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @EventHandler - public void onPubSubMessage(PubSubMessageEvent event) { - handlePubSubMessage(event.getChannel(), event.getMessage()); - } - - @Override - public boolean handleKick(UUID target, String message) { - // check if the player is online on this proxy - ProxiedPlayer player = plugin.getPlayer(target); - if (player == null) return false; - player.disconnect(TextComponent.fromLegacyText(message)); - return true; - } -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java new file mode 100644 index 0000000..8540005 --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.LoginEvent; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; + +import java.util.concurrent.TimeUnit; + + +public class BungeePlayerDataManager extends PlayerDataManager implements Listener { + + public BungeePlayerDataManager(RedisBungeePlugin plugin) { + super(plugin); + } + + @Override + @EventHandler + public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) { + super.handleNetworkPlayerServerChange(event); + } + + @Override + @EventHandler + public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { + super.handleNetworkPlayerQuit(event); + } + + @Override + @EventHandler + public void onPubSubMessageEvent(PubSubMessageEvent event) { + super.handlePubSubMessageEvent(event); + } + + @Override + @EventHandler + public void onServerConnectedEvent(ServerConnectedEvent event) { + final String currentServer = event.getServer().getInfo().getName(); + final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName(); + super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); + } + + @EventHandler + public void onLoginEvent(LoginEvent event) { + event.registerIntent((Plugin) plugin); + // check if online + if (getLastOnline(event.getConnection().getUniqueId()) == 0) { + if (plugin.configuration().kickWhenOnline()) { + kickPlayer(event.getConnection().getUniqueId(), plugin.langConfiguration().messages().loggedInFromOtherLocation()); + // wait 3 seconds before releasing the event + plugin.executeAsyncAfter(() -> event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); + } else { + event.setCancelled(true); + event.setCancelReason(BungeeComponentSerializer.get().serialize(plugin.langConfiguration().messages().alreadyLoggedIn())); + event.completeIntent((Plugin) plugin); + } + } else { + event.completeIntent((Plugin) plugin); + } + + } + + @Override + @EventHandler + public void onLoginEvent(PostLoginEvent event) { + super.addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getAddress().getAddress()); + } + + @Override + @EventHandler + public void onDisconnectEvent(PlayerDisconnectEvent event) { + super.removePlayer(event.getPlayer().getUniqueId()); + } + + +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java deleted file mode 100644 index f1a46c6..0000000 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import net.md_5.bungee.api.connection.PendingConnection; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import redis.clients.jedis.UnifiedJedis; -public class BungeePlayerUtils { - - public static void createBungeePlayer(ProxiedPlayer player, UnifiedJedis unifiedJedis, boolean fireEvent) { - String serverName = null; - if (player.getServer() != null) { - serverName = player.getServer().getInfo().getName(); - } - PendingConnection pendingConnection = player.getPendingConnection(); - PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, pendingConnection.getAddress().getAddress(), fireEvent); - } - -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index f540ab6..d5ae22b 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -10,131 +10,104 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; +import co.aikar.commands.BungeeCommandManager; +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.LangConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.tasks.*; -import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands; +import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; +import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; +import com.imaginarycode.minecraft.redisbungee.commands.CommandLoader; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.*; -import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; import com.squareup.okhttp.Dispatcher; import com.squareup.okhttp.OkHttpClient; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Plugin; -import redis.clients.jedis.*; +import net.md_5.bungee.api.scheduler.ScheduledTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.JedisPool; -import java.io.*; +import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; -import java.util.*; +import java.sql.Date; +import java.time.Instant; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; -public class RedisBungee extends Plugin implements RedisBungeePlugin, ConfigLoader { +public class RedisBungee extends Plugin implements RedisBungeePlugin, ConfigLoader, LangConfigLoader { private static RedisBungeeAPI apiStatic; - private AbstractRedisBungeeAPI api; private RedisBungeeMode redisBungeeMode; - private PubSubListener psl = null; + private ProxyDataManager proxyDataManager; + private BungeePlayerDataManager playerDataManager; + private ScheduledTask heartbeatTask; + private ScheduledTask cleanupTask; private Summoner summoner; private UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; - private BungeeDataManager dataManager; + private LangConfiguration langConfiguration; private OkHttpClient httpClient; - private volatile List proxiesIds; - private final AtomicInteger globalPlayerCount = new AtomicInteger(); - private Future integrityCheck; - private Future heartbeatTask; - private static final Object SERVER_TO_PLAYERS_KEY = new Object(); - private final Cache> serverToPlayersCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.SECONDS) - .build(); + private BungeeCommandManager commandManager; + + private final Logger logger = LoggerFactory.getLogger("RedisBungee"); @Override - public RedisBungeeConfiguration getConfiguration() { + public RedisBungeeConfiguration configuration() { return this.configuration; } @Override - public int getCount() { - return this.globalPlayerCount.get(); + public LangConfiguration langConfiguration() { + return this.langConfiguration; } - @Override - public Set getLocalPlayersAsUuidStrings() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (ProxiedPlayer player : getProxy().getPlayers()) { - builder.add(player.getUniqueId().toString()); - } - return builder.build(); - } - - @Override - public AbstractDataManager getDataManager() { - return this.dataManager; - } - - @Override public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() { return this.api; } + @Override + public ProxyDataManager proxyDataManager() { + return this.proxyDataManager; + } + + @Override + public PlayerDataManager playerDataManager() { + return this.playerDataManager; + } + @Override public UUIDTranslator getUuidTranslator() { return this.uuidTranslator; } - @Override - public Multimap serverToPlayersCache() { - try { - return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getProxiesIds() { - return proxiesIds; - } - - @Override - public PubSubListener getPubSubListener() { - return this.psl; - } - - @Override - public void executeAsync(Runnable runnable) { - this.getProxy().getScheduler().runAsync(this, runnable); - } - - @Override - public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) { - this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit); - } - @Override public void fireEvent(Object event) { this.getProxy().getPluginManager().callEvent((Event) event); @@ -147,17 +120,32 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin getLocalOnlineUUIDs() { + HashSet uuids = new HashSet<>(); + ProxyServer.getInstance().getPlayers().forEach((proxiedPlayer) -> uuids.add(proxiedPlayer.getUniqueId())); + return uuids; + } + + @Override + protected void handlePlatformCommandExecution(String command) { + logInfo("Dispatching {}", command); + ProxyServer.getInstance().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), command); + } + }; + this.playerDataManager = new BungeePlayerDataManager(this); + + getProxy().getPluginManager().registerListener(this, this.playerDataManager); + getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this)); + // start listening + getProxy().getScheduler().runAsync(this, proxyDataManager); + // heartbeat + this.heartbeatTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS); + // cleanup + this.cleanupTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS); // init the http lib httpClient = new OkHttpClient(); Dispatcher dispatcher = new Dispatcher(getExecutorService()); @@ -226,71 +247,49 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin implements Listener { - - - public RedisBungeeBungeeListener(RedisBungeePlugin plugin, List exemptAddresses) { - super(plugin, exemptAddresses); - } - - @Override - @EventHandler(priority = HIGHEST) - public void onLogin(LoginEvent event) { - event.registerIntent((Plugin) plugin); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - if (event.isCancelled()) { - return null; - } - if (plugin.getConfiguration().restoreOldKickBehavior()) { - for (String s : plugin.getProxiesIds()) { - if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) { - event.setCancelled(true); - event.setCancelReason(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)); - return null; - } - } - } else if (api.isPlayerOnline(event.getConnection().getUniqueId())) { - PlayerUtils.setKickedOtherLocation(event.getConnection().getUniqueId().toString(), unifiedJedis); - api.kickPlayer(event.getConnection().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); - } - return null; - } finally { - event.completeIntent((Plugin) plugin); - } - } - }); - } - - @Override - @EventHandler - public void onPostLogin(PostLoginEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), unifiedJedis); - BungeePlayerUtils.createBungeePlayer(event.getPlayer(), unifiedJedis, true); - return null; - } - }); - } - - @Override - @EventHandler - public void onPlayerDisconnect(PlayerDisconnectEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true); - return null; - } - }); - - } - - @Override - @EventHandler - public void onServerChange(ServerConnectedEvent event) { - final String currentServer = event.getServer().getInfo().getName(); - final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName(); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName()); - PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer); - return null; - } - }); - } - - @Override - @EventHandler - public void onPing(ProxyPingEvent event) { - if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) { - return; - } - ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection()); - - if (forced != null && event.getConnection().getListener().isPingPassthrough()) { - return; - } - event.getResponse().getPlayers().setOnline(plugin.getCount()); - } - - @Override - @SuppressWarnings("UnstableApiUsage") - @EventHandler - public void onPluginMessage(PluginMessageEvent event) { - if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) { - final String currentChannel = event.getTag(); - final byte[] data = Arrays.copyOf(event.getData(), event.getData().length); - plugin.executeAsync(() -> { - ByteArrayDataInput in = ByteStreams.newDataInput(data); - - String subchannel = in.readUTF(); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - String type; - - switch (subchannel) { - case "PlayerList": - out.writeUTF("PlayerList"); - Set original = Collections.emptySet(); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - original = plugin.getPlayers(); - } else { - out.writeUTF(type); - try { - original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); - } catch (IllegalArgumentException ignored) { - } - } - Set players = new HashSet<>(); - for (UUID uuid : original) - players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false)); - out.writeUTF(Joiner.on(',').join(players)); - break; - case "PlayerCount": - out.writeUTF("PlayerCount"); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - out.writeInt(plugin.getCount()); - } else { - out.writeUTF(type); - try { - out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); - } catch (IllegalArgumentException e) { - out.writeInt(0); - } - } - break; - case "LastOnline": - String user = in.readUTF(); - out.writeUTF("LastOnline"); - out.writeUTF(user); - out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); - break; - case "ServerPlayers": - String type1 = in.readUTF(); - out.writeUTF("ServerPlayers"); - Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - - boolean includesUsers; - - switch (type1) { - case "COUNT": - includesUsers = false; - break; - case "PLAYERS": - includesUsers = true; - break; - default: - // TODO: Should I raise an error? - return; - } - - out.writeUTF(type1); - - if (includesUsers) { - Multimap human = HashMultimap.create(); - for (Map.Entry entry : multimap.entries()) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - serializeMultimap(human, true, out); - } else { - serializeMultiset(multimap.keys(), out); - } - break; - case "Proxy": - out.writeUTF("Proxy"); - out.writeUTF(plugin.getConfiguration().getProxyId()); - break; - case "PlayerProxy": - String username = in.readUTF(); - out.writeUTF("PlayerProxy"); - out.writeUTF(username); - out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); - break; - default: - return; - } - - ((Server) event.getSender()).sendData(currentChannel, out.toByteArray()); - }); - } - } - - @Override - @EventHandler - public void onPubSubMessage(PubSubMessageEvent event) { - if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) { - String message = event.getMessage(); - if (message.startsWith("/")) - message = message.substring(1); - plugin.logInfo("Invoking command via PubSub: /" + message); - ((Plugin) plugin).getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), message); - } - } -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java new file mode 100644 index 0000000..b5c401f --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import net.md_5.bungee.api.AbstractReconnectHandler; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +import java.util.*; + +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.*; + +public class RedisBungeeListener implements Listener { + + private final RedisBungeePlugin plugin; + + public RedisBungeeListener(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPing(ProxyPingEvent event) { + if (!plugin.configuration().handleMotd()) return; + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getAddress().getAddress())) return; + ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection()); + + if (forced != null && event.getConnection().getListener().isPingPassthrough()) return; + event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers()); + } + + @SuppressWarnings("UnstableApiUsage") + @EventHandler + public void onPluginMessage(PluginMessageEvent event) { + if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) { + final String currentChannel = event.getTag(); + final byte[] data = Arrays.copyOf(event.getData(), event.getData().length); + plugin.executeAsync(() -> { + ByteArrayDataInput in = ByteStreams.newDataInput(data); + + String subchannel = in.readUTF(); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + String type; + + switch (subchannel) { + case "PlayerList" -> { + out.writeUTF("PlayerList"); + Set original = Collections.emptySet(); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + original = plugin.proxyDataManager().networkPlayers(); + } else { + out.writeUTF(type); + try { + original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); + } catch (IllegalArgumentException ignored) { + } + } + Set players = new HashSet<>(); + for (UUID uuid : original) + players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false)); + out.writeUTF(Joiner.on(',').join(players)); + } + case "PlayerCount" -> { + out.writeUTF("PlayerCount"); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + out.writeInt(plugin.proxyDataManager().totalNetworkPlayers()); + } else { + out.writeUTF(type); + try { + out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); + } catch (IllegalArgumentException e) { + out.writeInt(0); + } + } + } + case "LastOnline" -> { + String user = in.readUTF(); + out.writeUTF("LastOnline"); + out.writeUTF(user); + out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); + } + case "ServerPlayers" -> { + String type1 = in.readUTF(); + out.writeUTF("ServerPlayers"); + Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + boolean includesUsers; + switch (type1) { + case "COUNT" -> includesUsers = false; + case "PLAYERS" -> includesUsers = true; + default -> { + // TODO: Should I raise an error? + return; + } + } + out.writeUTF(type1); + if (includesUsers) { + Multimap human = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); + } + serializeMultimap(human, true, out); + } else { + serializeMultiset(multimap.keys(), out); + } + } + case "Proxy" -> { + out.writeUTF("Proxy"); + out.writeUTF(plugin.configuration().getProxyId()); + } + case "PlayerProxy" -> { + String username = in.readUTF(); + out.writeUTF("PlayerProxy"); + out.writeUTF(username); + out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); + } + default -> { + return; + } + } + + ((Server) event.getSender()).sendData(currentChannel, out.toByteArray()); + }); + } + } + + @EventHandler + public void onServerConnectEvent(ServerConnectEvent event) { + if (event.getReason() == ServerConnectEvent.Reason.JOIN_PROXY && plugin.configuration().handleReconnectToLastServer()) { + ProxiedPlayer player = event.getPlayer(); + String lastServer = plugin.playerDataManager().getLastServerFor(event.getPlayer().getUniqueId()); + if (lastServer == null) return; + player.sendMessage(BungeeComponentSerializer.get().serialize(plugin.langConfiguration().messages().serverConnecting(player.getLocale(), lastServer))); + ServerInfo serverInfo = ProxyServer.getInstance().getServerInfo(lastServer); + if (serverInfo == null) { + player.sendMessage(BungeeComponentSerializer.get().serialize(plugin.langConfiguration().messages().serverNotFound(player.getLocale(), lastServer))); + return; + } + event.setTarget(serverInfo); + } + } +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java deleted file mode 100644 index e5a9ea3..0000000 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.commands; - -import com.google.common.base.Joiner; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.imaginarycode.minecraft.redisbungee.RedisBungee; -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.config.ServerInfo; -import net.md_5.bungee.api.plugin.Command; - -import java.net.InetAddress; -import java.text.SimpleDateFormat; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - -/** - * This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen. - *

- * All classes use the {@link AbstractRedisBungeeAPI}. - * - * @author tuxed - * @since 0.2.3 - */ -public class RedisBungeeCommands { - private static final BaseComponent[] NO_PLAYER_SPECIFIED = - new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create(); - private static final BaseComponent[] PLAYER_NOT_FOUND = - new ComponentBuilder("No such player found.").color(ChatColor.RED).create(); - private static final BaseComponent[] NO_COMMAND_SPECIFIED = - new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create(); - - private static String playerPlural(int num) { - return num == 1 ? num + " player is" : num + " players are"; - } - - public static class GlistCommand extends Command { - private final RedisBungee plugin; - - public GlistCommand(RedisBungee plugin) { - super("glist", "bungeecord.command.list", "redisbungee", "rglist"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - int count = plugin.getAbstractRedisBungeeApi().getPlayerCount(); - BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW) - .append(playerPlural(count) + " currently online.").create(); - if (args.length > 0 && args[0].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - for (Map.Entry entry : serverToPlayers.entries()) { - // if for any reason UUID translation fails just return the uuid as name, to make command finish executing. - String playerName = plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false); - human.put(entry.getKey(), playerName != null ? playerName : entry.getValue().toString()); - } - for (String server : new TreeSet<>(serverToPlayers.keySet())) { - TextComponent serverName = new TextComponent(); - serverName.setColor(ChatColor.GREEN); - serverName.setText("[" + server + "] "); - TextComponent serverCount = new TextComponent(); - serverCount.setColor(ChatColor.YELLOW); - serverCount.setText("(" + serverToPlayers.get(server).size() + "): "); - TextComponent serverPlayers = new TextComponent(); - serverPlayers.setColor(ChatColor.WHITE); - serverPlayers.setText(Joiner.on(", ").join(human.get(server))); - sender.sendMessage(serverName, serverCount, serverPlayers); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create()); - } - } - }); - } - } - - public static class FindCommand extends Command { - private final RedisBungee plugin; - - public FindCommand(RedisBungee plugin) { - super("find", "bungeecord.command.find", "rfind"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - ServerInfo si = plugin.getProxy().getServerInfo(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid)); - if (si != null) { - TextComponent message = new TextComponent(); - message.setColor(ChatColor.BLUE); - message.setText(args[0] + " is on " + si.getName() + "."); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class LastSeenCommand extends Command { - private final RedisBungee plugin; - - public LastSeenCommand(RedisBungee plugin) { - super("lastseen", "redisbungee.command.lastseen", "rlastseen"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid); - TextComponent message = new TextComponent(); - if (secs == 0) { - message.setColor(ChatColor.GREEN); - message.setText(args[0] + " is currently online."); - } else if (secs != -1) { - message.setColor(ChatColor.BLUE); - message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); - } else { - message.setColor(ChatColor.RED); - message.setText(args[0] + " has never been online."); - } - sender.sendMessage(message); - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class IpCommand extends Command { - private final RedisBungee plugin; - - public IpCommand(RedisBungee plugin) { - super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid); - if (ia != null) { - TextComponent message = new TextComponent(); - message.setColor(ChatColor.GREEN); - message.setText(args[0] + " is connected from " + ia.toString() + "."); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class PlayerProxyCommand extends Command { - private final RedisBungee plugin; - - public PlayerProxyCommand(RedisBungee plugin) { - super("pproxy", "redisbungee.command.pproxy"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid); - if (proxy != null) { - TextComponent message = new TextComponent(); - message.setColor(ChatColor.GREEN); - message.setText(args[0] + " is connected to " + proxy + "."); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class SendToAll extends Command { - private final RedisBungee plugin; - - public SendToAll(RedisBungee plugin) { - super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall"); - this.plugin = plugin; - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (args.length > 0) { - String command = Joiner.on(" ").skipNulls().join(args); - plugin.getAbstractRedisBungeeApi().sendProxyCommand(command); - TextComponent message = new TextComponent(); - message.setColor(ChatColor.GREEN); - message.setText("Sent the command /" + command + " to all proxies."); - sender.sendMessage(message); - } else { - sender.sendMessage(NO_COMMAND_SPECIFIED); - } - } - } - - public static class ServerId extends Command { - private final RedisBungee plugin; - - public ServerId(RedisBungee plugin) { - super("serverid", "redisbungee.command.serverid", "rserverid"); - this.plugin = plugin; - } - - @Override - public void execute(CommandSender sender, String[] args) { - TextComponent textComponent = new TextComponent(); - textComponent.setText("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + "."); - textComponent.setColor(ChatColor.YELLOW); - sender.sendMessage(textComponent); - } - } - - public static class ServerIds extends Command { - private final RedisBungee plugin; - public ServerIds(RedisBungee plugin) { - super("serverids", "redisbungee.command.serverids"); - this.plugin =plugin; - } - - @Override - public void execute(CommandSender sender, String[] strings) { - TextComponent textComponent = new TextComponent(); - textComponent.setText("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies())); - textComponent.setColor(ChatColor.YELLOW); - sender.sendMessage(textComponent); - } - } - - public static class PlistCommand extends Command { - private final RedisBungee plugin; - - public PlistCommand(RedisBungee plugin) { - super("plist", "redisbungee.command.plist", "rplist"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId(); - if (!plugin.getProxiesIds().contains(proxy)) { - sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create()); - return; - } - Set players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy); - BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW) - .append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create(); - if (args.length >= 2 && args[1].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - for (Map.Entry entry : serverToPlayers.entries()) { - if (players.contains(entry.getValue())) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - } - for (String server : new TreeSet<>(human.keySet())) { - TextComponent serverName = new TextComponent(); - serverName.setColor(ChatColor.RED); - serverName.setText("[" + server + "] "); - TextComponent serverCount = new TextComponent(); - serverCount.setColor(ChatColor.YELLOW); - serverCount.setText("(" + human.get(server).size() + "): "); - TextComponent serverPlayers = new TextComponent(); - serverPlayers.setColor(ChatColor.WHITE); - serverPlayers.setText(Joiner.on(", ").join(human.get(server))); - sender.sendMessage(serverName, serverCount, serverPlayers); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(new ComponentBuilder("To see all players online, use /plist " + proxy + " showall.").color(ChatColor.YELLOW).create()); - } - } - }); - } - } -} \ No newline at end of file diff --git a/RedisBungee-Commands/build.gradle.kts b/RedisBungee-Commands/build.gradle.kts new file mode 100644 index 0000000..0c5944e --- /dev/null +++ b/RedisBungee-Commands/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + `java-library` +} + +dependencies { + implementation(project(":RedisBungee-API")) + implementation(libs.acf.core) +} + +description = "RedisBungee common commands" + + +tasks { + compileJava { + options.encoding = Charsets.UTF_8.name() + options.release.set(17) + } + javadoc { + options.encoding = Charsets.UTF_8.name() + } + processResources { + filteringCharset = Charsets.UTF_8.name() + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java new file mode 100644 index 0000000..5346be9 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands; + +import co.aikar.commands.CommandManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; + +import com.imaginarycode.minecraft.redisbungee.commands.legacy.LegacyRedisBungeeCommands; + +public class CommandLoader { + + public static void initCommands(CommandManager commandManager, RedisBungeePlugin plugin) { + var commandsConfiguration = plugin.configuration().commandsConfiguration(); + if (commandsConfiguration.redisbungeeEnabled()) { + commandManager.registerCommand(new CommandRedisBungee(plugin)); + } + if (commandsConfiguration.redisbungeeLegacyEnabled()) { + commandManager.registerCommand(new LegacyRedisBungeeCommands(commandManager,plugin)); + } + + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java new file mode 100644 index 0000000..c67b7a0 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.RegisteredCommand; +import co.aikar.commands.annotation.*; +import com.google.common.primitives.Ints; +import com.imaginarycode.minecraft.redisbungee.Constants; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; +import com.imaginarycode.minecraft.redisbungee.commands.utils.StopperUUIDCleanupTask; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@CommandAlias("rb|redisbungee") +@CommandPermission("redisbungee.command.use") +@Description("Main command") +public class CommandRedisBungee extends AdventureBaseCommand { + + private final RedisBungeePlugin plugin; + + public CommandRedisBungee(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + @Default + @Subcommand("info|version|git") + @Description("information about current redisbungee build") + public void info(CommandIssuer issuer) { + final String message = """ + This proxy is running RedisBungee Limework's fork + ======================================== + RedisBungee version: + Commit: + ======================================== + run /rb help for more commands"""; + sendMessage( + issuer, + MiniMessage.miniMessage() + .deserialize( + message, + Placeholder.component("version", Component.text(Constants.VERSION)), + Placeholder.component( + "commit", + Component.text(Constants.GIT_COMMIT.substring(0, 8)) + .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, Constants.getGithubCommitLink())) + .hoverEvent(HoverEvent.showText(Component.text("Click me to open: " + Constants.getGithubCommitLink()))) + ))); + } + // ......: ...... + @HelpCommand + @Description("shows the help page") + public void help(CommandIssuer issuer) { + final String barFormat = "========================================"; + final String commandFormat = "/rb : "; + + TextComponent.Builder message = Component.text(); + message.append(MiniMessage.miniMessage().deserialize(barFormat)); + + getSubCommands().forEach((subCommand, registeredCommand) -> { + String[] split = registeredCommand.getCommand().split(" "); + if (split.length > 1 && subCommand.equalsIgnoreCase(split[1])) { + message.appendNewline().append(MiniMessage.miniMessage().deserialize(commandFormat, Placeholder.component("sub-command", Component.text(subCommand)), + Placeholder.component("description", MiniMessage.miniMessage().deserialize(registeredCommand.getHelpText())) + )); + } + }); + + message.appendNewline().append(MiniMessage.miniMessage().deserialize(barFormat)); + + sendMessage(issuer, message.build()); + } + @Subcommand("clean") + @Description("cleans up the uuid cache WARNING... command above could cause performance issues") + @Private + public void cleanUp(CommandIssuer issuer) { + if (StopperUUIDCleanupTask.isRunning) { + sendMessage(issuer, + Component.text("cleanup is currently running!").color(NamedTextColor.RED)); + return; + } + sendMessage(issuer, + Component.text("cleanup is Starting, you should see the output status in the proxy console").color(NamedTextColor.GOLD)); + plugin.executeAsync(new StopperUUIDCleanupTask(plugin)); + } + + + + private List> subListProxies(List> data, final int currentPage, final int pageSize) { + return data.subList(((currentPage * pageSize) - pageSize), Ints.constrainToRange(currentPage * pageSize, 0, data.size())); + + } + @Subcommand("show") + @Description("Shows proxies in this network") + public void showProxies(CommandIssuer issuer, String[] args) { + final String closer = "========================================"; + final String pageTop = "Page: / Network ID: Proxies online: "; + final String proxy = " : online"; + final String proxyHere = " (#) "; + final String nextPage = ">>>>>"; + final String previousPage = "<<<<< "; + final String pageInvalid = "invalid page"; + final String noProxies = "No proxies were found :("; + + final int pageSize = 16; + + int currentPage; + if (args.length > 0) { + try { + currentPage = Integer.parseInt(args[0]); + if (currentPage < 1) currentPage = 1; + } catch (NumberFormatException e) { + sendMessage(issuer, MiniMessage.miniMessage().deserialize(pageInvalid)); + return; + } + } else currentPage = 1; + + var data = new ArrayList<>(plugin.proxyDataManager().eachProxyCount().entrySet()); + // there is no way this runs because there is always an heartbeat. + // if not could be some shenanigans done by devs :P + if (data.isEmpty()) { + sendMessage(issuer, MiniMessage.miniMessage().deserialize(noProxies)); + return; + } + // compute the total pages + int maxPages = (int) Math.ceil(data.size() / (double) pageSize); + if (currentPage > maxPages) currentPage = maxPages; + var subList = subListProxies(data, currentPage, pageSize); + TextComponent.Builder builder = Component.text(); + builder.append(MiniMessage.miniMessage().deserialize(closer)).appendNewline(); + builder.append(MiniMessage.miniMessage().deserialize(pageTop, + Placeholder.component("current", Component.text(currentPage)), + Placeholder.component("max", Component.text(maxPages)), + Placeholder.component("network", Component.text(plugin.proxyDataManager().networkId())), + Placeholder.component("proxies", Component.text(data.size())) + + + )).appendNewline(); + int left = pageSize; + for (Map.Entry entrySet : subList) { + builder.append(MiniMessage.miniMessage().deserialize(proxy, + + Placeholder.component("proxy", Component.text(entrySet.getKey())), + Placeholder.component("here", Component.text(plugin.proxyDataManager().proxyId().equals(entrySet.getKey()) ? proxyHere : "")), + Placeholder.component("players", Component.text(entrySet.getValue())) + + )).appendNewline(); + left--; + } + while(left > 0) { + builder.appendNewline(); + left--; + } + if (currentPage > 1) { + builder.append(MiniMessage.miniMessage().deserialize(previousPage) + .color(NamedTextColor.WHITE).clickEvent(ClickEvent.runCommand("/rb show " + (currentPage - 1)))); + } else { + builder.append(MiniMessage.miniMessage().deserialize(previousPage).color(NamedTextColor.GRAY)); + } + if (subList.size() == pageSize && !subListProxies(data, currentPage + 1, pageSize).isEmpty()) { + builder.append(MiniMessage.miniMessage().deserialize(nextPage) + .color(NamedTextColor.WHITE).clickEvent(ClickEvent.runCommand("/rb show " + (currentPage + 1)))); + } else { + builder.append(MiniMessage.miniMessage().deserialize(nextPage).color(NamedTextColor.GRAY)); + } + builder.appendNewline(); + builder.append(MiniMessage.miniMessage().deserialize(closer)); + sendMessage(issuer, builder.build()); + + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandFind.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandFind.java new file mode 100644 index 0000000..0a73d36 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandFind.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("find|rfind") +@CommandPermission("redisbungee.command.find") +public class CommandFind extends AdventureBaseCommand { + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandFind(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void find(CommandIssuer issuer, String[] args) { + rootCommand.find(issuer, args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandGList.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandGList.java new file mode 100644 index 0000000..54cc985 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandGList.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("glist|rglist") +@CommandPermission("redisbungee.command.glist") +public class CommandGList extends AdventureBaseCommand { + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandGList(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void gList(CommandIssuer issuer, String[] args) { + rootCommand.gList(issuer, args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandIp.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandIp.java new file mode 100644 index 0000000..410ae91 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandIp.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("ip|playerip|rip|rplayerip") +@CommandPermission("redisbungee.command.ip") +public class CommandIp extends AdventureBaseCommand { + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandIp(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + + @Default + public void ip(CommandIssuer issuer, String[] args) { + this.rootCommand.ip(issuer, args); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandLastSeen.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandLastSeen.java new file mode 100644 index 0000000..f44ea8e --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandLastSeen.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("lastseen|rlastseend") +@CommandPermission("redisbungee.command.lastseen") +public class CommandLastSeen extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandLastSeen(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void lastSeen(CommandIssuer issuer, String[] args) { + this.rootCommand.lastSeen(issuer,args); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPProxy.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPProxy.java new file mode 100644 index 0000000..70a949c --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPProxy.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("pproxy") +@CommandPermission("redisbungee.command.pproxy") +public class CommandPProxy extends AdventureBaseCommand { + private final LegacyRedisBungeeCommands rootCommand; + + public CommandPProxy(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void playerProxy(CommandIssuer issuer, String[] args) { + this.rootCommand.playerProxy(issuer,args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPlist.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPlist.java new file mode 100644 index 0000000..04e1609 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPlist.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("plist|rplist") +@CommandPermission("redisbungee.command.plist") +public class CommandPlist extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandPlist(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void playerList(CommandIssuer issuer, String[] args) { + this.rootCommand.playerList(issuer, args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandSendToAll.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandSendToAll.java new file mode 100644 index 0000000..ad9e1da --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandSendToAll.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("sendtoall|rsendtoall") +@CommandPermission("redisbungee.command.sendtoall") +public class CommandSendToAll extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandSendToAll(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + @Default + public void sendToAll(CommandIssuer issuer, String[] args) { + this.rootCommand.sendToAll(issuer, args); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerId.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerId.java new file mode 100644 index 0000000..c62c2f5 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerId.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("serverid|rserverid") +@CommandPermission("redisbungee.command.serverid") +public class CommandServerId extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandServerId(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + @Default + public void serverId(CommandIssuer issuer) { + this.rootCommand.serverId(issuer); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerIds.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerIds.java new file mode 100644 index 0000000..85b53b7 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerIds.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("serverids|rserverids") +@CommandPermission("redisbungee.command.serverids") +public class CommandServerIds extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandServerIds(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void serverIds(CommandIssuer issuer) { + this.rootCommand.serverIds(issuer); + } + + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/LegacyRedisBungeeCommands.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/LegacyRedisBungeeCommands.java new file mode 100644 index 0000000..6253e96 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/LegacyRedisBungeeCommands.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.CommandManager; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; + +import java.net.InetAddress; +import java.text.SimpleDateFormat; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + +@CommandAlias("rbl|redisbungeelegacy") +@CommandPermission("redisbungee.legacy.use") +public class LegacyRedisBungeeCommands extends AdventureBaseCommand { + + private final RedisBungeePlugin plugin; + + public LegacyRedisBungeeCommands(CommandManager commandManager, RedisBungeePlugin plugin) { + this.plugin = plugin; + var commands = plugin.configuration().commandsConfiguration().legacySubCommandsConfiguration(); + if (!plugin.configuration().commandsConfiguration().redisbungeeLegacyEnabled()) throw new IllegalStateException("someone tried to init me while disabled!"); + if (commands == null) throw new NullPointerException("commands config is null!!"); + + if (commands.installGlist()) commandManager.registerCommand(new CommandGList(this)); + if (commands.installFind()) commandManager.registerCommand(new CommandFind(this)); + if (commands.installIp()) commandManager.registerCommand(new CommandIp(this)); + if (commands.installLastseen()) commandManager.registerCommand(new CommandLastSeen(this)); + if (commands.installPlist()) commandManager.registerCommand(new CommandPlist(this)); + if (commands.installPproxy()) commandManager.registerCommand(new CommandPProxy(this)); + if (commands.installSendtoall()) commandManager.registerCommand(new CommandSendToAll(this)); + if (commands.installServerid()) commandManager.registerCommand(new CommandServerId(this)); + if (commands.installServerids()) commandManager.registerCommand(new CommandServerIds(this)); + } + + private static final Component NO_PLAYER_SPECIFIED = + Component.text("You must specify a player name.", NamedTextColor.RED); + private static final Component PLAYER_NOT_FOUND = + Component.text("No such player found.", NamedTextColor.RED); + private static final Component NO_COMMAND_SPECIFIED = + Component.text("You must specify a command to be run.", NamedTextColor.RED); + + private static String playerPlural(int num) { + return num == 1 ? num + " player is" : num + " players are"; + } + + @Subcommand("glist") + @CommandPermission("redisbungee.command.glist") + public void gList(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + int count = plugin.getAbstractRedisBungeeApi().getPlayerCount(); + Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW); + if (args.length > 0 && args[0].equals("showall")) { + Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + Multimap human = HashMultimap.create(); + serverToPlayers.forEach((key, value) -> { + // if for any reason UUID translation fails just return the uuid as name, to make command finish executing. + String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false); + human.put(key, playerName != null ? playerName : value.toString()); + }); + for (String server : new TreeSet<>(serverToPlayers.keySet())) { + Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN); + Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW); + Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); + sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers)); + } + sendMessage(issuer, playersOnline); + } else { + sendMessage(issuer, playersOnline); + sendMessage(issuer, Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW)); + } + + }); + } + + @Subcommand("find") + @CommandPermission("redisbungee.command.find") + public void find(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + String proxyId = plugin.playerDataManager().getProxyFor(uuid); + if (proxyId != null) { + String serverId = plugin.playerDataManager().getServerFor(uuid); + Component message = Component.text(args[0] + " is on proxy " + proxyId + " on server " + serverId +".", NamedTextColor.BLUE); + sendMessage(issuer, message); + } else { + sendMessage(issuer, PLAYER_NOT_FOUND); + } + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + }); + + } + + @Subcommand("lastseen") + @CommandPermission("redisbungee.command.lastseen") + public void lastSeen(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid); + TextComponent.Builder message = Component.text(); + if (secs == 0) { + message.color(NamedTextColor.GREEN); + message.content(args[0] + " is currently online."); + } else if (secs != -1) { + message.color(NamedTextColor.BLUE); + message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); + } else { + message.color(NamedTextColor.RED); + message.content(args[0] + " has never been online."); + } + sendMessage(issuer, message.build()); + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + + + }); + } + + @Subcommand("ip") + @CommandPermission("redisbungee.command.ip") + public void ip(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid); + if (ia != null) { + TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN); + sendMessage(issuer, message); + } else { + sendMessage(issuer, PLAYER_NOT_FOUND); + } + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + }); + } + + @Subcommand("pproxy") + @CommandPermission("redisbungee.command.pproxy") + public void playerProxy(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid); + if (proxy != null) { + TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN); + sendMessage(issuer, message); + } else { + sendMessage(issuer, PLAYER_NOT_FOUND); + } + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + }); + + } + + @Subcommand("sendtoall") + @CommandPermission("redisbungee.command.sendtoall") + public void sendToAll(CommandIssuer issuer, String[] args) { + if (args.length > 0) { + String command = Joiner.on(" ").skipNulls().join(args); + plugin.getAbstractRedisBungeeApi().sendProxyCommand(command); + TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN); + sendMessage(issuer, message); + } else { + sendMessage(issuer, NO_COMMAND_SPECIFIED); + } + + } + + @Subcommand("serverid") + @CommandPermission("redisbungee.command.serverid") + public void serverId(CommandIssuer issuer) { + sendMessage(issuer, Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); + } + + @Subcommand("serverids") + @CommandPermission("redisbungee.command.serverids") + public void serverIds(CommandIssuer issuer) { + sendMessage(issuer, Component.text("All Proxies IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); + } + + + @Subcommand("plist") + @CommandPermission("redisbungee.command.plist") + public void playerList(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); + if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { + sendMessage(issuer, Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED)); + return; + } + Set players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy); + Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW); + if (args.length >= 2 && args[1].equals("showall")) { + Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + Multimap human = HashMultimap.create(); + serverToPlayers.forEach((key, value) -> { + if (players.contains(value)) { + human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false)); + } + }); + for (String server : new TreeSet<>(human.keySet())) { + TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED); + TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW); + TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); + sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers)); + } + sendMessage(issuer, playersOnline); + } else { + sendMessage(issuer, playersOnline); + sendMessage(issuer, Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW)); + } + }); + + } + +} \ No newline at end of file diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/AdventureBaseCommand.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/AdventureBaseCommand.java new file mode 100644 index 0000000..873e8ee --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/AdventureBaseCommand.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.utils; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.CommandIssuer; +import net.kyori.adventure.text.Component; + +/** + * this just dumb class that wraps the adventure stuff into base command + */ +public abstract class AdventureBaseCommand extends BaseCommand { + + public static void sendMessage(CommandIssuer issuer, Component component) { + CommandPlatformHelper.getPlatformHelper().sendMessage(issuer, component); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java new file mode 100644 index 0000000..a951e9d --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.utils; + +import co.aikar.commands.CommandIssuer; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import net.kyori.adventure.text.Component; + + +public abstract class CommandPlatformHelper { + + private static CommandPlatformHelper SINGLETON; + + public abstract void sendMessage(CommandIssuer issuer, Component component); + + public static void init(CommandPlatformHelper platformHelper) { + if (SINGLETON != null) { + throw new IllegalStateException("tried to re init Platform Helper"); + } + SINGLETON = platformHelper; + } + + + public static CommandPlatformHelper getPlatformHelper() { + return SINGLETON; + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/StopperUUIDCleanupTask.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/StopperUUIDCleanupTask.java new file mode 100644 index 0000000..06dbe85 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/StopperUUIDCleanupTask.java @@ -0,0 +1,25 @@ +package com.imaginarycode.minecraft.redisbungee.commands.utils; + +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.tasks.UUIDCleanupTask; +import redis.clients.jedis.UnifiedJedis; + +public class StopperUUIDCleanupTask extends UUIDCleanupTask { + + public static boolean isRunning = false; + + public StopperUUIDCleanupTask(RedisBungeePlugin plugin) { + super(plugin); + } + + + @Override + public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { + isRunning = true; + try { + super.unifiedJedisTask(unifiedJedis); + } catch (Exception ignored) {} + isRunning = false; + return null; + } +} diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 4c2bbb4..fb9bc22 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -6,10 +6,6 @@ plugins { } -repositories { - mavenCentral() - maven { url = uri("https://repo.papermc.io/repository/maven-public/") } -} dependencies { api(project(":RedisBungee-API")) { @@ -17,9 +13,18 @@ dependencies { exclude("com.google.guava", "guava") exclude("com.google.code.gson", "gson") exclude("org.spongepowered", "configurate-yaml") + // exclude also adventure api + exclude("net.kyori", "adventure-api") + exclude("net.kyori", "adventure-text-serializer-gson") + exclude("net.kyori", "adventure-text-serializer-legacy") + exclude("net.kyori", "adventure-text-serializer-plain") + exclude("net.kyori", "adventure-text-minimessage") } - compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") - annotationProcessor("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") + compileOnly(libs.platform.velocity) + annotationProcessor(libs.platform.velocity) + implementation(project(":RedisBungee-Commands")) + implementation(libs.acf.velocity) + } description = "RedisBungee Velocity implementation" @@ -39,10 +44,12 @@ tasks { "https://jd.papermc.io/velocity/3.0.0/", // velocity api ) val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc") - options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) + options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) } runVelocity { velocityVersion("3.3.0-SNAPSHOT") + environment["REDISBUNGEE_PROXY_ID"] = "velocity-1" + environment["REDISBUNGEE_NETWORK_ID"] = "dev" } compileJava { options.encoding = Charsets.UTF_8.name() @@ -61,6 +68,9 @@ tasks { relocate("com.squareup.okhttp", "com.imaginarycode.minecraft.redisbungee.internal.okhttp") relocate("okio", "com.imaginarycode.minecraft.redisbungee.internal.okio") relocate("org.json", "com.imaginarycode.minecraft.redisbungee.internal.json") + relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") + // acf shade + relocate("co.aikar.commands", "com.imaginarycode.minecraft.redisbungee.internal.acf.commands") } } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java index 712ebba..2345f25 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java @@ -23,7 +23,7 @@ import java.util.UUID; * or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}. * * @author tuxed - * @since 0.2.3 | updated 0.8.0 + * @since 0.2.3 */ public class RedisBungeeAPI extends AbstractRedisBungeeAPI { diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java new file mode 100644 index 0000000..05e9264 --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; +import com.velocitypowered.api.event.proxy.ProxyPingEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerPing; +import net.kyori.adventure.text.Component; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap; +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset; + +public class RedisBungeeListener { + + private final RedisBungeePlugin plugin; + + public RedisBungeeListener(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + @Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last + public void onPing(ProxyPingEvent event) { + if (!plugin.configuration().handleMotd()) return; + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) return; + + ServerPing.Builder ping = event.getPing().asBuilder(); + ping.onlinePlayers(plugin.proxyDataManager().totalNetworkPlayers()); + event.setPing(ping.build()); + } + + @Subscribe + public void onPluginMessage(PluginMessageEvent event) { + if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) { + return; + } + + event.setResult(PluginMessageEvent.ForwardResult.handled()); + + plugin.executeAsync(() -> { + ByteArrayDataInput in = event.dataAsDataStream(); + + String subchannel = in.readUTF(); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + String type; + + switch (subchannel) { + case "PlayerList" -> { + out.writeUTF("PlayerList"); + Set original = Collections.emptySet(); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + original = plugin.proxyDataManager().networkPlayers(); + } else { + out.writeUTF(type); + try { + original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); + } catch (IllegalArgumentException ignored) { + } + } + Set players = original.stream() + .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false)) + .collect(Collectors.toSet()); + out.writeUTF(Joiner.on(',').join(players)); + } + case "PlayerCount" -> { + out.writeUTF("PlayerCount"); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + out.writeInt(plugin.proxyDataManager().totalNetworkPlayers()); + } else { + out.writeUTF(type); + try { + out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); + } catch (IllegalArgumentException e) { + out.writeInt(0); + } + } + } + case "LastOnline" -> { + String user = in.readUTF(); + out.writeUTF("LastOnline"); + out.writeUTF(user); + out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); + } + case "ServerPlayers" -> { + String type1 = in.readUTF(); + out.writeUTF("ServerPlayers"); + Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + boolean includesUsers; + switch (type1) { + case "COUNT" -> includesUsers = false; + case "PLAYERS" -> includesUsers = true; + default -> { + // TODO: Should I raise an error? + return; + } + } + out.writeUTF(type1); + if (includesUsers) { + Multimap human = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); + } + serializeMultimap(human, true, out); + } else { + serializeMultiset(multimap.keys(), out); + } + } + case "Proxy" -> { + out.writeUTF("Proxy"); + out.writeUTF(plugin.configuration().getProxyId()); + } + case "PlayerProxy" -> { + String username = in.readUTF(); + out.writeUTF("PlayerProxy"); + out.writeUTF(username); + out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); + } + default -> { + return; + } + } + + ((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); + }); + + } + + @Subscribe + public void onPlayerChooseInitialServerEvent(PlayerChooseInitialServerEvent event) { + if (plugin.configuration().handleReconnectToLastServer()) { + Player player = event.getPlayer(); + String lastServer = plugin.playerDataManager().getLastServerFor(player.getUniqueId()); + if (lastServer == null) return; + player.sendMessage(plugin.langConfiguration().messages().serverConnecting(player.getPlayerSettings().getLocale(), lastServer)); + Optional optionalRegisteredServer = ((RedisBungeeVelocityPlugin) plugin).getProxy().getServer(lastServer); + if (optionalRegisteredServer.isEmpty()) { + player.sendMessage(plugin.langConfiguration().messages().serverNotFound(player.getPlayerSettings().getLocale(), lastServer)); + return; + } + RegisteredServer server = optionalRegisteredServer.get(); + event.setInitialServer(server); + } + } + + +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java deleted file mode 100644 index f73bf88..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.google.common.base.Joiner; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.io.ByteArrayDataInput; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; -import com.imaginarycode.minecraft.redisbungee.api.AbstractRedisBungeeListener; -import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils; -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.velocitypowered.api.event.Continuation; -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.ResultedEvent; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.LoginEvent; -import com.velocitypowered.api.event.connection.PluginMessageEvent; -import com.velocitypowered.api.event.connection.PostLoginEvent; -import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult; -import com.velocitypowered.api.event.player.ServerConnectedEvent; -import com.velocitypowered.api.event.proxy.ProxyPingEvent; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; -import com.velocitypowered.api.proxy.server.ServerPing; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.*; -import java.util.stream.Collectors; - -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap; -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset; - -public class RedisBungeeVelocityListener extends AbstractRedisBungeeListener { - // Some messages are using legacy characters - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); - - public RedisBungeeVelocityListener(RedisBungeePlugin plugin, List exemptAddresses) { - super(plugin, exemptAddresses); - } - - @Subscribe(order = PostOrder.LAST) - public void onLogin(LoginEvent event, Continuation continuation) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - if (!event.getResult().isAllowed()) { - return null; - } - if (plugin.getConfiguration().restoreOldKickBehavior()) { - - for (String s : plugin.getProxiesIds()) { - if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) { - event.setResult(ResultedEvent.ComponentResult.denied(serializer.deserialize(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)))); - return null; - } - } - - } else if (api.isPlayerOnline(event.getPlayer().getUniqueId())) { - PlayerUtils.setKickedOtherLocation(event.getPlayer().getUniqueId().toString(), unifiedJedis); - api.kickPlayer(event.getPlayer().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); - } - return null; - } finally { - continuation.resume(); - } - } - - }); - } - - @Override - @Subscribe - public void onPostLogin(PostLoginEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), unifiedJedis); - VelocityPlayerUtils.createVelocityPlayer(event.getPlayer(), unifiedJedis, true); - return null; - } - }); - } - - @Override - @Subscribe - public void onPlayerDisconnect(DisconnectEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true); - return null; - } - - }); - - } - - @Override - @Subscribe - public void onServerChange(ServerConnectedEvent event) { - final String currentServer = event.getServer().getServerInfo().getName(); - final String oldServer = event.getPreviousServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", currentServer); - PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer); - return null; - } - }); - } - - @Override - @Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last - public void onPing(ProxyPingEvent event) { - if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) { - return; - } - ServerPing.Builder ping = event.getPing().asBuilder(); - ping.onlinePlayers(plugin.getCount()); - event.setPing(ping.build()); - } - - @Override - @Subscribe - public void onPluginMessage(PluginMessageEvent event) { - if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) { - return; - } - - event.setResult(ForwardResult.handled()); - - plugin.executeAsync(() -> { - ByteArrayDataInput in = event.dataAsDataStream(); - - String subchannel = in.readUTF(); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - String type; - - switch (subchannel) { - case "PlayerList": - out.writeUTF("PlayerList"); - Set original = Collections.emptySet(); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - original = plugin.getPlayers(); - } else { - out.writeUTF(type); - try { - original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); - } catch (IllegalArgumentException ignored) { - } - } - Set players = original.stream() - .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false)) - .collect(Collectors.toSet()); - out.writeUTF(Joiner.on(',').join(players)); - break; - case "PlayerCount": - out.writeUTF("PlayerCount"); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - out.writeInt(plugin.getCount()); - } else { - out.writeUTF(type); - try { - out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); - } catch (IllegalArgumentException e) { - out.writeInt(0); - } - } - break; - case "LastOnline": - String user = in.readUTF(); - out.writeUTF("LastOnline"); - out.writeUTF(user); - out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); - break; - case "ServerPlayers": - String type1 = in.readUTF(); - out.writeUTF("ServerPlayers"); - Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - - boolean includesUsers; - - switch (type1) { - case "COUNT": - includesUsers = false; - break; - case "PLAYERS": - includesUsers = true; - break; - default: - // TODO: Should I raise an error? - return; - } - - out.writeUTF(type1); - - if (includesUsers) { - Multimap human = HashMultimap.create(); - for (Map.Entry entry : multimap.entries()) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - serializeMultimap(human, true, out); - } else { - serializeMultiset(multimap.keys(), out); - } - break; - case "Proxy": - out.writeUTF("Proxy"); - out.writeUTF(plugin.getConfiguration().getProxyId()); - break; - case "PlayerProxy": - String username = in.readUTF(); - out.writeUTF("PlayerProxy"); - out.writeUTF(username); - out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); - break; - default: - return; - } - - ((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); - }); - - } - - - @Override - @Subscribe - public void onPubSubMessage(PubSubMessageEvent event) { - if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) { - String message = event.getMessage(); - if (message.startsWith("/")) - message = message.substring(1); - plugin.logInfo("Invoking command via PubSub: /" + message); - ((RedisBungeeVelocityPlugin) plugin).getProxy().getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), message); - - } - } -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index fe0d6c6..51edc96 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -10,24 +10,27 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; +import co.aikar.commands.VelocityCommandManager; import com.google.inject.Inject; -import com.imaginarycode.minecraft.redisbungee.api.*; -import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.commands.CommandLoader; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.LangConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.tasks.*; +import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; -import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; @@ -46,55 +49,62 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.scheduler.ScheduledTask; +import net.kyori.adventure.text.Component; import org.slf4j.Logger; -import redis.clients.jedis.*; import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.io.*; +import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.sql.Date; +import java.time.Duration; +import java.time.Instant; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; @Plugin(id = "redisbungee", name = "RedisBungee", version = Constants.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"}) -public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, ConfigLoader { +public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, ConfigLoader, LangConfigLoader { private final ProxyServer server; private final Logger logger; private final Path dataFolder; private final AbstractRedisBungeeAPI api; - private final PubSubListener psl; private Summoner jedisSummoner; private RedisBungeeMode redisBungeeMode; private final UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; - private final VelocityDataManager dataManager; + private LangConfiguration langConfiguration; private final OkHttpClient httpClient; - private volatile List proxiesIds; - private final AtomicInteger globalPlayerCount = new AtomicInteger(); - private ScheduledTask integrityCheck; + + private final ProxyDataManager proxyDataManager; + + private final VelocityPlayerDataManager playerDataManager; + + private ScheduledTask cleanUpTask; private ScheduledTask heartbeatTask; - private static final Object SERVER_TO_PLAYERS_KEY = new Object(); public static final List IDENTIFIERS = List.of( MinecraftChannelIdentifier.create("legacy", "redisbungee"), new LegacyChannelIdentifier("RedisBungee"), // This is needed for clients before 1.13 new LegacyChannelIdentifier("legacy:redisbungee") ); - private final Cache> serverToPlayersCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.SECONDS) - .build(); + private VelocityCommandManager commandManager; @Inject public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { this.server = server; this.logger = logger; this.dataFolder = dataDirectory; + logInfo("Version: {}", Constants.VERSION); try { loadConfig(this, dataDirectory); + loadLangConfig(this, dataDirectory); } catch (IOException e) { throw new RuntimeException("Unable to load/save config", e); } catch (JedisConnectionException e) { @@ -102,11 +112,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } this.api = new RedisBungeeAPI(this); InitialUtils.checkRedisVersion(this); - // check if this proxy is recovering from a crash and start heart the beat. - InitialUtils.checkIfRecovering(this, getDataFolder()); + this.proxyDataManager = new ProxyDataManager(this) { + @Override + public Set getLocalOnlineUUIDs() { + HashSet players = new HashSet<>(); + server.getAllPlayers().forEach(player -> players.add(player.getUniqueId())); + return players; + } + + @Override + protected void handlePlatformCommandExecution(String command) { + server.getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), command); + } + }; + this.playerDataManager = new VelocityPlayerDataManager(this); uuidTranslator = new UUIDTranslator(this); - dataManager = new VelocityDataManager(this); - psl = new PubSubListener(this); this.httpClient = new OkHttpClient(); Dispatcher dispatcher = new Dispatcher(Executors.newFixedThreadPool(6)); this.httpClient.setDispatcher(dispatcher); @@ -115,31 +135,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } - @Override - public RedisBungeeConfiguration getConfiguration() { - return this.configuration; - } - - @Override - public int getCount() { - return this.globalPlayerCount.get(); - } - - - @Override - public Set getLocalPlayersAsUuidStrings() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Player player : getProxy().getAllPlayers()) { - builder.add(player.getUniqueId().toString()); - } - return builder.build(); - } - - @Override - public AbstractDataManager getDataManager() { - return this.dataManager; - } - @Override public Summoner getSummoner() { return this.jedisSummoner; @@ -150,29 +145,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.api; } + @Override + public ProxyDataManager proxyDataManager() { + return this.proxyDataManager; + } + + @Override + public PlayerDataManager playerDataManager() { + return this.playerDataManager; + } + @Override public UUIDTranslator getUuidTranslator() { return this.uuidTranslator; } - @Override - public Multimap serverToPlayersCache() { - try { - return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getProxiesIds() { - return proxiesIds; - } - - @Override - public PubSubListener getPubSubListener() { - return this.psl; - } @Override public void executeAsync(Runnable runnable) { @@ -199,16 +186,41 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con this.getLogger().info(msg); } + @Override + public void logInfo(String format, Object... object) { + logger.info(format, object); + } + @Override public void logWarn(String msg) { this.getLogger().warn(msg); } + @Override + public void logWarn(String format, Object... object) { + logger.warn(format, object); + } + @Override public void logFatal(String msg) { this.getLogger().error(msg); } + @Override + public void logFatal(String format, Throwable throwable) { + logger.error(format, throwable); + } + + @Override + public RedisBungeeConfiguration configuration() { + return this.configuration; + } + + @Override + public LangConfiguration langConfiguration() { + return this.langConfiguration; + } + @Override public Player getPlayer(UUID uuid) { return this.getProxy().getPlayer(uuid).orElse(null); @@ -229,6 +241,15 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null); } + + @Override + public boolean handlePlatformKick(UUID uuid, Component message) { + Player player = getPlayer(uuid); + if (player == null) return false; + player.disconnect(message); + return true; + } + @Override public String getPlayerServerName(Player player) { return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null); @@ -247,44 +268,25 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con @Override public void initialize() { logInfo("Initializing RedisBungee....."); - updateProxiesIds(); // start heartbeat task - heartbeatTask = getProxy().getScheduler().buildTask(this, new HeartbeatTask(this, this.globalPlayerCount)).repeat(HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT).schedule(); + // heartbeat and clean up + this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule(); + this.cleanUpTask = server.getScheduler().buildTask(this, this.proxyDataManager::correctionTask).repeat(Duration.ofSeconds(60)).schedule(); - getProxy().getEventManager().register(this, new RedisBungeeVelocityListener(this, configuration.getExemptAddresses())); - getProxy().getEventManager().register(this, dataManager); - getProxy().getScheduler().buildTask(this, psl).schedule(); - - IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) { - @Override - public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) { - Player playerProxied = getProxy().getPlayer(UUID.fromString(player)).orElse(null); - if (playerProxied == null) - return; // We'll deal with it later. - VelocityPlayerUtils.createVelocityPlayer(playerProxied, unifiedJedis, false); - } - }; - integrityCheck = getProxy().getScheduler().buildTask(this, integrityCheckTask::execute).repeat(30, TimeUnit.SECONDS).schedule(); + server.getEventManager().register(this, this.playerDataManager); + server.getEventManager().register(this, new RedisBungeeListener(this)); + // subscribe + server.getScheduler().buildTask(this, this.proxyDataManager).schedule(); // register plugin messages IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register); - // register legacy commands - if (configuration.doRegisterLegacyCommands()) { - // Override Velocity commands - if (configuration.doOverrideBungeeCommands()) { - getProxy().getCommandManager().register("glist", new RedisBungeeCommands.GlistCommand(this), "redisbungee", "rglist"); - } - getProxy().getCommandManager().register("sendtoall", new RedisBungeeCommands.SendToAll(this), "rsendtoall"); - getProxy().getCommandManager().register("serverid", new RedisBungeeCommands.ServerId(this), "rserverid"); - getProxy().getCommandManager().register("serverids", new RedisBungeeCommands.ServerIds(this)); - getProxy().getCommandManager().register("pproxy", new RedisBungeeCommands.PlayerProxyCommand(this)); - getProxy().getCommandManager().register("plist", new RedisBungeeCommands.PlistCommand(this), "rplist"); - getProxy().getCommandManager().register("lastseen", new RedisBungeeCommands.LastSeenCommand(this), "rlastseen"); - getProxy().getCommandManager().register("ip", new RedisBungeeCommands.IpCommand(this), "playerip", "rip", "rplayerip"); - getProxy().getCommandManager().register("find", new RedisBungeeCommands.FindCommand(this), "rfind"); - } + // load commands + CommandPlatformHelper.init(new VelocityCommandPlatformHelper()); + this.commandManager = new VelocityCommandManager(this.getProxy(), this); + CommandLoader.initCommands(this.commandManager, this); + logInfo("RedisBungee initialized successfully "); } @@ -292,19 +294,18 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con public void stop() { logInfo("Turning off redis connections....."); // Poison the PubSub listener - if (psl != null) { - psl.poison(); - } - if (integrityCheck != null) { - integrityCheck.cancel(); + if (cleanUpTask != null) { + cleanUpTask.cancel(); } if (heartbeatTask != null) { heartbeatTask.cancel(); } - ShutdownUtils.shutdownCleanup(this); + + try { + this.proxyDataManager.close(); this.jedisSummoner.close(); - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException(e); } @@ -315,6 +316,7 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } catch (InterruptedException e) { throw new RuntimeException(e); } + if (commandManager != null) commandManager.unregisterCommands(); logInfo("RedisBungee shutdown complete"); } @@ -325,16 +327,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con this.redisBungeeMode = mode; } + @Override + public void onLangConfigLoad(LangConfiguration langConfiguration) { + this.langConfiguration = langConfiguration; + } @Override public RedisBungeeMode getRedisBungeeMode() { return this.redisBungeeMode; } - @Override - public void updateProxiesIds() { - this.proxiesIds = this.getCurrentProxiesIds(false); - } @Subscribe(order = PostOrder.FIRST) public void onProxyInitializeEvent(ProxyInitializeEvent event) { diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityCommandPlatformHelper.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityCommandPlatformHelper.java new file mode 100644 index 0000000..07cd9de --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityCommandPlatformHelper.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.VelocityCommandIssuer; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; +import net.kyori.adventure.text.Component; + +public class VelocityCommandPlatformHelper extends CommandPlatformHelper { + + @Override + public void sendMessage(CommandIssuer issuer, Component component) { + VelocityCommandIssuer vIssuer = (VelocityCommandIssuer) issuer; + vIssuer.getIssuer().sendMessage(component); + } + +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java deleted file mode 100644 index 4ad9ef3..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.PostLoginEvent; -import com.velocitypowered.api.proxy.Player; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - -import java.util.UUID; - - -public class VelocityDataManager extends AbstractDataManager { - - public VelocityDataManager(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - @Subscribe - public void onPostLogin(PostLoginEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @Subscribe - public void onPlayerDisconnect(DisconnectEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @Subscribe - public void onPubSubMessage(PubSubMessageEvent event) { - handlePubSubMessage(event.getChannel(), event.getMessage()); - } - - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); - @Override - public boolean handleKick(UUID target, String message) { - Player player = plugin.getPlayer(target); - if (player == null) { - return false; - } - player.disconnect(serializer.deserialize(message)); - return true; - } -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java new file mode 100644 index 0000000..ea89d90 --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.Component; + +import java.util.concurrent.TimeUnit; + +public class VelocityPlayerDataManager extends PlayerDataManager { + public VelocityPlayerDataManager(RedisBungeePlugin plugin) { + super(plugin); + } + + @Override + @Subscribe + public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) { + handleNetworkPlayerServerChange(event); + } + + @Override + @Subscribe + public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { + handleNetworkPlayerQuit(event); + } + + @Override + @Subscribe + public void onPubSubMessageEvent(PubSubMessageEvent event) { + handlePubSubMessageEvent(event); + } + + @Override + @Subscribe + public void onServerConnectedEvent(ServerConnectedEvent event) { + final String currentServer = event.getServer().getServerInfo().getName(); + final String oldServer; + if (event.getPreviousServer().isPresent()) { + oldServer = event.getPreviousServer().get().getServerInfo().getName(); + } else { + oldServer = null; + } + super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); + } + + @Subscribe + public void onLoginEvent(LoginEvent event, Continuation continuation) { + // check if online + if (getLastOnline(event.getPlayer().getUniqueId()) == 0) { + if (plugin.configuration().kickWhenOnline()) { + kickPlayer(event.getPlayer().getUniqueId(), plugin.langConfiguration().messages().loggedInFromOtherLocation()); + // wait 3 seconds before releasing the event + plugin.executeAsyncAfter(continuation::resume, TimeUnit.SECONDS, 3); + } else { + event.setResult(ResultedEvent.ComponentResult.denied(plugin.langConfiguration().messages().alreadyLoggedIn())); + continuation.resume(); + } + } else { + continuation.resume(); + } + } + + @Override + @Subscribe + public void onLoginEvent(PostLoginEvent event) { + addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getRemoteAddress().getAddress()); + } + + @Override + @Subscribe + public void onDisconnectEvent(DisconnectEvent event) { + if (event.getLoginStatus() == DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN || event.getLoginStatus() == DisconnectEvent.LoginStatus.PRE_SERVER_JOIN) { + removePlayer(event.getPlayer().getUniqueId()); + } + } +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java deleted file mode 100644 index 2f43fc8..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; -import redis.clients.jedis.UnifiedJedis; - -import java.util.Optional; - -public class VelocityPlayerUtils { - protected static void createVelocityPlayer(Player player, UnifiedJedis unifiedJedis, boolean fireEvent) { - Optional optionalServerConnection = player.getCurrentServer(); - String serverName = null; - if (optionalServerConnection.isPresent()) { - serverName = optionalServerConnection.get().getServerInfo().getName(); - } - PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, player.getRemoteAddress().getAddress(), fireEvent); - } - - -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java deleted file mode 100644 index 3537981..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.commands; - -import java.net.InetAddress; -import java.text.SimpleDateFormat; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - -import com.google.common.base.Joiner; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import com.imaginarycode.minecraft.redisbungee.RedisBungeeVelocityPlugin; -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.command.SimpleCommand; -import com.velocitypowered.api.proxy.server.RegisteredServer; -import com.velocitypowered.api.proxy.server.ServerInfo; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.format.NamedTextColor; - - -/** - * This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen. - *

- * All classes use the {@link AbstractRedisBungeeAPI}. - * - * @author tuxed - * @since 0.2.3 - */ -public class RedisBungeeCommands { - - private static final Component NO_PLAYER_SPECIFIED = - Component.text("You must specify a player name.", NamedTextColor.RED); - private static final Component PLAYER_NOT_FOUND = - Component.text("No such player found.", NamedTextColor.RED); - private static final Component NO_COMMAND_SPECIFIED = - Component.text("You must specify a command to be run.", NamedTextColor.RED); - - private static String playerPlural(int num) { - return num == 1 ? num + " player is" : num + " players are"; - } - - public static class GlistCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public GlistCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - int count = plugin.getAbstractRedisBungeeApi().getPlayerCount(); - Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW); - CommandSource sender = invocation.source(); - if (invocation.arguments().length > 0 && invocation.arguments()[0].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - serverToPlayers.forEach((key, value) -> { - // if for any reason UUID translation fails just return the uuid as name, to make command finish executing. - String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false); - human.put(key, playerName != null ? playerName : value.toString()); - }); - for (String server : new TreeSet<>(serverToPlayers.keySet())) { - Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN); - Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW); - Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); - sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers)); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW)); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("velocity.command.server"); - } - } - - public static class FindCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public FindCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String[] args = invocation.arguments(); - CommandSource sender = invocation.source(); - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - ServerInfo si = plugin.getProxy().getServer(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid)).map(RegisteredServer::getServerInfo).orElse(null); - if (si != null) { - Component message = Component.text(args[0] + " is on " + si.getName() + ".", NamedTextColor.BLUE); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.find"); - } - } - - public static class LastSeenCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public LastSeenCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String[] args = invocation.arguments(); - CommandSource sender = invocation.source(); - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid); - TextComponent.Builder message = Component.text(); - if (secs == 0) { - message.color(NamedTextColor.GREEN); - message.content(args[0] + " is currently online."); - } else if (secs != -1) { - message.color(NamedTextColor.BLUE); - message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); - } else { - message.color(NamedTextColor.RED); - message.content(args[0] + " has never been online."); - } - sender.sendMessage(message.build()); - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.lastseen"); - } - } - - public static class IpCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public IpCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - CommandSource sender = invocation.source(); - String[] args = invocation.arguments(); - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid); - if (ia != null) { - TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.ip"); - } - } - - public static class PlayerProxyCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public PlayerProxyCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - CommandSource sender = invocation.source(); - String[] args = invocation.arguments(); - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid); - if (proxy != null) { - TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.pproxy"); - } - } - - public static class SendToAll implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public SendToAll(RedisBungeeVelocityPlugin plugin) { - //super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall"); - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - String[] args = invocation.arguments(); - CommandSource sender = invocation.source(); - if (args.length > 0) { - String command = Joiner.on(" ").skipNulls().join(args); - plugin.getAbstractRedisBungeeApi().sendProxyCommand(command); - TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN); - sender.sendMessage(message); - } else { - sender.sendMessage(NO_COMMAND_SPECIFIED); - } - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.sendtoall"); - } - } - - public static class ServerId implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public ServerId(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(Invocation invocation) { - invocation.source().sendMessage(Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.serverid"); - } - } - - public static class ServerIds implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public ServerIds(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(Invocation invocation) { - invocation.source().sendMessage( - Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.serverids"); - } - } - - public static class PlistCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public PlistCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(Invocation invocation) { - CommandSource sender = invocation.source(); - String[] args = invocation.arguments(); - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId(); - if (!plugin.getProxiesIds().contains(proxy)) { - sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED)); - return; - } - Set players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy); - Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW); - if (args.length >= 2 && args[1].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - serverToPlayers.forEach((key, value) -> { - if (players.contains(value)) { - human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false)); - } - }); - for (String server : new TreeSet<>(human.keySet())) { - TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED); - TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW); - TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); - sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers)); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW)); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.plist"); - } - } - -} \ No newline at end of file diff --git a/gradle.build.kts b/build.gradle.kts similarity index 100% rename from gradle.build.kts rename to build.gradle.kts diff --git a/gradle.properties b/gradle.properties index 5440772..18ec268 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -group = com.imaginarycode.minecraft -version = 0.11.4-SNAPSHOT +group=com.imaginarycode.minecraft +version=0.12.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a7..d64cd49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..1af9e09 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cb..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/settings.gradle.kts b/settings.gradle.kts index 181ac78..f3ff03f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,5 +7,70 @@ pluginManagement { rootProject.name = "RedisBungee-Parent" include(":RedisBungee-Velocity") +include(":RedisBungee-Commands") include(":RedisBungee-Bungee") include(":RedisBungee-API") + +dependencyResolutionManagement { + repositories { + mavenCentral() + maven { + name = "PaperMC" + url = uri("https://repo.papermc.io/repository/maven-public/") + } + maven { + // hosts the bungeecord apis + name = "sonatype" + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + } + maven { + name = "aikar repo" + url = uri("https://repo.aikar.co/content/groups/aikar/") + } + + } + versionCatalogs { + val jedisVersion = "5.1.2" + val configurateVersion = "3.7.3" + val guavaVersion = "31.1-jre" + val okHttpVersion = "2.7.5" + val caffeineVersion = "3.1.8" + val adventureVersion = "4.16.0" + val acf = "0.5.1-SNAPSHOT" + val bungeecordApiVersion = "1.20-R0.1-SNAPSHOT" + val velocityVersion = "3.3.0-SNAPSHOT"; + + + create("libs") { + + library("guava", "com.google.guava:guava:$guavaVersion") + library("jedis", "redis.clients:jedis:$jedisVersion") + library("okhttp", "com.squareup.okhttp:okhttp:$okHttpVersion") + library("configurate", "org.spongepowered:configurate-yaml:$configurateVersion") + library("caffeine", "com.github.ben-manes.caffeine:caffeine:$caffeineVersion") + + library("adventure-api", "net.kyori:adventure-api:$adventureVersion") + library("adventure-gson", "net.kyori:adventure-text-serializer-gson:$adventureVersion") + library("adventure-legacy", "net.kyori:adventure-text-serializer-legacy:$adventureVersion") + library("adventure-plain", "net.kyori:adventure-text-serializer-plain:$adventureVersion") + library("adventure-miniMessage", "net.kyori:adventure-text-minimessage:$adventureVersion") + + library("acf-core", "co.aikar:acf-core:$acf") + library("acf-bungeecord", "co.aikar:acf-bungee:$acf") + library("acf-velocity", "co.aikar:acf-velocity:$acf") + + library("platform-bungeecord","net.md-5:bungeecord-api:$bungeecordApiVersion") + library("adventure-platforms-bungeecord", "net.kyori:adventure-platform-bungeecord:4.3.2") + + library("platform-velocity", "com.velocitypowered:velocity-api:$velocityVersion") + + + + + } + + + } + + +} \ No newline at end of file