2
0
mirror of https://github.com/proxiodev/RedisBungee.git synced 2026-04-02 21:20:47 +00:00

make RedisBungee projects compilable

still same 0.13.0 core nothing changed yet but with formatting appiled
This commit is contained in:
Mohammed Alteneiji 2026-04-01 15:58:59 +04:00
parent 88ef7822b9
commit e4fe6014ac
Signed by: ham1255
GPG Key ID: EF343502046229F4
95 changed files with 4969 additions and 4613 deletions

View File

@ -29,14 +29,14 @@ jobs:
# Artifact name # Artifact name
name: RedisBungee-Bungee name: RedisBungee-Bungee
# Destination path # Destination path
path: proxies/bungeecord/build/libs/* path: redisbungee/proxies/bungeecord/build/libs/*
- name: Upload Velocity - name: Upload Velocity
uses: actions/upload-artifact@v4.4.0 uses: actions/upload-artifact@v4.4.0
with: with:
name: RedisBungee-Velocity name: RedisBungee-Velocity
path: proxies/velocity/build/libs/* path: redisbungee/proxies/velocity/build/libs/*
- name: Upload API - name: Upload API
uses: actions/upload-artifact@v4.4.0 uses: actions/upload-artifact@v4.4.0
with: with:
name: RedisBungee-API name: RedisBungee-API
path: api/build/libs/* path: redisbungee/api/build/libs/*

View File

@ -25,11 +25,14 @@ subprojects {
} }
} }
extensions.configure<com.diffplug.gradle.spotless.SpotlessExtension> { extensions.configure<com.diffplug.gradle.spotless.SpotlessExtension> {
var redisBungeeProjects = sequenceOf("RedisBungee-API", "RedisBungee-Lang", "RedisBungee-Commands", "RedisBungee-Bungee", "RedisBungee-Proxy-Bungee", "RedisBungee-Velocity", "RedisBungee-Proxy-Velocity")
java { java {
removeUnusedImports() removeUnusedImports()
googleJavaFormat() googleJavaFormat()
if (project.name == "valiobungee-api") { if (project.name == "valiobungee-api") {
licenseHeaderFile(file("copyright_header.txt")) licenseHeaderFile(file("copyright_header.txt"))
} else if (redisBungeeProjects.contains(project.name)) {
licenseHeaderFile(rootProject.file("redisbungee/copyright_header.txt"))
} else { } else {
licenseHeaderFile(rootProject.file("copyright_header.txt")) licenseHeaderFile(rootProject.file("copyright_header.txt"))
} }

View File

@ -1,3 +1,6 @@
group=net.limework group=net.limework
version=1.0.0-SNAPSHOT version=1.0.0-SNAPSHOT
api-version=v1 api-version=v1
redisbungee-group=com.imaginarycode.minecraft
redisbungee-version=0.13.0-SNAPSHOT

View File

@ -3,22 +3,52 @@ protobuf-plugin = "0.9.5"
protobuf = "3.25.8" # needed for reference to be used for protoc protobuf = "3.25.8" # needed for reference to be used for protoc
slf4j = "2.0.17" slf4j = "2.0.17"
guava = "33.5.0-jre"
jedis = "5.2.0"
okhttp = "4.12.0"
configurateV3 = "3.7.3"
caffeine = "3.2.3"
adventure = "4.26.1"
adventure-bungeecord-platform = "4.4.1"
acf = "e2005dd62d"
bungeecordApi = "1.21-R0.5-SNAPSHOT"
velocity = "3.5.0-SNAPSHOT"
[plugins] [plugins]
blossom = "net.kyori.blossom:2.2.0" blossom = "net.kyori.blossom:2.2.0"
indragit = "net.kyori.indra.git:4.0.0" indragit = "net.kyori.indra.git:4.0.0"
shadow = "com.gradleup.shadow:9.3.1" shadow = "com.gradleup.shadow:9.3.1"
spotless = "com.diffplug.spotless:8.2.0" spotless = "com.diffplug.spotless:8.2.0"
protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" }
run-velocity = { id = "xyz.jpenilla.run-velocity", version = "2.3.1" }
[libraries] [libraries]
redisson = "org.redisson:redisson:4.3.0" # protobuf
protobuf = { group = "com.google.protobuf", name = "protobuf-java", version.ref = "protobuf" } protobuf = { group = "com.google.protobuf", name = "protobuf-java", version.ref = "protobuf" }
protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.3"
# valiobungee
redisson = "org.redisson:redisson:4.3.0"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.3"
# logging # logging
slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
# testing # testing
testing-juipter = "org.junit.jupiter:junit-jupiter:6.0.3" testing-juipter = "org.junit.jupiter:junit-jupiter:6.0.3"
testing-slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } testing-slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
# redisbungee speific
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jedis = { module = "redis.clients:jedis", version.ref = "jedis" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
configurateV3 = { module = "org.spongepowered:configurate-yaml", version.ref = "configurateV3" }
# minecraft speific
adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" }
adventure-miniMessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" }
acf-core = { module = "com.github.ProxioDev.commands:acf-core", version.ref = "acf" }
acf-bungeecord = { module = "com.github.ProxioDev.commands:acf-bungee", version.ref = "acf" }
acf-velocity = { module = "com.github.ProxioDev.commands:acf-velocity", version.ref = "acf" }
platform-bungeecord = { module = "net.md-5:bungeecord-api", version.ref = "bungeecordApi" }
adventure-platforms-bungeecord = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-bungeecord-platform" }
platform-velocity = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" }

View File

@ -1,5 +1,3 @@
import java.io.ByteArrayOutputStream
plugins { plugins {
`java-library` `java-library`
`maven-publish` `maven-publish`
@ -8,6 +6,10 @@ plugins {
} }
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies { dependencies {
api(libs.guava) api(libs.guava)
api(libs.jedis) api(libs.jedis)
@ -22,8 +24,8 @@ sourceSets {
main { main {
blossom { blossom {
javaSources { javaSources {
property("version", "$version") property("version", version.toString())
property("git-commit", indraGit.commit().toString()) property("git", indraGit.commit().get().name)
} }
} }
} }

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) 2013-2013 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;
public class Constants {
public static final String VERSION = "{{ version }}";
public static final String GIT_COMMIT = "{{ git }}";
public static String getGithubCommitLink() {
return "https://github.com/ProxioDev/ValioBungee/commit/" + GIT_COMMIT;
}
}

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -15,13 +14,12 @@ import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import java.net.InetAddress;
import java.util.*;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
import java.net.InetAddress;
import java.util.*;
/** /**
* This abstract class is extended by platform plugin to provide some platform specific methods. * This abstract class is extended by platform plugin to provide some platform specific methods.
* overall its general contains all methods needed by external usage. * overall its general contains all methods needed by external usage.
@ -31,309 +29,321 @@ import java.util.*;
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public abstract class AbstractRedisBungeeAPI { public abstract class AbstractRedisBungeeAPI {
protected final RedisBungeePlugin<?> plugin; protected final RedisBungeePlugin<?> plugin;
private static AbstractRedisBungeeAPI abstractRedisBungeeAPI; private static AbstractRedisBungeeAPI abstractRedisBungeeAPI;
public AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) { public AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
// this does make sure that no one can replace first initiated API class. // this does make sure that no one can replace first initiated API class.
if (abstractRedisBungeeAPI == null) { if (abstractRedisBungeeAPI == null) {
abstractRedisBungeeAPI = this; abstractRedisBungeeAPI = this;
}
this.plugin = plugin;
} }
this.plugin = plugin;
}
/** /**
* Get a combined count of all players on this network. * Get a combined count of all players on this network.
* *
* @return a count of all players found * @return a count of all players found
*/ */
public final int getPlayerCount() { public final int getPlayerCount() {
return plugin.proxyDataManager().totalNetworkPlayers(); return plugin.proxyDataManager().totalNetworkPlayers();
}
/**
* Get the last time a player was on. If the player is currently online, this will return 0. If
* the player has not been recorded, this will return -1. Otherwise it will return a value in
* milliseconds.
*
* @param player a player name
* @return the last time a player was on, if online returns a 0
*/
public final long getLastOnline(@NonNull UUID player) {
return plugin.playerDataManager().getLastOnline(player);
}
/**
* Get the server where the specified player is playing. This function also deals with the case of
* local players as well, and will return local information on them.
*
* @param player a player uuid
* @return a String name for the server the player is on. Can be Null if plugins is doing weird
* stuff to the proxy internals
*/
@Nullable
public final String getServerNameFor(@NonNull UUID player) {
return plugin.playerDataManager().getServerFor(player);
}
/**
* Get a combined list of players on this network.
*
* <p><strong>Note that this function returns an instance of {@link
* com.google.common.collect.ImmutableSet}.</strong>
*
* @return a Set with all players found
*/
public final Set<UUID> getPlayersOnline() {
return plugin.proxyDataManager().networkPlayers();
}
/**
* Get a combined list of players on this network, as a collection of usernames.
*
* @return a Set with all players found
* @see #getNameFromUuid(java.util.UUID)
* @since 0.3
*/
public final Collection<String> getHumanPlayersOnline() {
Set<String> names = new HashSet<>();
for (UUID uuid : getPlayersOnline()) {
names.add(getNameFromUuid(uuid, false));
} }
return names;
}
/** /**
* Get the last time a player was on. If the player is currently online, this will return 0. If the player has not been recorded, * Get a full list of players on all servers.
* this will return -1. Otherwise it will return a value in milliseconds. *
* * @return a immutable Multimap with all players found on this network
* @param player a player name * @since 0.2.5
* @return the last time a player was on, if online returns a 0 */
*/ public final Multimap<String, UUID> getServerToPlayers() {
public final long getLastOnline(@NonNull UUID player) { return plugin.playerDataManager().serversToPlayers();
return plugin.playerDataManager().getLastOnline(player); }
}
/** /**
* Get the server where the specified player is playing. This function also deals with the case of local players * Get a list of players on the server with the given name.
* as well, and will return local information on them. *
* * @param server a server name
* @param player a player uuid * @return a Set with all players found on this server
* @return a String name for the server the player is on. Can be Null if plugins is doing weird stuff to the proxy internals */
*/ public final Set<UUID> getPlayersOnServer(@NonNull String server) {
@Nullable return ImmutableSet.copyOf(getServerToPlayers().get(server));
public final String getServerNameFor(@NonNull UUID player) { }
return plugin.playerDataManager().getServerFor(player);
}
/** /**
* Get a combined list of players on this network. * Get a list of players on the specified proxy.
* <p> *
* <strong>Note that this function returns an instance of {@link com.google.common.collect.ImmutableSet}.</strong> * @param proxyID proxy id
* * @return a Set with all UUIDs found on this proxy
* @return a Set with all players found */
*/ public final Set<UUID> getPlayersOnProxy(@NonNull String proxyID) {
public final Set<UUID> getPlayersOnline() { return plugin.proxyDataManager().getPlayersOn(proxyID);
return plugin.proxyDataManager().networkPlayers(); }
}
/** /**
* Get a combined list of players on this network, as a collection of usernames. * Convenience method: Checks if the specified player is online.
* *
* @return a Set with all players found * @param player a player name
* @see #getNameFromUuid(java.util.UUID) * @return if the player is online
* @since 0.3 */
*/ public final boolean isPlayerOnline(@NonNull UUID player) {
public final Collection<String> getHumanPlayersOnline() { return getLastOnline(player) == 0;
Set<String> names = new HashSet<>(); }
for (UUID uuid : getPlayersOnline()) {
names.add(getNameFromUuid(uuid, false));
}
return names;
}
/** /**
* Get a full list of players on all servers. * Get the {@link java.net.InetAddress} associated with this player.
* *
* @return a immutable Multimap with all players found on this network * @param player the player to fetch the IP for
* @since 0.2.5 * @return an {@link java.net.InetAddress} if the player is online, null otherwise
*/ * @since 0.2.4
public final Multimap<String, UUID> getServerToPlayers() { */
return plugin.playerDataManager().serversToPlayers(); public final InetAddress getPlayerIp(@NonNull UUID player) {
} return plugin.playerDataManager().getIpFor(player);
}
/** /**
* Get a list of players on the server with the given name. * Get the RedisBungee proxy ID this player is connected to.
* *
* @param server a server name * @param player the player to fetch the IP for
* @return a Set with all players found on this server * @return the proxy the player is connected to, or null if they are offline
*/ * @since 0.3.3
public final Set<UUID> getPlayersOnServer(@NonNull String server) { */
return ImmutableSet.copyOf(getServerToPlayers().get(server)); public final String getProxy(@NonNull UUID player) {
} return plugin.playerDataManager().getProxyFor(player);
}
/** /**
* Get a list of players on the specified proxy. * Sends a proxy command to all proxies.
* *
* @param proxyID proxy id * @param command the command to send and execute
* @return a Set with all UUIDs found on this proxy * @see #sendProxyCommand(String, String)
*/ * @since 0.2.5
public final Set<UUID> getPlayersOnProxy(@NonNull String proxyID) { */
return plugin.proxyDataManager().getPlayersOn(proxyID); public final void sendProxyCommand(@NonNull String command) {
} sendProxyCommand("allservers", command);
}
/** /**
* Convenience method: Checks if the specified player is online. * Sends a proxy command to the proxy with the given ID. "allservers" means all proxies.
* *
* @param player a player name * @param proxyId a proxy ID
* @return if the player is online * @param command the command to send and execute
*/ * @see #getProxyId()
public final boolean isPlayerOnline(@NonNull UUID player) { * @see #getAllProxies()
return getLastOnline(player) == 0; * @since 0.2.5
} */
public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) {
plugin.proxyDataManager().sendCommandTo(proxyId, command);
}
/** /**
* Get the {@link java.net.InetAddress} associated with this player. * Sends a message to a PubSub channel which makes PubSubMessageEvent fire.
* *
* @param player the player to fetch the IP for * <p>Note: Since 0.12.0 registering a channel api is no longer required
* @return an {@link java.net.InetAddress} if the player is online, null otherwise *
* @since 0.2.4 * @param channel The PubSub channel
*/ * @param message the message body to send
public final InetAddress getPlayerIp(@NonNull UUID player) { * @since 0.3.3
return plugin.playerDataManager().getIpFor(player); */
} public final void sendChannelMessage(@NonNull String channel, @NonNull String message) {
plugin.proxyDataManager().sendChannelMessage(channel, message);
}
/** /**
* Get the RedisBungee proxy ID this player is connected to. * Get the current BungeeCord / Velocity proxy ID for this server.
* *
* @param player the player to fetch the IP for * @return the current server ID
* @return the proxy the player is connected to, or null if they are offline * @see #getAllProxies()
* @since 0.3.3 * @since 0.8.0
*/ */
public final String getProxy(@NonNull UUID player) { public final String getProxyId() {
return plugin.playerDataManager().getProxyFor(player); return plugin.proxyDataManager().proxyId();
} }
/** /**
* Sends a proxy command to all proxies. * Get all the linked proxies in this network.
* *
* @param command the command to send and execute * @return the list of all proxies
* @see #sendProxyCommand(String, String) * @see #getProxyId()
* @since 0.2.5 * @since 0.8.0
*/ */
public final void sendProxyCommand(@NonNull String command) { public final List<String> getAllProxies() {
sendProxyCommand("allservers", command); return plugin.proxyDataManager().proxiesIds();
} }
/** /**
* Sends a proxy command to the proxy with the given ID. "allservers" means all proxies. * Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function
* * falls back to Mojang as a last resort, so calls <strong>may</strong> be blocking.
* @param proxyId a proxy ID *
* @param command the command to send and execute * <p>For the common use case of translating a list of UUIDs into names, use {@link
* @see #getProxyId() * #getHumanPlayersOnline()} instead.
* @see #getAllProxies() *
* @since 0.2.5 * <p>If performance is a concern, use {@link #getNameFromUuid(java.util.UUID, boolean)} as this
*/ * allows you to disable Mojang lookups.
public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) { *
plugin.proxyDataManager().sendCommandTo(proxyId, command); * @param uuid the UUID to fetch the name for
} * @return the name for the UUID
* @since 0.3
*/
public final String getNameFromUuid(@NonNull UUID uuid) {
return getNameFromUuid(uuid, true);
}
/** /**
* Sends a message to a PubSub channel which makes PubSubMessageEvent fire. * Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function can
* <p> * fall back to Mojang as a last resort if {@code expensiveLookups} is true, so calls
* Note: Since 0.12.0 registering a channel api is no longer required * <strong>may</strong> be blocking.
* *
* @param channel The PubSub channel * <p>For the common use case of translating the list of online players into names, use {@link
* @param message the message body to send * #getHumanPlayersOnline()}.
* @since 0.3.3 *
*/ * <p>If performance is a concern, set {@code expensiveLookups} to false as this will disable
public final void sendChannelMessage(@NonNull String channel, @NonNull String message) { * lookups via Mojang.
plugin.proxyDataManager().sendChannelMessage(channel, message); *
} * @param uuid the UUID to fetch the name for
* @param expensiveLookups whether or not to perform potentially expensive lookups
* @return the name for the UUID
* @since 0.3.2
*/
public final String getNameFromUuid(@NonNull UUID uuid, boolean expensiveLookups) {
return plugin.getUuidTranslator().getNameFromUuid(uuid, expensiveLookups);
}
/** /**
* Get the current BungeeCord / Velocity proxy ID for this server. * Fetch a UUID from the specified name. Names are cached locally and in Redis. This function
* * falls back to Mojang as a last resort, so calls <strong>may</strong> be blocking.
* @return the current server ID *
* @see #getAllProxies() * <p>If performance is a concern, see {@link #getUuidFromName(String, boolean)}, which disables
* @since 0.8.0 * the following functions:
*/ *
public final String getProxyId() { * <ul>
return plugin.proxyDataManager().proxyId(); * <li>Searching local entries case-insensitively
} * <li>Searching Mojang
* </ul>
*
* @param name the UUID to fetch the name for
* @return the UUID for the name
* @since 0.3
*/
public final UUID getUuidFromName(@NonNull String name) {
return getUuidFromName(name, true);
}
/** /**
* Get all the linked proxies in this network. * Fetch a UUID from the specified name. Names are cached locally and in Redis. This function
* * falls back to Mojang as a last resort if {@code expensiveLookups} is true, so calls
* @return the list of all proxies * <strong>may</strong> be blocking.
* @see #getProxyId() *
* @since 0.8.0 * <p>If performance is a concern, set {@code expensiveLookups} to false to disable searching
*/ * Mojang and searching for usernames case-insensitively.
public final List<String> getAllProxies() { *
return plugin.proxyDataManager().proxiesIds(); * @param name the UUID to fetch the name for
} * @param expensiveLookups whether or not to perform potentially expensive lookups
* @return the {@link UUID} for the name
* @since 0.3.2
*/
public final UUID getUuidFromName(@NonNull String name, boolean expensiveLookups) {
return plugin.getUuidTranslator().getTranslatedUuid(name, expensiveLookups);
}
/** /**
* Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function falls back to Mojang * returns Summoner class responsible for Single Jedis {@link redis.clients.jedis.JedisPooled}
* as a last resort, so calls <strong>may</strong> be blocking. * with {@link JedisPool}, Cluster Jedis {@link redis.clients.jedis.JedisCluster} handling
* <p> *
* For the common use case of translating a list of UUIDs into names, use {@link #getHumanPlayersOnline()} instead. * @return {@link Summoner}
* <p> * @since 0.8.0
* If performance is a concern, use {@link #getNameFromUuid(java.util.UUID, boolean)} as this allows you to disable Mojang lookups. */
* public Summoner<?> getSummoner() {
* @param uuid the UUID to fetch the name for return this.plugin.getSummoner();
* @return the name for the UUID }
* @since 0.3
*/
public final String getNameFromUuid(@NonNull UUID uuid) {
return getNameFromUuid(uuid, true);
}
/** /**
* Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function can fall back to Mojang * Kicks a player from the network using miniMessage calls {@link #getUuidFromName(String)} to get
* as a last resort if {@code expensiveLookups} is true, so calls <strong>may</strong> be blocking. * uuid <a href="https://docs.advntr.dev/minimessage/format.html">...</a>
* <p> *
* For the common use case of translating the list of online players into names, use {@link #getHumanPlayersOnline()}. * @param playerName player name
* <p> * @param miniMessage kick message that player will see on kick using minimessage as format
* If performance is a concern, set {@code expensiveLookups} to false as this will disable lookups via Mojang. * @since 0.13.0
* */
* @param uuid the UUID to fetch the name for public void kickPlayer(String playerName, String miniMessage) {
* @param expensiveLookups whether or not to perform potentially expensive lookups kickPlayer(getUuidFromName(playerName), miniMessage);
* @return the name for the UUID }
* @since 0.3.2
*/
public final String getNameFromUuid(@NonNull UUID uuid, boolean expensiveLookups) {
return plugin.getUuidTranslator().getNameFromUuid(uuid, expensiveLookups);
}
/** /**
* Fetch a UUID from the specified name. Names are cached locally and in Redis. This function falls back to Mojang * Kicks a player from the network <a
* as a last resort, so calls <strong>may</strong> be blocking. * href="https://docs.advntr.dev/minimessage/format.html">...</a>
* <p> *
* If performance is a concern, see {@link #getUuidFromName(String, boolean)}, which disables the following functions: * @param player player uuid
* <ul> * @param miniMessage kick message that player will see on kick using minimessage as format
* <li>Searching local entries case-insensitively</li> * @since 0.13.0
* <li>Searching Mojang</li> */
* </ul> public void kickPlayer(UUID player, String miniMessage) {
* plugin.playerDataManager().serializedPlayerKick(player, miniMessage);
* @param name the UUID to fetch the name for }
* @return the UUID for the name
* @since 0.3
*/
public final UUID getUuidFromName(@NonNull String name) {
return getUuidFromName(name, true);
}
/** /**
* Fetch a UUID from the specified name. Names are cached locally and in Redis. This function falls back to Mojang * shows what mode is RedisBungee is on Basically what every redis mode is used like cluster or
* as a last resort if {@code expensiveLookups} is true, so calls <strong>may</strong> be blocking. * single instance.
* <p> *
* If performance is a concern, set {@code expensiveLookups} to false to disable searching Mojang and searching for usernames * @return {@link RedisBungeeMode}
* case-insensitively. * @since 0.8.0
* */
* @param name the UUID to fetch the name for public RedisBungeeMode getMode() {
* @param expensiveLookups whether or not to perform potentially expensive lookups return this.plugin.getRedisBungeeMode();
* @return the {@link UUID} for the name }
* @since 0.3.2
*/
public final UUID getUuidFromName(@NonNull String name, boolean expensiveLookups) {
return plugin.getUuidTranslator().getTranslatedUuid(name, expensiveLookups);
}
public static AbstractRedisBungeeAPI getAbstractRedisBungeeAPI() {
/** return abstractRedisBungeeAPI;
* returns Summoner class responsible for Single Jedis {@link redis.clients.jedis.JedisPooled} with {@link JedisPool}, Cluster Jedis {@link redis.clients.jedis.JedisCluster} handling }
*
* @return {@link Summoner}
* @since 0.8.0
*/
public Summoner<?> getSummoner() {
return this.plugin.getSummoner();
}
/**
* Kicks a player from the network using miniMessage
* calls {@link #getUuidFromName(String)} to get uuid
* <a href="https://docs.advntr.dev/minimessage/format.html">...</a>
* @param playerName player name
* @param miniMessage kick message that player will see on kick using minimessage as format
* @since 0.13.0
*/
public void kickPlayer(String playerName, String miniMessage) {
kickPlayer(getUuidFromName(playerName), miniMessage);
}
/**
* Kicks a player from the network
* <a href="https://docs.advntr.dev/minimessage/format.html">...</a>
* @param player player uuid
* @param miniMessage kick message that player will see on kick using minimessage as format
* @since 0.13.0
*/
public void kickPlayer(UUID player, String miniMessage) {
plugin.playerDataManager().serializedPlayerKick(player, miniMessage);
}
/**
* 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
*/
public RedisBungeeMode getMode() {
return this.plugin.getRedisBungeeMode();
}
public static AbstractRedisBungeeAPI getAbstractRedisBungeeAPI() {
return abstractRedisBungeeAPI;
}
} }

View File

@ -1,23 +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;
public class Constants {
public final static String VERSION = "{{ version }}";
public final static String GIT_COMMIT = "{{ git-commit }}";
public static String getGithubCommitLink() {
return "https://github.com/ProxioDev/ValioBungee/commit/" + GIT_COMMIT;
}
}

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api; package com.imaginarycode.minecraft.redisbungee.api;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
@ -20,267 +19,293 @@ import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEv
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.json.JSONObject; import org.json.JSONObject;
import redis.clients.jedis.ClusterPipeline; import redis.clients.jedis.ClusterPipeline;
import redis.clients.jedis.Pipeline; import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response; import redis.clients.jedis.Response;
import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
public abstract class PlayerDataManager<P> { public abstract class PlayerDataManager<P> {
protected final RedisBungeePlugin<P> plugin; protected final RedisBungeePlugin<P> plugin;
private final Object SERVERS_TO_PLAYERS_KEY = new Object(); private final Object SERVERS_TO_PLAYERS_KEY = new Object();
private final UnifiedJedis unifiedJedis; private final UnifiedJedis unifiedJedis;
private final String proxyId; private final String proxyId;
private final String networkId; private final String networkId;
private final LoadingCache<UUID, String> serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis); private final LoadingCache<UUID, String> serverCache =
private final LoadingCache<UUID, String> lastServerCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastServerFromRedis); Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis);
private final LoadingCache<UUID, String> proxyCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getProxyFromRedis); private final LoadingCache<UUID, String> lastServerCache =
private final LoadingCache<UUID, InetAddress> ipCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getIpAddressFromRedis); Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastServerFromRedis);
private final LoadingCache<UUID, Long> lastOnlineCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastOnlineFromRedis); private final LoadingCache<UUID, String> proxyCache =
private final LoadingCache<Object, Multimap<String, UUID>> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder); Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getProxyFromRedis);
private final LoadingCache<UUID, InetAddress> ipCache =
Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getIpAddressFromRedis);
private final LoadingCache<UUID, Long> lastOnlineCache =
Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastOnlineFromRedis);
private final LoadingCache<Object, Multimap<String, UUID>> serverToPlayersCache =
Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(this::serversToPlayersBuilder);
public PlayerDataManager(RedisBungeePlugin<P> plugin) { public PlayerDataManager(RedisBungeePlugin<P> plugin) {
this.plugin = plugin; this.plugin = plugin;
this.unifiedJedis = plugin.proxyDataManager().unifiedJedis(); this.unifiedJedis = plugin.proxyDataManager().unifiedJedis();
this.proxyId = plugin.proxyDataManager().proxyId(); this.proxyId = plugin.proxyDataManager().proxyId();
this.networkId = plugin.proxyDataManager().networkId(); this.networkId = plugin.proxyDataManager().networkId();
} }
// handle network wide // handle network wide
// server change // server change
// public void onPlayerChangedServerNetworkEvent // public void onPlayerChangedServerNetworkEvent
// public void onNetworkPlayerQuit // public void onNetworkPlayerQuit
// public void onNetworkPlayerJoin // public void onNetworkPlayerJoin
// local events // local events
// public void onPubSubMessageEvent // public void onPubSubMessageEvent
// public void onServerConnectedEvent // public void onServerConnectedEvent
// public void onLoginEvent // public void onLoginEvent
// public void onDisconnectEvent // public void onDisconnectEvent
protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) { protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) {
this.serverCache.invalidate(event.getUuid()); this.serverCache.invalidate(event.getUuid());
this.lastServerCache.invalidate(event.getUuid()); this.lastServerCache.invalidate(event.getUuid());
//TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache in-place without querying redis. That would be a lot more efficient. // TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY); // in-place without querying redis. That would be a lot more efficient.
} this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) { protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid()); this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid()); this.serverCache.invalidate(event.getUuid());
this.ipCache.invalidate(event.getUuid()); this.ipCache.invalidate(event.getUuid());
this.lastOnlineCache.invalidate(event.getUuid()); this.lastOnlineCache.invalidate(event.getUuid());
//TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache in-place without querying redis. That would be a lot more efficient. // TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY); // in-place without querying redis. That would be a lot more efficient.
} this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handleNetworkPlayerJoin(IPlayerJoinedNetworkEvent event) { protected void handleNetworkPlayerJoin(IPlayerJoinedNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid()); this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid()); this.serverCache.invalidate(event.getUuid());
this.ipCache.invalidate(event.getUuid()); this.ipCache.invalidate(event.getUuid());
this.lastOnlineCache.invalidate(event.getUuid()); this.lastOnlineCache.invalidate(event.getUuid());
//TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache in-place without querying redis. That would be a lot more efficient. // TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY); // in-place without querying redis. That would be a lot more efficient.
} this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handlePubSubMessageEvent(IPubSubMessageEvent event) {
protected void handlePubSubMessageEvent(IPubSubMessageEvent event) { switch (event.getChannel()) {
switch (event.getChannel()) { case "redisbungee-serverchange" -> {
case "redisbungee-serverchange" -> { JSONObject data = new JSONObject(event.getMessage());
JSONObject data = new JSONObject(event.getMessage()); UUID uuid = UUID.fromString(data.getString("uuid"));
UUID uuid = UUID.fromString(data.getString("uuid")); String from = null;
String from = null; if (data.has("from")) from = data.getString("from");
if (data.has("from")) from = data.getString("from"); String to = data.getString("to");
String to = data.getString("to");
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
}
case "redisbungee-player-join" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
}
case "redisbungee-player-leave" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
}
case "redisbungee-player-kick" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
String message = data.getString("serialized-message");
handleSerializedKick(uuid, message);
}
}
}
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)); plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
handleServerChangeRedis(uuid, to); }
} case "redisbungee-player-join" -> {
JSONObject data = new JSONObject(event.getMessage());
// must check if player is on the local proxy UUID uuid = UUID.fromString(data.getString("uuid"));
// https://docs.advntr.dev/minimessage/index.html
// implemented downstream in Velocity and Bungeecord
protected abstract boolean handleSerializedKick(UUID player, String serializedMiniMessage);
// https://docs.advntr.dev/minimessage/index.html
// implemented downstream in Velocity and Bungeecord
// called by kickPlayer in each impl of this class `NOT OVERRIDE`
public void serializedPlayerKick(UUID player, String serializedMiniMessage) {
JSONObject data = new JSONObject();
data.put("proxy", this.proxyId);
data.put("uuid", player);
data.put("serialized-message", serializedMiniMessage);
if (!handleSerializedKick(player, serializedMiniMessage))
plugin.proxyDataManager().sendChannelMessage("redisbungee-player-kick", data.toString());
}
private void handleServerChangeRedis(UUID uuid, String server) {
Map<String, String> data = new HashMap<>();
data.put("server", server);
data.put("last-server", server);
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", data);
}
protected void addPlayer(final UUID uuid, final String name, final InetAddress inetAddress) {
Map<String, String> redisData = new HashMap<>();
redisData.put("last-online", String.valueOf(0));
redisData.put("proxy", this.proxyId);
redisData.put("ip", inetAddress.getHostAddress());
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", redisData);
plugin.getUuidTranslator().persistInfo(name, uuid, this.unifiedJedis);
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)); plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
this.plugin.proxyDataManager().addPlayer(uuid); }
} case "redisbungee-player-leave" -> {
JSONObject data = new JSONObject(event.getMessage());
protected void removePlayer(UUID uuid) { UUID uuid = UUID.fromString(data.getString("uuid"));
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-online", String.valueOf(System.currentTimeMillis()));
unifiedJedis.hdel("redisbungee::" + 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)); plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
this.plugin.proxyDataManager().removePlayer(uuid); }
case "redisbungee-player-kick" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
String message = data.getString("serialized-message");
handleSerializedKick(uuid, message);
}
} }
}
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);
}
protected String getProxyFromRedis(UUID uuid) { // must check if player is on the local proxy
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "proxy"); // https://docs.advntr.dev/minimessage/index.html
} // implemented downstream in Velocity and Bungeecord
protected abstract boolean handleSerializedKick(UUID player, String serializedMiniMessage);
protected String getServerFromRedis(UUID uuid) { // https://docs.advntr.dev/minimessage/index.html
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "server"); // implemented downstream in Velocity and Bungeecord
} // called by kickPlayer in each impl of this class `NOT OVERRIDE`
public void serializedPlayerKick(UUID player, String serializedMiniMessage) {
JSONObject data = new JSONObject();
data.put("proxy", this.proxyId);
data.put("uuid", player);
data.put("serialized-message", serializedMiniMessage);
if (!handleSerializedKick(player, serializedMiniMessage))
plugin.proxyDataManager().sendChannelMessage("redisbungee-player-kick", data.toString());
}
protected String getLastServerFromRedis(UUID uuid) { private void handleServerChangeRedis(UUID uuid, String server) {
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-server"); Map<String, String> data = new HashMap<>();
} data.put("server", server);
data.put("last-server", server);
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", data);
}
protected InetAddress getIpAddressFromRedis(UUID uuid) { protected void addPlayer(final UUID uuid, final String name, final InetAddress inetAddress) {
String ip = unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "ip"); Map<String, String> redisData = new HashMap<>();
if (ip == null) return null; redisData.put("last-online", String.valueOf(0));
return InetAddresses.forString(ip); redisData.put("proxy", this.proxyId);
} redisData.put("ip", inetAddress.getHostAddress());
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", redisData);
plugin.getUuidTranslator().persistInfo(name, uuid, this.unifiedJedis);
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 long getLastOnlineFromRedis(UUID uuid) { protected void removePlayer(UUID uuid) {
String unixString = unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-online"); unifiedJedis.hset(
if (unixString == null) return -1; "redisbungee::" + this.networkId + "::player::" + uuid + "::data",
return Long.parseLong(unixString); "last-online",
} String.valueOf(System.currentTimeMillis()));
unifiedJedis.hdel(
"redisbungee::" + 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);
}
public String getLastServerFor(UUID uuid) { protected String getProxyFromRedis(UUID uuid) {
return this.lastServerCache.get(uuid); return unifiedJedis.hget(
} "redisbungee::" + this.networkId + "::player::" + uuid + "::data", "proxy");
}
public String getServerFor(UUID uuid) { protected String getServerFromRedis(UUID uuid) {
return this.serverCache.get(uuid); return unifiedJedis.hget(
} "redisbungee::" + this.networkId + "::player::" + uuid + "::data", "server");
}
public String getProxyFor(UUID uuid) { protected String getLastServerFromRedis(UUID uuid) {
return this.proxyCache.get(uuid); return unifiedJedis.hget(
} "redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-server");
}
public InetAddress getIpFor(UUID uuid) { protected InetAddress getIpAddressFromRedis(UUID uuid) {
return this.ipCache.get(uuid); String ip =
} unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "ip");
if (ip == null) return null;
return InetAddresses.forString(ip);
}
public long getLastOnline(UUID uuid) { protected long getLastOnlineFromRedis(UUID uuid) {
return this.lastOnlineCache.get(uuid); String unixString =
} unifiedJedis.hget(
"redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-online");
if (unixString == null) return -1;
return Long.parseLong(unixString);
}
public Multimap<String, UUID> serversToPlayers() { public String getLastServerFor(UUID uuid) {
return this.serverToPlayersCache.get(SERVERS_TO_PLAYERS_KEY); return this.lastServerCache.get(uuid);
} }
protected Multimap<String, UUID> serversToPlayersBuilder(Object o) { public String getServerFor(UUID uuid) {
try { return this.serverCache.get(uuid);
return new RedisPipelineTask<Multimap<String, UUID>>(plugin) { }
private final Set<UUID> uuids = plugin.proxyDataManager().networkPlayers();
private final ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
@Override public String getProxyFor(UUID uuid) {
public Multimap<String, UUID> doPooledPipeline(Pipeline pipeline) { return this.proxyCache.get(uuid);
HashMap<UUID, Response<String>> responses = new HashMap<>(); }
for (UUID uuid : uuids) {
Optional.ofNullable(pipeline.hget("redisbungee::" + networkId + "::player::" + uuid + "::data", "server")).ifPresent(stringResponse -> {
responses.put(uuid, stringResponse);
});
}
pipeline.sync();
responses.forEach((uuid, response) -> {
String key = response.get();
if (key == null) return;
builder.put(key, uuid); public InetAddress getIpFor(UUID uuid) {
return this.ipCache.get(uuid);
}
public long getLastOnline(UUID uuid) {
return this.lastOnlineCache.get(uuid);
}
public Multimap<String, UUID> serversToPlayers() {
return this.serverToPlayersCache.get(SERVERS_TO_PLAYERS_KEY);
}
protected Multimap<String, UUID> serversToPlayersBuilder(Object o) {
try {
return new RedisPipelineTask<Multimap<String, UUID>>(plugin) {
private final Set<UUID> uuids = plugin.proxyDataManager().networkPlayers();
private final ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
@Override
public Multimap<String, UUID> doPooledPipeline(Pipeline pipeline) {
HashMap<UUID, Response<String>> responses = new HashMap<>();
for (UUID uuid : uuids) {
Optional.ofNullable(
pipeline.hget(
"redisbungee::" + networkId + "::player::" + uuid + "::data", "server"))
.ifPresent(
stringResponse -> {
responses.put(uuid, stringResponse);
}); });
return builder.build(); }
} pipeline.sync();
responses.forEach(
(uuid, response) -> {
String key = response.get();
if (key == null) return;
@Override builder.put(key, uuid);
public Multimap<String, UUID> clusterPipeline(ClusterPipeline pipeline) { });
HashMap<UUID, Response<String>> responses = new HashMap<>(); return builder.build();
for (UUID uuid : uuids) {
Optional.ofNullable(pipeline.hget("redisbungee::" + networkId + "::player::" + uuid + "::data", "server")).ifPresent(stringResponse -> {
responses.put(uuid, stringResponse);
});
}
pipeline.sync();
responses.forEach((uuid, response) -> {
String key = response.get();
if (key == null) return;
builder.put(key, uuid);
});
return builder.build();
}
}.call();
} catch (Exception e) {
throw new RuntimeException(e);
} }
}
@Override
public Multimap<String, UUID> clusterPipeline(ClusterPipeline pipeline) {
HashMap<UUID, Response<String>> responses = new HashMap<>();
for (UUID uuid : uuids) {
Optional.ofNullable(
pipeline.hget(
"redisbungee::" + networkId + "::player::" + uuid + "::data", "server"))
.ifPresent(
stringResponse -> {
responses.put(uuid, stringResponse);
});
}
pipeline.sync();
responses.forEach(
(uuid, response) -> {
String key = response.get();
if (key == null) return;
builder.put(key, uuid);
});
return builder.build();
}
}.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -1,15 +1,16 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api; package com.imaginarycode.minecraft.redisbungee.api;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@ -25,384 +26,422 @@ import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.PubSubPay
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer; import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask;
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import redis.clients.jedis.*; import redis.clients.jedis.*;
import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.params.XAddParams;
import redis.clients.jedis.params.XReadParams; import redis.clients.jedis.params.XReadParams;
import redis.clients.jedis.resps.StreamEntry; 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 { public abstract class ProxyDataManager implements Runnable {
private static final int MAX_ENTRIES = 10000; private static final int MAX_ENTRIES = 10000;
private final AtomicBoolean closed = new AtomicBoolean(false); private final AtomicBoolean closed = new AtomicBoolean(false);
private final UnifiedJedis unifiedJedis; private final UnifiedJedis unifiedJedis;
// data: // data:
// Proxy id, heartbeat (unix epoch from instant), players as int // Proxy id, heartbeat (unix epoch from instant), players as int
private final ConcurrentHashMap<String, HeartbeatPayload.HeartbeatData> heartbeats = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, HeartbeatPayload.HeartbeatData> heartbeats =
new ConcurrentHashMap<>();
private final String networkId; private final String networkId;
private final String proxyId; private final String proxyId;
private final String STREAM_ID; private final String STREAM_ID;
// This different from proxy id, just to detect if there is duplicate proxy using same proxy id // This different from proxy id, just to detect if there is duplicate proxy using same proxy id
private final UUID dataManagerUUID = UUID.randomUUID(); private final UUID dataManagerUUID = UUID.randomUUID();
protected final RedisBungeePlugin<?> plugin; 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(); 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) { public ProxyDataManager(RedisBungeePlugin<?> plugin) {
this.plugin = plugin; this.plugin = plugin;
this.proxyId = this.plugin.configuration().getProxyId(); this.proxyId = this.plugin.configuration().getProxyId();
this.unifiedJedis = plugin.getSummoner().obtainResource(); this.unifiedJedis = plugin.getSummoner().obtainResource();
this.networkId = plugin.configuration().networkId(); this.networkId = plugin.configuration().networkId();
this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream"; this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream";
this.destroyProxyMembers(); this.destroyProxyMembers();
}
public abstract Set<UUID> getLocalOnlineUUIDs();
public Set<UUID> 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 abstract Set<UUID> getLocalOnlineUUIDs(); // this skip checking if proxy is and its package private
// due proxy shutdown shenanigans
public boolean isPlayerTrulyOnProxy(String proxyId, UUID uuid) {
return unifiedJedis.sismember(
"redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players",
uuid.toString());
}
public Set<UUID> getPlayersOn(String proxyId) { public List<String> proxiesIds() {
checkArgument(proxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID"); return Collections.list(this.heartbeats.keys());
if (proxyId.equals(this.proxyId)) return this.getLocalOnlineUUIDs(); }
if (!this.heartbeats.containsKey(proxyId)) {
return new HashSet<>(); // return empty hashset or null? public synchronized void sendCommandTo(String proxyToRun, String command) {
} if (isClosed()) return;
return getProxyMembers(proxyId); if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) {
handlePlatformCommandExecution(command);
} }
publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command));
}
// this skip checking if proxy is and its package private public synchronized void sendChannelMessage(String channel, String message) {
// due proxy shutdown shenanigans if (isClosed()) return;
public boolean isPlayerTrulyOnProxy(String proxyId, UUID uuid) { publishPayload(new PubSubPayload(this.proxyId, channel, message));
return unifiedJedis.sismember("redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players", uuid.toString()); }
}
// 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 List<String> proxiesIds() { public Set<UUID> networkPlayers() {
return Collections.list(this.heartbeats.keys()); try {
} return new RedisPipelineTask<Set<UUID>>(this.plugin) {
@Override
public synchronized void sendCommandTo(String proxyToRun, String command) { public Set<UUID> doPooledPipeline(Pipeline pipeline) {
if (isClosed()) return; HashSet<Response<Set<String>>> responses = new HashSet<>();
if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) { for (String proxyId : proxiesIds()) {
handlePlatformCommandExecution(command); responses.add(
} pipeline.smembers(
publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command)); "redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players"));
} }
pipeline.sync();
public synchronized void sendChannelMessage(String channel, String message) { HashSet<UUID> uuids = new HashSet<>();
if (isClosed()) return; for (Response<Set<String>> response : responses) {
publishPayload(new PubSubPayload(this.proxyId, channel, message)); for (String stringUUID : response.get()) {
} uuids.add(UUID.fromString(stringUUID));
// 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<UUID> networkPlayers() {
try {
return new RedisPipelineTask<Set<UUID>>(this.plugin) {
@Override
public Set<UUID> doPooledPipeline(Pipeline pipeline) {
HashSet<Response<Set<String>>> responses = new HashSet<>();
for (String proxyId : proxiesIds()) {
responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players"));
}
pipeline.sync();
HashSet<UUID> uuids = new HashSet<>();
for (Response<Set<String>> response : responses) {
for (String stringUUID : response.get()) {
uuids.add(UUID.fromString(stringUUID));
}
}
return uuids;
}
@Override
public Set<UUID> clusterPipeline(ClusterPipeline pipeline) {
HashSet<Response<Set<String>>> responses = new HashSet<>();
for (String proxyId : proxiesIds()) {
responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players"));
}
pipeline.sync();
HashSet<UUID> uuids = new HashSet<>();
for (Response<Set<String>> 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<String, Integer> eachProxyCount() {
ImmutableMap.Builder<String, Integer> 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<String, String> 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<UUID> localOnlineUUIDs = getLocalOnlineUUIDs();
Set<UUID> storedRedisUuids = getProxyMembers(this.proxyId);
if (!localOnlineUUIDs.equals(storedRedisUuids)) {
plugin.logWarn("De-synced players set detected correcting....");
Set<UUID> add = new HashSet<>(localOnlineUUIDs);
Set<UUID> 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);
} }
}
return uuids;
}
@Override
public Set<UUID> clusterPipeline(ClusterPipeline pipeline) {
HashSet<Response<Set<String>>> responses = new HashSet<>();
for (String proxyId : proxiesIds()) {
responses.add(
pipeline.smembers(
"redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players"));
}
pipeline.sync();
HashSet<UUID> uuids = new HashSet<>();
for (Response<Set<String>> 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<String, Integer> eachProxyCount() {
ImmutableMap.Builder<String, Integer> 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<String, String> 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<UUID> localOnlineUUIDs = getLocalOnlineUUIDs();
Set<UUID> storedRedisUuids = getProxyMembers(this.proxyId);
if (!localOnlineUUIDs.equals(storedRedisUuids)) {
plugin.logWarn("De-synced players set detected correcting....");
Set<UUID> add = new HashSet<>(localOnlineUUIDs);
Set<UUID> 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<Void>(plugin) {
@Override
public Void doPooledPipeline(Pipeline pipeline) {
Set<String> removeString = new HashSet<>();
for (UUID uuid : remove) { for (UUID uuid : remove) {
plugin.logWarn("found {} that does not belong to this proxy removing it from the corrected set", uuid); removeString.add(uuid.toString());
} }
try { Set<String> addString = new HashSet<>();
new RedisPipelineTask<Void>(plugin) { for (UUID uuid : add) {
@Override addString.add(uuid.toString());
public Void doPooledPipeline(Pipeline pipeline) { }
Set<String> removeString = new HashSet<>(); pipeline.srem(
for (UUID uuid : remove) { "redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players",
removeString.add(uuid.toString()); removeString.toArray(new String[] {}));
} pipeline.sadd(
Set<String> addString = new HashSet<>(); "redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players",
for (UUID uuid : add) { addString.toArray(new String[] {}));
addString.add(uuid.toString()); pipeline.sync();
} return null;
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 @Override
public Void clusterPipeline(ClusterPipeline pipeline) { public Void clusterPipeline(ClusterPipeline pipeline) {
Set<String> removeString = new HashSet<>(); Set<String> removeString = new HashSet<>();
for (UUID uuid : remove) { for (UUID uuid : remove) {
removeString.add(uuid.toString()); removeString.add(uuid.toString());
}
Set<String> 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!"); Set<String> 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<String> deadProxies = new HashSet<>();
for (Map.Entry<String, HeartbeatPayload.HeartbeatData> 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<Void>(plugin) {
@Override
public Void doPooledPipeline(Pipeline pipeline) {
for (String deadProxy : deadProxies) {
pipeline.del(
"redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players");
}
pipeline.sync();
return null;
} }
@Override
// handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~30 seconds public Void clusterPipeline(ClusterPipeline pipeline) {
final Set<String> deadProxies = new HashSet<>(); for (String deadProxy : deadProxies) {
for (Map.Entry<String, HeartbeatPayload.HeartbeatData> stringHeartbeatDataEntry : this.heartbeats.entrySet()) { pipeline.del(
String id = stringHeartbeatDataEntry.getKey(); "redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players");
long heartbeat = stringHeartbeatDataEntry.getValue().heartbeat(); }
if (Instant.now().getEpochSecond() - heartbeat > RedisUtil.PROXY_TIMEOUT) { pipeline.sync();
deadProxies.add(id); return null;
cleanProxy(id);
}
} }
}.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<UUID> getProxyMembers(String proxyId) {
Set<String> uuidsStrings =
unifiedJedis.smembers(
"redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players");
HashSet<UUID> 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<java.util.Map.Entry<String, List<StreamEntry>>> data =
unifiedJedis.xread(
XReadParams.xReadParams().block(0),
Collections.singletonMap(
STREAM_ID,
lastStreamEntryID != null ? lastStreamEntryID : StreamEntryID.LAST_ENTRY));
for (Map.Entry<String, List<StreamEntry>> 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 { try {
new RedisPipelineTask<Void>(plugin) { Thread.sleep(5000);
@Override } catch (InterruptedException ignored) {
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) { public void close() {
cleanProxy(payload.senderProxy()); closed.set(true);
} this.publishDeath();
this.heartbeats.clear();
this.destroyProxyMembers();
}
private void cleanProxy(String id) { public boolean isClosed() {
if (id.equals(this.proxyId())) { return closed.get();
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) { public String proxyId() {
String channel = payload.channel(); return proxyId;
String message = payload.message(); }
this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message));
}
protected abstract void handlePlatformCommandExecution(String command); public UnifiedJedis unifiedJedis() {
return unifiedJedis;
}
private void handleCommand(RunCommandPayload payload) { public String networkId() {
String proxyToRun = payload.proxyToRun(); return networkId;
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<UUID> getProxyMembers(String proxyId) {
Set<String> uuidsStrings = unifiedJedis.smembers("redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players");
HashSet<UUID> 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<java.util.Map.Entry<String, List<StreamEntry>>> data = unifiedJedis.xread(XReadParams.xReadParams().block(0), Collections.singletonMap(STREAM_ID, lastStreamEntryID != null ? lastStreamEntryID : StreamEntryID.LAST_ENTRY));
for (Map.Entry<String, List<StreamEntry>> 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;
}
} }

View File

@ -1,15 +1,15 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api; package com.imaginarycode.minecraft.redisbungee.api;
public enum RedisBungeeMode { public enum RedisBungeeMode {
SINGLE, CLUSTER SINGLE,
CLUSTER
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api; package com.imaginarycode.minecraft.redisbungee.api;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
@ -15,75 +14,71 @@ import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfigurati
import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform; import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* 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 * This Class has all internal methods needed by every redis bungee plugin, and it can be used to
* <p> * implement another platforms than bungeecord or another forks of RedisBungee
* Reason this is interface because some proxies implementations require the user to extend class for plugins for example bungeecord. *
* <p>Reason this is interface because some proxies implementations require the user to extend class
* for plugins for example bungeecord.
* *
* @author Ham1255 * @author Ham1255
* @since 0.7.0 * @since 0.7.0
*/ */
public interface RedisBungeePlugin<P> extends EventsPlatform { public interface RedisBungeePlugin<P> extends EventsPlatform {
default void initialize() { default void initialize() {}
} default void stop() {}
default void stop() { void logInfo(String msg);
} void logInfo(String format, Object... object);
void logInfo(String msg); void logWarn(String msg);
void logInfo(String format, Object... object); void logWarn(String format, Object... object);
void logWarn(String msg); void logFatal(String msg);
void logWarn(String format, Object... object); void logFatal(String format, Throwable throwable);
void logFatal(String msg); RedisBungeeConfiguration configuration();
void logFatal(String format, Throwable throwable); Summoner<?> getSummoner();
RedisBungeeConfiguration configuration(); RedisBungeeMode getRedisBungeeMode();
Summoner<?> getSummoner(); AbstractRedisBungeeAPI getAbstractRedisBungeeApi();
RedisBungeeMode getRedisBungeeMode(); ProxyDataManager proxyDataManager();
AbstractRedisBungeeAPI getAbstractRedisBungeeApi(); PlayerDataManager<P> playerDataManager();
ProxyDataManager proxyDataManager(); UUIDTranslator getUuidTranslator();
PlayerDataManager<P> playerDataManager(); boolean isOnlineMode();
UUIDTranslator getUuidTranslator(); P getPlayer(UUID uuid);
boolean isOnlineMode(); P getPlayer(String name);
P getPlayer(UUID uuid); UUID getPlayerUUID(String player);
P getPlayer(String name); String getPlayerName(UUID player);
UUID getPlayerUUID(String player); String getPlayerServerName(P player);
String getPlayerName(UUID player); boolean isPlayerOnAServer(P player);
String getPlayerServerName(P player); InetAddress getPlayerIp(P player);
boolean isPlayerOnAServer(P player); void executeAsync(Runnable runnable);
InetAddress getPlayerIp(P player); void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
void executeAsync(Runnable runnable); String platformId();
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
String platformId();
} }

View File

@ -1,17 +1,16 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.config; package com.imaginarycode.minecraft.redisbungee.api.config;
public enum HandleMotdOrder { public enum HandleMotdOrder {
FIRST, FIRST,
NORMAL, NORMAL,
LAST LAST
} }

View File

@ -1,93 +1,109 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.config; package com.imaginarycode.minecraft.redisbungee.api.config;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.net.InetAddresses; import com.google.common.net.InetAddresses;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.List; import java.util.List;
public class RedisBungeeConfiguration { public class RedisBungeeConfiguration {
private final String proxyId; private final String proxyId;
private final List<InetAddress> exemptAddresses; private final List<InetAddress> exemptAddresses;
private final boolean kickWhenOnline; private final boolean kickWhenOnline;
private final boolean handleReconnectToLastServer; private final boolean handleReconnectToLastServer;
private final boolean handleMotd; private final boolean handleMotd;
private final HandleMotdOrder handleMotdOrder; private final HandleMotdOrder handleMotdOrder;
private final CommandsConfiguration commandsConfiguration; private final CommandsConfiguration commandsConfiguration;
private final String networkId; private final String networkId;
public RedisBungeeConfiguration(
public RedisBungeeConfiguration(String networkId, String proxyId, List<String> exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, HandleMotdOrder handleMotdOrder, CommandsConfiguration commandsConfiguration) { String networkId,
this.proxyId = proxyId; String proxyId,
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder(); List<String> exemptAddresses,
for (String s : exemptAddresses) { boolean kickWhenOnline,
addressBuilder.add(InetAddresses.forString(s)); boolean handleReconnectToLastServer,
} boolean handleMotd,
this.exemptAddresses = addressBuilder.build(); HandleMotdOrder handleMotdOrder,
this.kickWhenOnline = kickWhenOnline; CommandsConfiguration commandsConfiguration) {
this.handleReconnectToLastServer = handleReconnectToLastServer; this.proxyId = proxyId;
this.handleMotd = handleMotd; ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
this.handleMotdOrder = handleMotdOrder; for (String s : exemptAddresses) {
this.commandsConfiguration = commandsConfiguration; addressBuilder.add(InetAddresses.forString(s));
this.networkId = networkId;
} }
this.exemptAddresses = addressBuilder.build();
this.kickWhenOnline = kickWhenOnline;
this.handleReconnectToLastServer = handleReconnectToLastServer;
this.handleMotd = handleMotd;
this.handleMotdOrder = handleMotdOrder;
this.commandsConfiguration = commandsConfiguration;
this.networkId = networkId;
}
public String getProxyId() { public String getProxyId() {
return proxyId; return proxyId;
} }
public List<InetAddress> getExemptAddresses() { public List<InetAddress> getExemptAddresses() {
return exemptAddresses; return exemptAddresses;
} }
public boolean kickWhenOnline() { public boolean kickWhenOnline() {
return kickWhenOnline; return kickWhenOnline;
} }
public boolean handleMotd() { public boolean handleMotd() {
return this.handleMotd; return this.handleMotd;
} }
public HandleMotdOrder handleMotdOrder() { public HandleMotdOrder handleMotdOrder() {
return handleMotdOrder; return handleMotdOrder;
} }
public boolean handleReconnectToLastServer() { public boolean handleReconnectToLastServer() {
return this.handleReconnectToLastServer; return this.handleReconnectToLastServer;
} }
public record CommandsConfiguration(boolean redisbungeeEnabled, boolean redisbungeeLegacyEnabled, public record CommandsConfiguration(
LegacySubCommandsConfiguration legacySubCommandsConfiguration) { boolean redisbungeeEnabled,
boolean redisbungeeLegacyEnabled,
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 record LegacySubCommandsConfiguration(boolean findEnabled, boolean glistEnabled, boolean ipEnabled, public CommandsConfiguration commandsConfiguration() {
boolean lastseenEnabled, boolean plistEnabled, boolean pproxyEnabled, return commandsConfiguration;
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() { public String networkId() {
return commandsConfiguration; return networkId;
} }
public String networkId() {
return networkId;
}
} }

View File

@ -1,16 +1,14 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.config.loaders; package com.imaginarycode.minecraft.redisbungee.api.config.loaders;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
@ -19,6 +17,9 @@ import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfigurati
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.objectmapping.ObjectMappingException; import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
@ -27,181 +28,271 @@ import redis.clients.jedis.*;
import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.providers.ClusterConnectionProvider;
import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.providers.PooledConnectionProvider;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
public interface ConfigLoader extends GenericConfigLoader { public interface ConfigLoader extends GenericConfigLoader {
int CONFIG_VERSION = 2; int CONFIG_VERSION = 2;
default void loadConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException { default void loadConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml"); Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml");
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build(); final YAMLConfigurationLoader yamlConfigurationFileLoader =
ConfigurationNode node = yamlConfigurationFileLoader.load(); YAMLConfigurationLoader.builder().setPath(configFile).build();
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) { ConfigurationNode node = yamlConfigurationFileLoader.load();
handleOldConfig(dataFolder, "config.yml", "config.yml"); if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
node = yamlConfigurationFileLoader.load(); handleOldConfig(dataFolder, "config.yml", "config.yml");
} node = yamlConfigurationFileLoader.load();
final boolean useSSL = node.getNode("useSSL").getBoolean(false); }
final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true); final boolean useSSL = node.getNode("useSSL").getBoolean(false);
String redisPassword = node.getNode("redis-password").getString(""); final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true);
String redisUsername = node.getNode("redis-username").getString(""); String redisPassword = node.getNode("redis-password").getString("");
String networkId = node.getNode("network-id").getString("main"); String redisUsername = node.getNode("redis-username").getString("");
String proxyId = node.getNode("proxy-id").getString("proxy-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); final int maxConnections = node.getNode("max-redis-connections").getInt(10);
List<String> exemptAddresses; List<String> exemptAddresses;
try { try {
exemptAddresses = node.getNode("exempt-ip-addresses").getList(TypeToken.of(String.class)); exemptAddresses = node.getNode("exempt-ip-addresses").getList(TypeToken.of(String.class));
} catch (ObjectMappingException e) { } catch (ObjectMappingException e) {
exemptAddresses = Collections.emptyList(); exemptAddresses = Collections.emptyList();
} }
// check redis password // check redis password
if ((redisPassword.isEmpty() || redisPassword.equals("none"))) { if ((redisPassword.isEmpty() || redisPassword.equals("none"))) {
redisPassword = null; redisPassword = null;
plugin.logWarn("password is empty"); plugin.logWarn("password is empty");
} }
if ((redisUsername.isEmpty() || redisUsername.equals("none"))) { if ((redisUsername.isEmpty() || redisUsername.equals("none"))) {
redisUsername = null; redisUsername = null;
} }
// env var // env var
String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID"); String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID");
if (proxyIdFromEnv != null) { if (proxyIdFromEnv != null) {
plugin.logInfo("Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID", proxyId, proxyIdFromEnv); plugin.logInfo(
proxyId = proxyIdFromEnv; "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"); String networkIdFromEnv = System.getenv("REDISBUNGEE_NETWORK_ID");
if (networkIdFromEnv != null) { if (networkIdFromEnv != null) {
plugin.logInfo("Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID", networkId, networkIdFromEnv); plugin.logInfo(
networkId = networkIdFromEnv; "Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID",
} networkId,
networkIdFromEnv);
networkId = networkIdFromEnv;
}
// Configuration sanity checks. // Configuration sanity checks.
if (proxyId == null || proxyId.isEmpty()) { if (proxyId == null || proxyId.isEmpty()) {
String genId = UUID.randomUUID().toString(); String genId = UUID.randomUUID().toString();
plugin.logInfo("Generated proxy id " + genId + " and saving it to config."); plugin.logInfo("Generated proxy id " + genId + " and saving it to config.");
node.getNode("proxy-id").setValue(genId); node.getNode("proxy-id").setValue(genId);
yamlConfigurationFileLoader.save(node); yamlConfigurationFileLoader.save(node);
proxyId = genId; proxyId = genId;
plugin.logInfo("proxy id was generated: " + proxyId); plugin.logInfo("proxy id was generated: " + proxyId);
} else { } else {
plugin.logInfo("Loaded proxy id " + proxyId); plugin.logInfo("Loaded proxy id " + proxyId);
} }
if (networkId.isEmpty()) { if (networkId.isEmpty()) {
networkId = "main"; networkId = "main";
plugin.logWarn("network id was empty and replaced with 'main'"); plugin.logWarn("network id was empty and replaced with 'main'");
} }
plugin.logInfo("Loaded network id " + networkId); plugin.logInfo("Loaded network id " + networkId);
// TO avoid proxies from different platforms from seeing each other. // TO avoid proxies from different platforms from seeing each other.
networkId = plugin.platformId() + "-" + networkId; networkId = plugin.platformId() + "-" + networkId;
plugin.logInfo("Platform is {} so network id is now is {}", plugin.platformId(), networkId); plugin.logInfo("Platform is {} so network id is now is {}", plugin.platformId(), networkId);
boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean(); boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean();
boolean handleMotd = node.getNode("handle-motd").getBoolean(true); boolean handleMotd = node.getNode("handle-motd").getBoolean(true);
plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer); plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer);
plugin.logInfo("handle motd: {}", handleMotd); plugin.logInfo("handle motd: {}", handleMotd);
HandleMotdOrder handleMotdOrder = HandleMotdOrder.NORMAL; HandleMotdOrder handleMotdOrder = HandleMotdOrder.NORMAL;
String handleMotdOrderName = node.getNode("handle-motd-priority").getString(); String handleMotdOrderName = node.getNode("handle-motd-priority").getString();
if (handleMotdOrderName != null) { if (handleMotdOrderName != null) {
try { try {
handleMotdOrder = HandleMotdOrder.valueOf(handleMotdOrderName.toUpperCase(Locale.ROOT)); handleMotdOrder = HandleMotdOrder.valueOf(handleMotdOrderName.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
plugin.logWarn("handle motd order value '{}' is unsupported (allowed: {})", handleMotdOrderName, HandleMotdOrder.values()); plugin.logWarn(
} "handle motd order value '{}' is unsupported (allowed: {})",
} handleMotdOrderName,
plugin.logInfo("handle motd order: {}", handleMotdOrder); HandleMotdOrder.values());
}
}
plugin.logInfo("handle motd order: {}", handleMotdOrder);
// commands // commands
boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(true); boolean redisBungeeEnabled =
boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(false); 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 glistEnabled =
boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(false); node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled")
boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(false); .getBoolean(false);
boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(false); boolean findEnabled =
boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(false); node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled")
boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(false); .getBoolean(false);
boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(false); boolean lastseenEnabled =
boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(false); node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled")
boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(false); .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 installGlist =
boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(false); node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install")
boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(false); .getBoolean(false);
boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(false); boolean installFind =
boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(false); node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install")
boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(false); .getBoolean(false);
boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(false); boolean installLastseen =
boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(false); node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install")
boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(false); .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 =
RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(networkId, proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, handleMotdOrder, new RedisBungeeConfiguration(
networkId,
proxyId,
exemptAddresses,
kickWhenOnline,
reconnectToLastServer,
handleMotd,
handleMotdOrder,
new RedisBungeeConfiguration.CommandsConfiguration( new RedisBungeeConfiguration.CommandsConfiguration(
redisBungeeEnabled, redisBungeeLegacyEnabled, redisBungeeEnabled,
redisBungeeLegacyEnabled,
new RedisBungeeConfiguration.LegacySubCommandsConfiguration( new RedisBungeeConfiguration.LegacySubCommandsConfiguration(
findEnabled, glistEnabled, ipEnabled, findEnabled,
lastseenEnabled, pListEnabled, pproxyEnabled, glistEnabled,
sendToAllEnabled, serverIdEnabled, serverIdsEnabled, ipEnabled,
installFind, installGlist, installIp, lastseenEnabled,
installLastseen, installPlist, installPproxy, pListEnabled,
installSendToAll, installServerid, installServerIds) pproxyEnabled,
)); sendToAllEnabled,
Summoner<?> summoner; serverIdEnabled,
RedisBungeeMode redisBungeeMode; serverIdsEnabled,
if (useSSL) { installFind,
plugin.logInfo("Using ssl"); installGlist,
} installIp,
if (node.getNode("cluster-mode-enabled").getBoolean(false)) { installLastseen,
plugin.logInfo("RedisBungee MODE: CLUSTER"); installPlist,
Set<HostAndPort> hostAndPortSet = new HashSet<>(); installPproxy,
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>(); installSendToAll,
poolConfig.setMaxTotal(maxConnections); installServerid,
poolConfig.setBlockWhenExhausted(true); installServerIds)));
node.getNode("redis-cluster-servers").getChildrenList().forEach((childNode) -> { Summoner<?> summoner;
RedisBungeeMode redisBungeeMode;
if (useSSL) {
plugin.logInfo("Using ssl");
}
if (node.getNode("cluster-mode-enabled").getBoolean(false)) {
plugin.logInfo("RedisBungee MODE: CLUSTER");
Set<HostAndPort> hostAndPortSet = new HashSet<>();
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(maxConnections);
poolConfig.setBlockWhenExhausted(true);
node.getNode("redis-cluster-servers")
.getChildrenList()
.forEach(
(childNode) -> {
Map<Object, ? extends ConfigurationNode> hostAndPort = childNode.getChildrenMap(); Map<Object, ? extends ConfigurationNode> hostAndPort = childNode.getChildrenMap();
String host = hostAndPort.get("host").getString(); String host = hostAndPort.get("host").getString();
int port = hostAndPort.get("port").getInt(); int port = hostAndPort.get("port").getInt();
hostAndPortSet.add(new HostAndPort(host, port)); hostAndPortSet.add(new HostAndPort(host, port));
}); });
plugin.logInfo(hostAndPortSet.size() + " cluster nodes were specified"); plugin.logInfo(hostAndPortSet.size() + " cluster nodes were specified");
if (hostAndPortSet.isEmpty()) { if (hostAndPortSet.isEmpty()) {
throw new RuntimeException("No redis cluster servers specified"); throw new RuntimeException("No redis cluster servers specified");
} }
summoner = new JedisClusterSummoner(new ClusterConnectionProvider(hostAndPortSet, DefaultJedisClientConfig.builder().user(redisUsername).password(redisPassword).ssl(useSSL).socketTimeoutMillis(5000).timeoutMillis(10000).build(), poolConfig)); summoner =
redisBungeeMode = RedisBungeeMode.CLUSTER; new JedisClusterSummoner(
} else { new ClusterConnectionProvider(
plugin.logInfo("RedisBungee MODE: SINGLE"); hostAndPortSet,
final String redisServer = node.getNode("redis-server").getString("127.0.0.1"); DefaultJedisClientConfig.builder()
final int redisPort = node.getNode("redis-port").getInt(6379); .user(redisUsername)
if (redisServer != null && redisServer.isEmpty()) { .password(redisPassword)
throw new RuntimeException("No redis server specified"); .ssl(useSSL)
} .socketTimeoutMillis(5000)
JedisPool jedisPool = null; .timeoutMillis(10000)
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) { .build(),
JedisPoolConfig config = new JedisPoolConfig(); poolConfig));
config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3)); redisBungeeMode = RedisBungeeMode.CLUSTER;
config.setBlockWhenExhausted(true); } else {
jedisPool = new JedisPool(config, redisServer, redisPort, 5000, redisUsername, redisPassword, useSSL); plugin.logInfo("RedisBungee MODE: SINGLE");
plugin.logInfo("Compatibility JedisPool was created"); final String redisServer = node.getNode("redis-server").getString("127.0.0.1");
} final int redisPort = node.getNode("redis-port").getInt(6379);
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>(); if (redisServer != null && redisServer.isEmpty()) {
poolConfig.setMaxTotal(maxConnections); throw new RuntimeException("No redis server specified");
poolConfig.setBlockWhenExhausted(true); }
summoner = new JedisPooledSummoner(new PooledConnectionProvider(new ConnectionFactory(new HostAndPort(redisServer, redisPort), DefaultJedisClientConfig.builder().user(redisUsername).timeoutMillis(5000).ssl(useSSL).password(redisPassword).build()), poolConfig), jedisPool); JedisPool jedisPool = null;
redisBungeeMode = RedisBungeeMode.SINGLE; if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) {
} JedisPoolConfig config = new JedisPoolConfig();
plugin.logInfo("Successfully connected to Redis."); config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3));
onConfigLoad(configuration, summoner, redisBungeeMode); config.setBlockWhenExhausted(true);
jedisPool =
new JedisPool(
config, redisServer, redisPort, 5000, redisUsername, redisPassword, useSSL);
plugin.logInfo("Compatibility JedisPool was created");
}
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(maxConnections);
poolConfig.setBlockWhenExhausted(true);
summoner =
new JedisPooledSummoner(
new PooledConnectionProvider(
new ConnectionFactory(
new HostAndPort(redisServer, redisPort),
DefaultJedisClientConfig.builder()
.user(redisUsername)
.timeoutMillis(5000)
.ssl(useSSL)
.password(redisPassword)
.build()),
poolConfig),
jedisPool);
redisBungeeMode = RedisBungeeMode.SINGLE;
} }
plugin.logInfo("Successfully connected to Redis.");
onConfigLoad(configuration, summoner, redisBungeeMode);
}
void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode); void onConfigLoad(
RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode);
} }

View File

@ -1,58 +1,56 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.config.loaders; package com.imaginarycode.minecraft.redisbungee.api.config.loaders;
import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.time.Instant; import java.time.Instant;
import org.jetbrains.annotations.Nullable;
public interface GenericConfigLoader { public interface GenericConfigLoader {
// CHANGES on every reboot // CHANGES on every reboot
String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond(); String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond();
default Path createConfigFile(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException { default Path createConfigFile(
if (Files.notExists(dataFolder)) { Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
Files.createDirectory(dataFolder); 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;
} }
Path file = dataFolder.resolve(configFile);
default void handleOldConfig(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException { if (Files.notExists(file) && defaultResourceID != null) {
Path oldConfigFolder = dataFolder.resolve("old_config"); try (InputStream in = getClass().getClassLoader().getResourceAsStream(defaultResourceID)) {
if (Files.notExists(oldConfigFolder)) { Files.createFile(file);
Files.createDirectory(oldConfigFolder); assert in != null;
} Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
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);
} }
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);
}
} }

View File

@ -1,33 +1,33 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.events; package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID; import java.util.UUID;
/** /**
* Since each platform have their own events' implementation for example Bungeecord events extends Event while velocity don't * Since each platform have their own events' implementation for example Bungeecord events extends
* Event while velocity don't
* *
* @author Ham1255 * @author Ham1255
* @since 0.7.0 * @since 0.7.0
*/ */
public interface EventsPlatform { public interface EventsPlatform {
IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server); IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(
UUID uuid, String previousServer, String server);
IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid); IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid);
IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid); IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid);
IPubSubMessageEvent createPubSubEvent(String channel, String message); IPubSubMessageEvent createPubSubEvent(String channel, String message);
void fireEvent(Object event);
void fireEvent(Object event);
} }

View File

@ -1,23 +1,21 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.events; package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID; import java.util.UUID;
public interface IPlayerChangedServerNetworkEvent extends RedisBungeeEvent { public interface IPlayerChangedServerNetworkEvent extends RedisBungeeEvent {
UUID getUuid(); UUID getUuid();
String getServer(); String getServer();
String getPreviousServer();
String getPreviousServer();
} }

View File

@ -1,19 +1,17 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.events; package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID; import java.util.UUID;
public interface IPlayerJoinedNetworkEvent extends RedisBungeeEvent { public interface IPlayerJoinedNetworkEvent extends RedisBungeeEvent {
UUID getUuid(); UUID getUuid();
} }

View File

@ -1,19 +1,17 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.events; package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID; import java.util.UUID;
public interface IPlayerLeftNetworkEvent extends RedisBungeeEvent { public interface IPlayerLeftNetworkEvent extends RedisBungeeEvent {
UUID getUuid(); UUID getUuid();
} }

View File

@ -1,20 +1,17 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.events; package com.imaginarycode.minecraft.redisbungee.api.events;
public interface IPubSubMessageEvent extends RedisBungeeEvent { public interface IPubSubMessageEvent extends RedisBungeeEvent {
String getChannel(); String getChannel();
String getMessage();
String getMessage();
} }

View File

@ -1,14 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.events; package com.imaginarycode.minecraft.redisbungee.api.events;
interface RedisBungeeEvent { interface RedisBungeeEvent {}
}

View File

@ -1,24 +1,31 @@
/*
* Copyright (c) 2026 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; package com.imaginarycode.minecraft.redisbungee.api.payloads;
public abstract class AbstractPayload { public abstract class AbstractPayload {
private final String senderProxy; private final String senderProxy;
public AbstractPayload(String proxyId) { public AbstractPayload(String proxyId) {
this.senderProxy = proxyId; this.senderProxy = proxyId;
} }
public AbstractPayload(String senderProxy, String className) { public AbstractPayload(String senderProxy, String className) {
this.senderProxy = senderProxy; this.senderProxy = senderProxy;
} }
public String senderProxy() { public String senderProxy() {
return senderProxy; return senderProxy;
} }
public String getClassName() {
return getClass().getName();
}
public String getClassName() {
return getClass().getName();
}
} }

View File

@ -1,34 +1,35 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.gson; package com.imaginarycode.minecraft.redisbungee.api.payloads.gson;
import com.google.gson.*; import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
import java.lang.reflect.Type; import java.lang.reflect.Type;
public class AbstractPayloadSerializer implements JsonSerializer<AbstractPayload>, JsonDeserializer<AbstractPayload> { public class AbstractPayloadSerializer
implements JsonSerializer<AbstractPayload>, JsonDeserializer<AbstractPayload> {
@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 @Override
public AbstractPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public JsonElement serialize(
JsonObject jsonObject = json.getAsJsonObject(); AbstractPayload src, Type typeOfSrc, JsonSerializationContext context) {
return new AbstractPayload(jsonObject.get("proxy").getAsString(), jsonObject.get("class").getAsString()) { JsonObject jsonObject = new JsonObject();
}; jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
} return jsonObject;
}
@Override
public JsonElement serialize(AbstractPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
return jsonObject;
}
} }

View File

@ -1,19 +1,18 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class DeathPayload extends AbstractPayload { public class DeathPayload extends AbstractPayload {
public DeathPayload(String proxyId) { public DeathPayload(String proxyId) {
super(proxyId); super(proxyId);
} }
} }

View File

@ -1,31 +1,28 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class HeartbeatPayload extends AbstractPayload { public class HeartbeatPayload extends AbstractPayload {
public record HeartbeatData(long heartbeat, int players) { public record HeartbeatData(long heartbeat, int players) {}
} private final HeartbeatData data;
private final HeartbeatData data; public HeartbeatPayload(String proxyId, HeartbeatData data) {
super(proxyId);
this.data = data;
}
public HeartbeatPayload(String proxyId, HeartbeatData data) { public HeartbeatData data() {
super(proxyId); return data;
this.data = data; }
}
public HeartbeatData data() {
return data;
}
} }

View File

@ -1,34 +1,32 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class PubSubPayload extends AbstractPayload { public class PubSubPayload extends AbstractPayload {
private final String channel; private final String channel;
private final String message; private final String message;
public PubSubPayload(String proxyId, String channel, String message) {
super(proxyId);
this.channel = channel;
this.message = message;
}
public PubSubPayload(String proxyId, String channel, String message) { public String channel() {
super(proxyId); return channel;
this.channel = channel; }
this.message = message;
}
public String channel() { public String message() {
return channel; return message;
} }
public String message() {
return message;
}
} }

View File

@ -1,36 +1,33 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class RunCommandPayload extends AbstractPayload { public class RunCommandPayload extends AbstractPayload {
private final String proxyToRun;
private final String proxyToRun; private final String command;
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 RunCommandPayload(String proxyId, String proxyToRun, String command) { public String command() {
super(proxyId); return command;
this.proxyToRun = proxyToRun; }
this.command = command;
}
public String proxyToRun() {
return proxyToRun;
}
public String command() {
return command;
}
} }

View File

@ -1,36 +1,36 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*; import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload;
import java.lang.reflect.Type; import java.lang.reflect.Type;
public class DeathPayloadSerializer implements JsonSerializer<DeathPayload>, JsonDeserializer<DeathPayload> { public class DeathPayloadSerializer
implements JsonSerializer<DeathPayload>, JsonDeserializer<DeathPayload> {
private static final Gson gson = new Gson(); 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 @Override
public DeathPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = json.getAsJsonObject(); JsonObject jsonObject = new JsonObject();
String senderProxy = jsonObject.get("proxy").getAsString(); jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
return new DeathPayload(senderProxy); return jsonObject;
} }
@Override
public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
return jsonObject;
}
} }

View File

@ -1,38 +1,40 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*; import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload;
import java.lang.reflect.Type; import java.lang.reflect.Type;
public class HeartbeatPayloadSerializer implements JsonSerializer<HeartbeatPayload>, JsonDeserializer<HeartbeatPayload> { public class HeartbeatPayloadSerializer
implements JsonSerializer<HeartbeatPayload>, JsonDeserializer<HeartbeatPayload> {
@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 @Override
public HeartbeatPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public JsonElement serialize(
JsonObject jsonObject = json.getAsJsonObject(); HeartbeatPayload src, Type typeOfSrc, JsonSerializationContext context) {
String senderProxy = jsonObject.get("proxy").getAsString(); JsonObject jsonObject = new JsonObject();
long heartbeat = jsonObject.get("heartbeat").getAsLong(); jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
int players = jsonObject.get("players").getAsInt(); jsonObject.add("heartbeat", new JsonPrimitive(src.data().heartbeat()));
return new HeartbeatPayload(senderProxy, new HeartbeatPayload.HeartbeatData(heartbeat, players)); jsonObject.add("players", new JsonPrimitive(src.data().players()));
} return jsonObject;
}
@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;
}
} }

View File

@ -1,40 +1,41 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*; import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload;
import java.lang.reflect.Type; import java.lang.reflect.Type;
public class PubSubPayloadSerializer implements JsonSerializer<PubSubPayload>, JsonDeserializer<PubSubPayload> { public class PubSubPayloadSerializer
implements JsonSerializer<PubSubPayload>, JsonDeserializer<PubSubPayload> {
private static final Gson gson = new Gson(); 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 @Override
public PubSubPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public JsonElement serialize(
JsonObject jsonObject = json.getAsJsonObject(); PubSubPayload src, Type typeOfSrc, JsonSerializationContext context) {
String senderProxy = jsonObject.get("proxy").getAsString(); JsonObject jsonObject = new JsonObject();
String channel = jsonObject.get("channel").getAsString(); jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
String message = jsonObject.get("message").getAsString(); jsonObject.add("channel", new JsonPrimitive(src.channel()));
return new PubSubPayload(senderProxy, channel, message); jsonObject.add("message", context.serialize(src.message()));
} return jsonObject;
}
@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;
}
} }

View File

@ -1,38 +1,39 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*; import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload; import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload;
import java.lang.reflect.Type; import java.lang.reflect.Type;
public class RunCommandPayloadSerializer implements JsonSerializer<RunCommandPayload>, JsonDeserializer<RunCommandPayload> { public class RunCommandPayloadSerializer
implements JsonSerializer<RunCommandPayload>, JsonDeserializer<RunCommandPayload> {
@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 @Override
public RunCommandPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public JsonElement serialize(
JsonObject jsonObject = json.getAsJsonObject(); RunCommandPayload src, Type typeOfSrc, JsonSerializationContext context) {
String senderProxy = jsonObject.get("proxy").getAsString(); JsonObject jsonObject = new JsonObject();
String proxyToRun = jsonObject.get("proxy-to-run").getAsString(); jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
String command = jsonObject.get("command").getAsString(); jsonObject.add("proxy-to-run", new JsonPrimitive(src.proxyToRun()));
return new RunCommandPayload(senderProxy, proxyToRun, command); jsonObject.add("command", context.serialize(src.command()));
} return jsonObject;
}
@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;
}
} }

View File

@ -1,42 +1,37 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.summoners; package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.providers.ClusterConnectionProvider;
public class JedisClusterSummoner implements Summoner<JedisCluster> { public class JedisClusterSummoner implements Summoner<JedisCluster> {
private final ClusterConnectionProvider clusterConnectionProvider; private final ClusterConnectionProvider clusterConnectionProvider;
public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) { public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) {
this.clusterConnectionProvider = clusterConnectionProvider; this.clusterConnectionProvider = clusterConnectionProvider;
// test the connection // test the connection
JedisCluster jedisCluster = obtainResource(); JedisCluster jedisCluster = obtainResource();
jedisCluster.set("random_data", "0"); jedisCluster.set("random_data", "0");
jedisCluster.del("random_data"); jedisCluster.del("random_data");
} }
@Override
public void close() throws IOException {
this.clusterConnectionProvider.close();
}
@Override
public JedisCluster obtainResource() {
return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(10));
}
@Override
public void close() throws IOException {
this.clusterConnectionProvider.close();
}
@Override
public JedisCluster obtainResource() {
return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(10));
}
} }

View File

@ -1,60 +1,55 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.summoners; package com.imaginarycode.minecraft.redisbungee.api.summoners;
import java.io.IOException;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPooled; import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.providers.PooledConnectionProvider;
import java.io.IOException;
public class JedisPooledSummoner implements Summoner<JedisPooled> { public class JedisPooledSummoner implements Summoner<JedisPooled> {
private final PooledConnectionProvider connectionProvider; private final PooledConnectionProvider connectionProvider;
private final JedisPool jedisPool; private final JedisPool jedisPool;
public JedisPooledSummoner(PooledConnectionProvider connectionProvider, JedisPool jedisPool) {
this.connectionProvider = connectionProvider;
this.jedisPool = jedisPool;
// test connections
if (jedisPool != null) {
try (Jedis jedis = this.jedisPool.getResource()) {
// Test the connection to make sure configuration is right
jedis.ping();
}
}
final JedisPooled jedisPooled = this.obtainResource();
jedisPooled.set("random_data", "0");
jedisPooled.del("random_data");
public JedisPooledSummoner(PooledConnectionProvider connectionProvider, JedisPool jedisPool) {
this.connectionProvider = connectionProvider;
this.jedisPool = jedisPool;
// test connections
if (jedisPool != null) {
try (Jedis jedis = this.jedisPool.getResource()) {
// Test the connection to make sure configuration is right
jedis.ping();
}
} }
final JedisPooled jedisPooled = this.obtainResource();
jedisPooled.set("random_data", "0");
jedisPooled.del("random_data");
}
@Override @Override
public JedisPooled obtainResource() { public JedisPooled obtainResource() {
// create UnClosable JedisPool *disposable* // create UnClosable JedisPool *disposable*
return new NotClosableJedisPooled(this.connectionProvider); return new NotClosableJedisPooled(this.connectionProvider);
} }
public JedisPool getCompatibilityJedisPool() { public JedisPool getCompatibilityJedisPool() {
return this.jedisPool; return this.jedisPool;
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (this.jedisPool != null) { if (this.jedisPool != null) {
this.jedisPool.close(); this.jedisPool.close();
}
this.connectionProvider.close();
} }
this.connectionProvider.close();
}
} }

View File

@ -1,29 +1,25 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.summoners; package com.imaginarycode.minecraft.redisbungee.api.summoners;
import java.time.Duration;
import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.providers.ClusterConnectionProvider;
import java.time.Duration;
public class NotClosableJedisCluster extends JedisCluster { public class NotClosableJedisCluster extends JedisCluster {
NotClosableJedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) { NotClosableJedisCluster(
super(provider, maxAttempts, maxTotalRetriesDuration); ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) {
} super(provider, maxAttempts, maxTotalRetriesDuration);
}
@Override @Override
public void close() { public void close() {}
}
} }

View File

@ -1,26 +1,22 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.summoners; package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.JedisPooled; import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.providers.PooledConnectionProvider;
public class NotClosableJedisPooled extends JedisPooled { public class NotClosableJedisPooled extends JedisPooled {
NotClosableJedisPooled(PooledConnectionProvider provider) { NotClosableJedisPooled(PooledConnectionProvider provider) {
super(provider); super(provider);
} }
@Override @Override
public void close() { public void close() {}
}
} }

View File

@ -1,19 +1,16 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.summoners; package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.UnifiedJedis;
import java.io.Closeable; import java.io.Closeable;
import redis.clients.jedis.UnifiedJedis;
/** /**
* This class intended for future release to support redis sentinel or redis clusters * This class intended for future release to support redis sentinel or redis clusters
@ -23,6 +20,5 @@ import java.io.Closeable;
*/ */
public interface Summoner<P extends UnifiedJedis> extends Closeable { public interface Summoner<P extends UnifiedJedis> extends Closeable {
P obtainResource(); P obtainResource();
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.tasks; package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
@ -16,34 +15,30 @@ import redis.clients.jedis.*;
public abstract class RedisPipelineTask<T> extends RedisTask<T> { public abstract class RedisPipelineTask<T> extends RedisTask<T> {
public RedisPipelineTask(AbstractRedisBungeeAPI api) {
super(api);
}
public RedisPipelineTask(AbstractRedisBungeeAPI api) { public RedisPipelineTask(RedisBungeePlugin<?> plugin) {
super(api); 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);
}
} }
public RedisPipelineTask(RedisBungeePlugin<?> plugin) { return null;
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);
public abstract T doPooledPipeline(Pipeline pipeline);
public abstract T clusterPipeline(ClusterPipeline pipeline);
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.tasks; package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
@ -16,52 +15,51 @@ import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import java.util.concurrent.Callable;
import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.UnifiedJedis;
import java.util.concurrent.Callable;
/** /**
* Since Jedis now have UnifiedJedis which basically extended by cluster / single connections classes * Since Jedis now have UnifiedJedis which basically extended by cluster / single connections
* can help us to have shared code. * classes can help us to have shared code.
*/ */
public abstract class RedisTask<V> implements Runnable, Callable<V> { public abstract class RedisTask<V> implements Runnable, Callable<V> {
protected final Summoner<?> summoner; protected final Summoner<?> summoner;
protected final RedisBungeeMode mode; protected final RedisBungeeMode mode;
@Override @Override
public V call() throws Exception { public V call() throws Exception {
return this.execute(); return this.execute();
}
public RedisTask(AbstractRedisBungeeAPI api) {
this.summoner = api.getSummoner();
this.mode = api.getMode();
}
public RedisTask(RedisBungeePlugin<?> plugin) {
this.summoner = plugin.getSummoner();
this.mode = plugin.getRedisBungeeMode();
}
public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis);
@Override
public void run() {
this.execute();
}
public V execute() {
// JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its
// single instance anyway.
if (mode == RedisBungeeMode.SINGLE) {
JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner;
return this.unifiedJedisTask(jedisSummoner.obtainResource());
} else if (mode == RedisBungeeMode.CLUSTER) {
JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner;
return this.unifiedJedisTask(jedisClusterSummoner.obtainResource());
} }
return null;
public RedisTask(AbstractRedisBungeeAPI api) { }
this.summoner = api.getSummoner();
this.mode = api.getMode();
}
public RedisTask(RedisBungeePlugin<?> plugin) {
this.summoner = plugin.getSummoner();
this.mode = plugin.getRedisBungeeMode();
}
public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis);
@Override
public void run() {
this.execute();
}
public V execute() {
// JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its single instance anyway.
if (mode == RedisBungeeMode.SINGLE) {
JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner;
return this.unifiedJedisTask(jedisSummoner.obtainResource());
} else if (mode == RedisBungeeMode.CLUSTER) {
JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner;
return this.unifiedJedisTask(jedisClusterSummoner.obtainResource());
}
return null;
}
} }

View File

@ -1,56 +1,54 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.tasks; package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.CachedUUIDEntry; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.CachedUUIDEntry;
import java.util.ArrayList;
import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.exceptions.JedisException;
import java.util.ArrayList; public class UUIDCleanupTask extends RedisTask<Void> {
private final Gson gson = new Gson();
private final RedisBungeePlugin<?> plugin;
public class UUIDCleanupTask extends RedisTask<Void>{ public UUIDCleanupTask(RedisBungeePlugin<?> plugin) {
super(plugin);
this.plugin = plugin;
}
private final Gson gson = new Gson(); // this code is inspired from https://github.com/minecrafter/redisbungeeclean
private final RedisBungeePlugin<?> plugin; @Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
public UUIDCleanupTask(RedisBungeePlugin<?> plugin) { try {
super(plugin); final long number = unifiedJedis.hlen("uuid-cache");
this.plugin = plugin; plugin.logInfo("Found {} entries", number);
} ArrayList<String> fieldsToRemove = new ArrayList<>();
unifiedJedis
// this code is inspired from https://github.com/minecrafter/redisbungeeclean .hgetAll("uuid-cache")
@Override .forEach(
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { (field, data) -> {
try {
final long number = unifiedJedis.hlen("uuid-cache");
plugin.logInfo("Found {} entries", number);
ArrayList<String> fieldsToRemove = new ArrayList<>();
unifiedJedis.hgetAll("uuid-cache").forEach((field, data) -> {
CachedUUIDEntry cachedUUIDEntry = gson.fromJson(data, CachedUUIDEntry.class); CachedUUIDEntry cachedUUIDEntry = gson.fromJson(data, CachedUUIDEntry.class);
if (cachedUUIDEntry.expired()) { if (cachedUUIDEntry.expired()) {
fieldsToRemove.add(field); fieldsToRemove.add(field);
} }
}); });
if (!fieldsToRemove.isEmpty()) { if (!fieldsToRemove.isEmpty()) {
unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0])); unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0]));
} }
plugin.logInfo("deleted {} entries", fieldsToRemove.size()); plugin.logInfo("deleted {} entries", fieldsToRemove.size());
} catch (JedisException e) { } catch (JedisException e) {
plugin.logFatal("There was an error fetching information", e); plugin.logFatal("There was an error fetching information", e);
}
return null;
} }
return null;
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.util; package com.imaginarycode.minecraft.redisbungee.api.util;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
@ -15,34 +14,39 @@ import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol;
import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.UnifiedJedis;
public class InitialUtils { public class InitialUtils {
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) { public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) { new RedisTask<Void>(plugin) {
@Override @Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
// This is more portable than INFO <section> // This is more portable than INFO <section>
String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO)); String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO));
for (String s : info.split("\r\n")) { for (String s : info.split("\r\n")) {
if (s.startsWith("redis_version:")) { if (s.startsWith("redis_version:")) {
String version = s.split(":")[1]; String version = s.split(":")[1];
plugin.logInfo("Redis server version: " + version); plugin.logInfo("Redis server version: " + version);
if (!RedisUtil.isRedisVersionRight(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."); plugin.logFatal(
throw new RuntimeException("Unsupported Redis version detected"); "Your version of Redis ("
} + version
long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); + ") is not at least version "
if (uuidCacheSize > 750000) { + RedisUtil.MAJOR_VERSION
plugin.logInfo("Looks like you have a really big UUID cache! Run '/rb clean' to remove expired cache entries"); + "."
} + RedisUtil.MINOR_VERSION
break; + " RedisBungee requires a newer version of Redis.");
} throw new RuntimeException("Unsupported Redis version detected");
}
return null;
} }
}.execute(); long uuidCacheSize = unifiedJedis.hlen("uuid-cache");
} if (uuidCacheSize > 750000) {
plugin.logInfo(
"Looks like you have a really big UUID cache! Run '/rb clean' to remove expired cache entries");
}
break;
}
}
return null;
}
}.execute();
}
} }

View File

@ -1,31 +1,39 @@
/*
* Copyright (c) 2026 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; package com.imaginarycode.minecraft.redisbungee.api.util;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@VisibleForTesting @VisibleForTesting
public class RedisUtil { public class RedisUtil {
public final static int PROXY_TIMEOUT = 30; public static final int PROXY_TIMEOUT = 30;
public static final int MAJOR_VERSION = 6; public static final int MAJOR_VERSION = 6;
public static final int MINOR_VERSION = 2; public static final int MINOR_VERSION = 2;
public static boolean isRedisVersionRight(String redisVersion) {
String[] args = redisVersion.split("\\.");
if (args.length < 2) {
return false;
}
int major = Integer.parseInt(args[0]);
int minor = Integer.parseInt(args[1]);
if (major > MAJOR_VERSION) return true;
return major == MAJOR_VERSION && minor >= MINOR_VERSION;
public static boolean isRedisVersionRight(String redisVersion) {
String[] args = redisVersion.split("\\.");
if (args.length < 2) {
return false;
} }
int major = Integer.parseInt(args[0]);
int minor = Integer.parseInt(args[1]);
// Ham1255: i am keeping this if some plugin uses this *IF* if (major > MAJOR_VERSION) return true;
@Deprecated return major == MAJOR_VERSION && minor >= MINOR_VERSION;
public static boolean canUseLua(String redisVersion) { }
// Need to use >=3 to use Lua optimizations.
return isRedisVersionRight(redisVersion); // Ham1255: i am keeping this if some plugin uses this *IF*
} @Deprecated
public static boolean canUseLua(String redisVersion) {
// Need to use >=3 to use Lua optimizations.
return isRedisVersionRight(redisVersion);
}
} }

View File

@ -1,40 +1,48 @@
/*
* Copyright (c) 2026 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.serialize; package com.imaginarycode.minecraft.redisbungee.api.util.serialize;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset; import com.google.common.collect.Multiset;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
public class MultiMapSerialization { public class MultiMapSerialization {
public static void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) { public static void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
output.writeInt(collection.elementSet().size()); output.writeInt(collection.elementSet().size());
for (Multiset.Entry<String> entry : collection.entrySet()) { for (Multiset.Entry<String> entry : collection.entrySet()) {
output.writeUTF(entry.getElement()); output.writeUTF(entry.getElement());
output.writeInt(entry.getCount()); output.writeInt(entry.getCount());
}
} }
}
@SuppressWarnings("SameParameterValue") @SuppressWarnings("SameParameterValue")
public static void serializeMultimap(Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) { public static void serializeMultimap(
output.writeInt(collection.keySet().size()); Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) {
for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) { output.writeInt(collection.keySet().size());
output.writeUTF(entry.getKey()); for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) {
if (includeNames) { output.writeUTF(entry.getKey());
serializeCollection(entry.getValue(), output); if (includeNames) {
} else { serializeCollection(entry.getValue(), output);
output.writeInt(entry.getValue().size()); } else {
} output.writeInt(entry.getValue().size());
} }
} }
}
public static void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) { public static void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) {
output.writeInt(collection.size()); output.writeInt(collection.size());
for (Object o : collection) { for (Object o : collection) {
output.writeUTF(o.toString()); output.writeUTF(o.toString());
}
} }
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.util.uuid; package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import java.util.Calendar; import java.util.Calendar;
@ -15,7 +14,7 @@ import java.util.UUID;
public record CachedUUIDEntry(String name, UUID uuid, Calendar expiry) { public record CachedUUIDEntry(String name, UUID uuid, Calendar expiry) {
public boolean expired() { public boolean expired() {
return Calendar.getInstance().after(expiry); return Calendar.getInstance().after(expiry);
} }
} }

View File

@ -1,55 +1,54 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.util.uuid; package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
public class NameFetcher { public class NameFetcher {
private static final OkHttpClient httpClient = new OkHttpClient(); private static final OkHttpClient httpClient = new OkHttpClient();
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException { public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
String name = getName(uuid); String name = getName(uuid);
if (name == null) return Collections.emptyList(); if (name == null) return Collections.emptyList();
return Collections.singletonList(name); return Collections.singletonList(name);
} }
public static String getName(UUID uuid) throws IOException { public static String getName(UUID uuid) throws IOException {
String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString(); String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString();
Request request = new Request.Builder() Request request =
.addHeader("User-Agent", "RedisBungee-ProxioDev") new Request.Builder()
.url(url) .addHeader("User-Agent", "RedisBungee-ProxioDev")
.get() .url(url)
.build(); .get()
ResponseBody body = httpClient.newCall(request).execute().body(); .build();
String response = body.string(); ResponseBody body = httpClient.newCall(request).execute().body();
body.close(); String response = body.string();
body.close();
JsonObject json = gson.fromJson(response, JsonObject.class); JsonObject json = gson.fromJson(response, JsonObject.class);
if (!json.has("success") || !json.get("success").getAsBoolean()) return null; if (!json.has("success") || !json.get("success").getAsBoolean()) return null;
if (!json.has("data")) return null; if (!json.has("data")) return null;
JsonObject data = json.getAsJsonObject("data"); JsonObject data = json.getAsJsonObject("data");
if (!data.has("player")) return null; if (!data.has("player")) return null;
JsonObject player = data.getAsJsonObject("player"); JsonObject player = data.getAsJsonObject("player");
if (!player.has("username")) return null; if (!player.has("username")) return null;
return player.get("username").getAsString(); return player.get("username").getAsString();
} }
} }

View File

@ -1,71 +1,79 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.util.uuid; package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.gson.Gson; import com.google.gson.Gson;
import okhttp3.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import okhttp3.*;
/* Credits to evilmidget38 for this class. I modified it to use Gson. */ /* Credits to evilmidget38 for this class. I modified it to use Gson. */
public class UUIDFetcher implements Callable<Map<String, UUID>> { public class UUIDFetcher implements Callable<Map<String, UUID>> {
private static final double PROFILES_PER_REQUEST = 100; private static final double PROFILES_PER_REQUEST = 100;
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
private static final MediaType JSON = MediaType.parse("application/json"); private static final MediaType JSON = MediaType.parse("application/json");
private final List<String> names; private final List<String> names;
private final boolean rateLimiting; private final boolean rateLimiting;
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
private static final OkHttpClient httpClient = new OkHttpClient(); private static final OkHttpClient httpClient = new OkHttpClient();
private UUIDFetcher(List<String> names, boolean rateLimiting) { private UUIDFetcher(List<String> names, boolean rateLimiting) {
this.names = ImmutableList.copyOf(names); this.names = ImmutableList.copyOf(names);
this.rateLimiting = rateLimiting; this.rateLimiting = rateLimiting;
} }
public UUIDFetcher(List<String> names) { public UUIDFetcher(List<String> names) {
this(names, true); this(names, true);
} }
public static UUID getUUID(String id) { public static UUID getUUID(String id) {
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32)); return UUID.fromString(
} id.substring(0, 8)
+ "-"
+ id.substring(8, 12)
+ "-"
+ id.substring(12, 16)
+ "-"
+ id.substring(16, 20)
+ "-"
+ id.substring(20, 32));
}
public Map<String, UUID> call() throws Exception { public Map<String, UUID> call() throws Exception {
Map<String, UUID> uuidMap = new HashMap<>(); Map<String, UUID> uuidMap = new HashMap<>();
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST); int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
for (int i = 0; i < requests; i++) { for (int i = 0; i < requests; i++) {
String body = gson.toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size()))); String body = gson.toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
Request request = new Request.Builder().url(PROFILE_URL).post(RequestBody.create(JSON, body)).build(); Request request =
ResponseBody responseBody = httpClient.newCall(request).execute().body(); new Request.Builder().url(PROFILE_URL).post(RequestBody.create(JSON, body)).build();
String response = responseBody.string(); ResponseBody responseBody = httpClient.newCall(request).execute().body();
responseBody.close(); String response = responseBody.string();
Profile[] array = gson.fromJson(response, Profile[].class); responseBody.close();
for (Profile profile : array) { Profile[] array = gson.fromJson(response, Profile[].class);
UUID uuid = UUIDFetcher.getUUID(profile.id); for (Profile profile : array) {
uuidMap.put(profile.name, uuid); UUID uuid = UUIDFetcher.getUUID(profile.id);
} uuidMap.put(profile.name, uuid);
if (rateLimiting && i != requests - 1) { }
Thread.sleep(100L); if (rateLimiting && i != requests - 1) {
} Thread.sleep(100L);
} }
return uuidMap;
} }
return uuidMap;
}
private static class Profile { private static class Profile {
String id; String id;
String name; String name;
} }
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.api.util.uuid; package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
@ -15,194 +14,188 @@ import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; 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.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.NonNull;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisException;
public final class UUIDTranslator { public final class UUIDTranslator {
private static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"); private static final Pattern UUID_PATTERN =
private static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}"); Pattern.compile(
private final RedisBungeePlugin<?> plugin; "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
private final Map<String, CachedUUIDEntry> nameToUuidMap = new ConcurrentHashMap<>(128, 0.5f, 4); private static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
private final Map<UUID, CachedUUIDEntry> uuidToNameMap = new ConcurrentHashMap<>(128, 0.5f, 4); private final RedisBungeePlugin<?> plugin;
private static final Gson gson = new Gson(); private final Map<String, CachedUUIDEntry> nameToUuidMap = new ConcurrentHashMap<>(128, 0.5f, 4);
private final Map<UUID, CachedUUIDEntry> uuidToNameMap = new ConcurrentHashMap<>(128, 0.5f, 4);
private static final Gson gson = new Gson();
public UUIDTranslator(RedisBungeePlugin<?> plugin) { public UUIDTranslator(RedisBungeePlugin<?> plugin) {
this.plugin = plugin; this.plugin = plugin;
}
private void addToMaps(String name, UUID uuid) {
// This is why I like LocalDate...
// Cache the entry for three days.
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 3);
// Create the entry and populate the local maps
CachedUUIDEntry entry = new CachedUUIDEntry(name, uuid, calendar);
nameToUuidMap.put(name.toLowerCase(), entry);
uuidToNameMap.put(uuid, entry);
}
public UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) {
// If the player is online, give them their UUID.
// Remember, local data > remote data.
if (plugin.getPlayer(player) != null) return plugin.getPlayerUUID(player);
// Check if it exists in the map
CachedUUIDEntry cachedUUIDEntry = nameToUuidMap.get(player.toLowerCase());
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired()) return cachedUUIDEntry.uuid();
else nameToUuidMap.remove(player);
} }
private void addToMaps(String name, UUID uuid) { // Check if we can exit early
// This is why I like LocalDate... if (UUID_PATTERN.matcher(player).find()) {
return UUID.fromString(player);
// Cache the entry for three days.
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 3);
// Create the entry and populate the local maps
CachedUUIDEntry entry = new CachedUUIDEntry(name, uuid, calendar);
nameToUuidMap.put(name.toLowerCase(), entry);
uuidToNameMap.put(uuid, entry);
} }
public UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) { if (MOJANGIAN_UUID_PATTERN.matcher(player).find()) {
// If the player is online, give them their UUID. // Reconstruct the UUID
// Remember, local data > remote data. return UUIDFetcher.getUUID(player);
if (plugin.getPlayer(player) != null) }
return plugin.getPlayerUUID(player);
// Check if it exists in the map // If we are in offline mode, UUID generation is simple.
CachedUUIDEntry cachedUUIDEntry = nameToUuidMap.get(player.toLowerCase()); // We don't even have to cache the UUID, since this is easy to recalculate.
if (cachedUUIDEntry != null) { if (!plugin.isOnlineMode()) {
if (!cachedUUIDEntry.expired()) return UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
return cachedUUIDEntry.uuid(); }
else RedisTask<UUID> redisTask =
nameToUuidMap.remove(player); new RedisTask<UUID>(plugin) {
} @Override
public UUID unifiedJedisTask(UnifiedJedis unifiedJedis) {
String stored = unifiedJedis.hget("uuid-cache", player.toLowerCase());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
// Check if we can exit early // Check for expiry:
if (UUID_PATTERN.matcher(player).find()) { if (entry.expired()) {
return UUID.fromString(player); unifiedJedis.hdel("uuid-cache", player.toLowerCase());
} // Doesn't hurt to also remove the UUID entry as well.
unifiedJedis.hdel("uuid-cache", entry.uuid().toString());
if (MOJANGIAN_UUID_PATTERN.matcher(player).find()) { } else {
// Reconstruct the UUID nameToUuidMap.put(player.toLowerCase(), entry);
return UUIDFetcher.getUUID(player); uuidToNameMap.put(entry.uuid(), entry);
} return entry.uuid();
}
// If we are in offline mode, UUID generation is simple.
// We don't even have to cache the UUID, since this is easy to recalculate.
if (!plugin.isOnlineMode()) {
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
}
RedisTask<UUID> redisTask = new RedisTask<UUID>(plugin) {
@Override
public UUID unifiedJedisTask(UnifiedJedis unifiedJedis) {
String stored = unifiedJedis.hget("uuid-cache", player.toLowerCase());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
// Check for expiry:
if (entry.expired()) {
unifiedJedis.hdel("uuid-cache", player.toLowerCase());
// Doesn't hurt to also remove the UUID entry as well.
unifiedJedis.hdel("uuid-cache", entry.uuid().toString());
} else {
nameToUuidMap.put(player.toLowerCase(), entry);
uuidToNameMap.put(entry.uuid(), entry);
return entry.uuid();
}
}
// That didn't work. Let's ask Mojang.
if (!expensiveLookups || !plugin.isOnlineMode())
return null;
Map<String, UUID> uuidMap1;
try {
uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call();
} catch (Exception e) {
plugin.logFatal("Unable to fetch UUID from Mojang for " + player);
return null;
}
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
if (entry.getKey().equalsIgnoreCase(player)) {
persistInfo(entry.getKey(), entry.getValue(), unifiedJedis);
return entry.getValue();
}
}
return null;
} }
};
// Let's try Redis.
try {
return redisTask.execute();
} catch (JedisException e) {
plugin.logFatal("Unable to fetch UUID for " + player);
}
return null; // Nope, game over! // That didn't work. Let's ask Mojang.
} if (!expensiveLookups || !plugin.isOnlineMode()) return null;
public String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) { Map<String, UUID> uuidMap1;
// If the player is online, give them their UUID. try {
// Remember, local data > remote data. uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call();
if (plugin.getPlayer(player) != null) } catch (Exception e) {
return plugin.getPlayerName(player); plugin.logFatal("Unable to fetch UUID from Mojang for " + player);
return null;
// Check if it exists in the map }
CachedUUIDEntry cachedUUIDEntry = uuidToNameMap.get(player); for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
if (cachedUUIDEntry != null) { if (entry.getKey().equalsIgnoreCase(player)) {
if (!cachedUUIDEntry.expired()) persistInfo(entry.getKey(), entry.getValue(), unifiedJedis);
return cachedUUIDEntry.name(); return entry.getValue();
else }
uuidToNameMap.remove(player);
}
RedisTask<String> redisTask = new RedisTask<String>(plugin) {
@Override
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
String stored = unifiedJedis.hget("uuid-cache", player.toString());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
// Check for expiry:
if (entry.expired()) {
unifiedJedis.hdel("uuid-cache", player.toString());
// Doesn't hurt to also remove the named entry as well.
// TODO: Since UUIDs are fixed, we could look up the name and see if the UUID matches.
unifiedJedis.hdel("uuid-cache", entry.name());
} else {
nameToUuidMap.put(entry.name().toLowerCase(), entry);
uuidToNameMap.put(player, entry);
return entry.name();
}
}
if (!expensiveLookups || !plugin.isOnlineMode())
return null;
// That didn't work. Let's ask PlayerDB.
String name;
try {
name = NameFetcher.getName(player);
} catch (Exception e) {
plugin.logFatal("Unable to fetch name from PlayerDB for " + player);
return null;
}
if (name != null) {
persistInfo(name, player, unifiedJedis);
return name;
}
return null;
} }
};
// Okay, it wasn't locally cached. Let's try Redis.
try {
return redisTask.execute();
} catch (JedisException e) {
plugin.logFatal("Unable to fetch name for " + player);
return null; return null;
} }
};
// Let's try Redis.
try {
return redisTask.execute();
} catch (JedisException e) {
plugin.logFatal("Unable to fetch UUID for " + player);
} }
public void persistInfo(String name, UUID uuid, UnifiedJedis unifiedJedis) { return null; // Nope, game over!
addToMaps(name, uuid); }
String json = gson.toJson(uuidToNameMap.get(uuid));
unifiedJedis.hset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json)); public String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) {
// If the player is online, give them their UUID.
// Remember, local data > remote data.
if (plugin.getPlayer(player) != null) return plugin.getPlayerName(player);
// Check if it exists in the map
CachedUUIDEntry cachedUUIDEntry = uuidToNameMap.get(player);
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired()) return cachedUUIDEntry.name();
else uuidToNameMap.remove(player);
} }
RedisTask<String> redisTask =
new RedisTask<String>(plugin) {
@Override
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
String stored = unifiedJedis.hget("uuid-cache", player.toString());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
// Check for expiry:
if (entry.expired()) {
unifiedJedis.hdel("uuid-cache", player.toString());
// Doesn't hurt to also remove the named entry as well.
// TODO: Since UUIDs are fixed, we could look up the name and see if the UUID
// matches.
unifiedJedis.hdel("uuid-cache", entry.name());
} else {
nameToUuidMap.put(entry.name().toLowerCase(), entry);
uuidToNameMap.put(player, entry);
return entry.name();
}
}
if (!expensiveLookups || !plugin.isOnlineMode()) return null;
// That didn't work. Let's ask PlayerDB.
String name;
try {
name = NameFetcher.getName(player);
} catch (Exception e) {
plugin.logFatal("Unable to fetch name from PlayerDB for " + player);
return null;
}
if (name != null) {
persistInfo(name, player, unifiedJedis);
return name;
}
return null;
}
};
// Okay, it wasn't locally cached. Let's try Redis.
try {
return redisTask.execute();
} catch (JedisException e) {
plugin.logFatal("Unable to fetch name for " + player);
return null;
}
}
public void persistInfo(String name, UUID uuid, UnifiedJedis unifiedJedis) {
addToMaps(name, uuid);
String json = gson.toJson(uuidToNameMap.get(uuid));
unifiedJedis.hset(
"uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
}
} }

View File

@ -2,6 +2,9 @@ plugins {
`java-library` `java-library`
} }
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies { dependencies {
compileOnly(project(":RedisBungee-API")) compileOnly(project(":RedisBungee-API"))
implementation(libs.acf.core) implementation(libs.acf.core)

View File

@ -1,50 +1,48 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands; package com.imaginarycode.minecraft.redisbungee.commands;
import co.aikar.commands.CommandContexts; import co.aikar.commands.CommandContexts;
import co.aikar.commands.CommandManager; import co.aikar.commands.CommandManager;
import co.aikar.commands.InvalidCommandArgument; import co.aikar.commands.InvalidCommandArgument;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.legacy.LegacyRedisBungeeCommands; import com.imaginarycode.minecraft.redisbungee.commands.legacy.LegacyRedisBungeeCommands;
import java.util.UUID; import java.util.UUID;
public class CommandLoader { public class CommandLoader {
public static void initCommands(CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) { public static void initCommands(
registerContexts(commandManager); CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) {
var commandsConfiguration = plugin.configuration().commandsConfiguration(); registerContexts(commandManager);
if (commandsConfiguration.redisbungeeEnabled()) { var commandsConfiguration = plugin.configuration().commandsConfiguration();
commandManager.registerCommand(new CommandRedisBungee(plugin)); if (commandsConfiguration.redisbungeeEnabled()) {
} commandManager.registerCommand(new CommandRedisBungee(plugin));
if (commandsConfiguration.redisbungeeLegacyEnabled()) {
commandManager.registerCommand(new LegacyRedisBungeeCommands(commandManager,plugin));
}
commandManager.registerCommand(new CommandRedisBungeeDebug(plugin));
} }
private static void registerContexts(CommandManager<?, ?, ?, ?, ?, ?> commandManager) { if (commandsConfiguration.redisbungeeLegacyEnabled()) {
CommandContexts<?> commandContexts = commandManager.getCommandContexts(); commandManager.registerCommand(new LegacyRedisBungeeCommands(commandManager, plugin));
commandContexts.registerContext(UUID.class, c -> { }
String uuidString = c.popFirstArg();
try { commandManager.registerCommand(new CommandRedisBungeeDebug(plugin));
return UUID.fromString(uuidString); }
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("invaild uuid"); private static void registerContexts(CommandManager<?, ?, ?, ?, ?, ?> commandManager) {
} CommandContexts<?> commandContexts = commandManager.getCommandContexts();
commandContexts.registerContext(
UUID.class,
c -> {
String uuidString = c.popFirstArg();
try {
return UUID.fromString(uuidString);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("invaild uuid");
}
}); });
} }
} }

View File

@ -1,23 +1,24 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands; package com.imaginarycode.minecraft.redisbungee.commands;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
import co.aikar.commands.RegisteredCommand;
import co.aikar.commands.annotation.*; import co.aikar.commands.annotation.*;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import com.imaginarycode.minecraft.redisbungee.Constants; import com.imaginarycode.minecraft.redisbungee.Constants;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
import com.imaginarycode.minecraft.redisbungee.commands.utils.StopperUUIDCleanupTask; import com.imaginarycode.minecraft.redisbungee.commands.utils.StopperUUIDCleanupTask;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
@ -26,26 +27,23 @@ import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@CommandAlias("rb|redisbungee") @CommandAlias("rb|redisbungee")
@CommandPermission("redisbungee.command.use") @CommandPermission("redisbungee.command.use")
@Description("Main command") @Description("Main command")
public class CommandRedisBungee extends AdventureBaseCommand { public class CommandRedisBungee extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin; private final RedisBungeePlugin<?> plugin;
public CommandRedisBungee(RedisBungeePlugin<?> plugin) { public CommandRedisBungee(RedisBungeePlugin<?> plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
@Default @Default
@Subcommand("info|version|git") @Subcommand("info|version|git")
@Description("information about current redisbungee build") @Description("information about current redisbungee build")
public void info(CommandIssuer issuer) { public void info(CommandIssuer issuer) {
final String message = """ final String message =
"""
<color:aqua>This proxy is running RedisBungee Limework's fork <color:aqua>This proxy is running RedisBungee Limework's fork
<color:gold>======================================== <color:gold>========================================
<color:aqua>RedisBungee version: <color:green><version> <color:aqua>RedisBungee version: <color:green><version>
@ -61,129 +59,166 @@ public class CommandRedisBungee extends AdventureBaseCommand {
Placeholder.component( Placeholder.component(
"commit", "commit",
Component.text(Constants.GIT_COMMIT.substring(0, 8)) Component.text(Constants.GIT_COMMIT.substring(0, 8))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, Constants.getGithubCommitLink())) .clickEvent(
.hoverEvent(HoverEvent.showText(Component.text("Click me to open: " + Constants.getGithubCommitLink()))) ClickEvent.clickEvent(
))); ClickEvent.Action.OPEN_URL, Constants.getGithubCommitLink()))
.hoverEvent(
HoverEvent.showText(
Component.text(
"Click me to open: " + Constants.getGithubCommitLink()))))));
}
// <color:aqua>......: <color:green>......
@HelpCommand
@Description("shows the help page")
public void help(CommandIssuer issuer) {
final String barFormat = "<color:gold>========================================";
final String commandFormat = "<color:aqua>/rb <sub-command>: <color:green><description>";
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<color:red> <bold>WARNING...</bold> <color:white>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;
} }
// <color:aqua>......: <color:green>...... sendMessage(
@HelpCommand issuer,
@Description("shows the help page") Component.text("cleanup is Starting, you should see the output status in the proxy console")
public void help(CommandIssuer issuer) { .color(NamedTextColor.GOLD));
final String barFormat = "<color:gold>========================================"; plugin.executeAsync(new StopperUUIDCleanupTask(plugin));
final String commandFormat = "<color:aqua>/rb <sub-command>: <color:green><description>"; }
TextComponent.Builder message = Component.text(); private List<Map.Entry<String, Integer>> subListProxies(
message.append(MiniMessage.miniMessage().deserialize(barFormat)); List<Map.Entry<String, Integer>> data, final int currentPage, final int pageSize) {
return data.subList(
((currentPage * pageSize) - pageSize),
Ints.constrainToRange(currentPage * pageSize, 0, data.size()));
}
getSubCommands().forEach((subCommand, registeredCommand) -> { @Subcommand("show")
String[] split = registeredCommand.getCommand().split(" "); @Description("Shows proxies in this network")
if (split.length > 1 && subCommand.equalsIgnoreCase(split[1])) { public void showProxies(CommandIssuer issuer, String[] args) {
message.appendNewline().append(MiniMessage.miniMessage().deserialize(commandFormat, Placeholder.component("sub-command", Component.text(subCommand)), final String closer = "<color:gold>========================================";
Placeholder.component("description", MiniMessage.miniMessage().deserialize(registeredCommand.getHelpText())) final String pageTop =
)); "<color:yellow>Page: <color:green><current>/<max> <color:yellow>Network ID: <color:green><network> <color:yellow>Proxies online: <color:green><proxies>";
} final String proxy = "<color:yellow><proxy><here> : <color:green><players> online";
}); final String proxyHere = " (#) ";
final String nextPage = ">>>>>";
final String previousPage = "<<<<< ";
final String pageInvalid = "<color:red>invalid page";
final String noProxies = "<color:red>No proxies were found :(";
message.appendNewline().append(MiniMessage.miniMessage().deserialize(barFormat)); final int pageSize = 16;
sendMessage(issuer, message.build()); 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;
} }
@Subcommand("clean") // compute the total pages
@Description("cleans up the uuid cache<color:red> <bold>WARNING...</bold> <color:white>command above could cause performance issues") int maxPages = (int) Math.ceil(data.size() / (double) pageSize);
@Private if (currentPage > maxPages) currentPage = maxPages;
public void cleanUp(CommandIssuer issuer) { var subList = subListProxies(data, currentPage, pageSize);
if (StopperUUIDCleanupTask.isRunning) { TextComponent.Builder builder = Component.text();
sendMessage(issuer, builder.append(MiniMessage.miniMessage().deserialize(closer)).appendNewline();
Component.text("cleanup is currently running!").color(NamedTextColor.RED)); builder
return; .append(
} MiniMessage.miniMessage()
sendMessage(issuer, .deserialize(
Component.text("cleanup is Starting, you should see the output status in the proxy console").color(NamedTextColor.GOLD)); pageTop,
plugin.executeAsync(new StopperUUIDCleanupTask(plugin)); 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<String, Integer> 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--;
private List<Map.Entry<String, Integer>> subListProxies(List<Map.Entry<String, Integer>> data, final int currentPage, final int pageSize) {
return data.subList(((currentPage * pageSize) - pageSize), Ints.constrainToRange(currentPage * pageSize, 0, data.size()));
} }
@Subcommand("show") if (currentPage > 1) {
@Description("Shows proxies in this network") builder.append(
public void showProxies(CommandIssuer issuer, String[] args) { MiniMessage.miniMessage()
final String closer = "<color:gold>========================================"; .deserialize(previousPage)
final String pageTop = "<color:yellow>Page: <color:green><current>/<max> <color:yellow>Network ID: <color:green><network> <color:yellow>Proxies online: <color:green><proxies>"; .color(NamedTextColor.WHITE)
final String proxy = "<color:yellow><proxy><here> : <color:green><players> online"; .clickEvent(ClickEvent.runCommand("/rb show " + (currentPage - 1))));
final String proxyHere = " (#) "; } else {
final String nextPage = ">>>>>"; builder.append(
final String previousPage = "<<<<< "; MiniMessage.miniMessage().deserialize(previousPage).color(NamedTextColor.GRAY));
final String pageInvalid = "<color:red>invalid page";
final String noProxies = "<color:red>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<String, Integer> 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());
} }
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());
}
} }

View File

@ -1,20 +1,18 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands; package com.imaginarycode.minecraft.redisbungee.commands;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.*; import co.aikar.commands.annotation.*;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
import java.util.UUID; import java.util.UUID;
@CommandAlias("rbd|redisbungeedebug") @CommandAlias("rbd|redisbungeedebug")
@ -22,25 +20,29 @@ import java.util.UUID;
@Description("debug commands") @Description("debug commands")
public class CommandRedisBungeeDebug extends AdventureBaseCommand { public class CommandRedisBungeeDebug extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin; private final RedisBungeePlugin<?> plugin;
public CommandRedisBungeeDebug(RedisBungeePlugin<?> plugin) { public CommandRedisBungeeDebug(RedisBungeePlugin<?> plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
@Subcommand("kickByName")
@Description("kicks a player from the network by name")
@Private
public void kick(CommandIssuer issuer, String playerName) {
plugin.playerDataManager().serializedPlayerKick(plugin.getUuidTranslator().getTranslatedUuid(playerName, false), "kicked using redisbungee api using name");
}
@Subcommand("kickByUUID")
@Description("kicks a player from the network by UUID")
@Private
public void kick(CommandIssuer issuer, UUID uuid) {
plugin.playerDataManager().serializedPlayerKick(uuid, "kicked using redisbungee api using uuid");
}
@Subcommand("kickByName")
@Description("kicks a player from the network by name")
@Private
public void kick(CommandIssuer issuer, String playerName) {
plugin
.playerDataManager()
.serializedPlayerKick(
plugin.getUuidTranslator().getTranslatedUuid(playerName, false),
"kicked using redisbungee api using name");
}
@Subcommand("kickByUUID")
@Description("kicks a player from the network by UUID")
@Private
public void kick(CommandIssuer issuer, UUID uuid) {
plugin
.playerDataManager()
.serializedPlayerKick(uuid, "kicked using redisbungee api using uuid");
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.find") @CommandPermission("redisbungee.command.find")
public class CommandFind extends AdventureBaseCommand { public class CommandFind extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand; private final LegacyRedisBungeeCommands rootCommand;
public CommandFind(LegacyRedisBungeeCommands rootCommand) { public CommandFind(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand; this.rootCommand = rootCommand;
} }
@Default
public void find(CommandIssuer issuer, String[] args) {
rootCommand.find(issuer, args);
}
@Default
public void find(CommandIssuer issuer, String[] args) {
rootCommand.find(issuer, args);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.glist") @CommandPermission("redisbungee.command.glist")
public class CommandGList extends AdventureBaseCommand { public class CommandGList extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand; private final LegacyRedisBungeeCommands rootCommand;
public CommandGList(LegacyRedisBungeeCommands rootCommand) { public CommandGList(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand; this.rootCommand = rootCommand;
} }
@Default
public void gList(CommandIssuer issuer, String[] args) {
rootCommand.gList(issuer, args);
}
@Default
public void gList(CommandIssuer issuer, String[] args) {
rootCommand.gList(issuer, args);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.ip") @CommandPermission("redisbungee.command.ip")
public class CommandIp extends AdventureBaseCommand { public class CommandIp extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand; private final LegacyRedisBungeeCommands rootCommand;
public CommandIp(LegacyRedisBungeeCommands rootCommand) { public CommandIp(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand; this.rootCommand = rootCommand;
} }
@Default
@Default public void ip(CommandIssuer issuer, String[] args) {
public void ip(CommandIssuer issuer, String[] args) { this.rootCommand.ip(issuer, args);
this.rootCommand.ip(issuer, args); }
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.lastseen") @CommandPermission("redisbungee.command.lastseen")
public class CommandLastSeen extends AdventureBaseCommand { public class CommandLastSeen extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand; public CommandLastSeen(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
public CommandLastSeen(LegacyRedisBungeeCommands rootCommand) { @Default
this.rootCommand = rootCommand; public void lastSeen(CommandIssuer issuer, String[] args) {
} this.rootCommand.lastSeen(issuer, args);
}
@Default
public void lastSeen(CommandIssuer issuer, String[] args) {
this.rootCommand.lastSeen(issuer,args);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -19,15 +18,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandAlias("pproxy") @CommandAlias("pproxy")
@CommandPermission("redisbungee.command.pproxy") @CommandPermission("redisbungee.command.pproxy")
public class CommandPProxy extends AdventureBaseCommand { public class CommandPProxy extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand; private final LegacyRedisBungeeCommands rootCommand;
public CommandPProxy(LegacyRedisBungeeCommands rootCommand) { public CommandPProxy(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand; this.rootCommand = rootCommand;
} }
@Default
public void playerProxy(CommandIssuer issuer, String[] args) {
this.rootCommand.playerProxy(issuer,args);
}
@Default
public void playerProxy(CommandIssuer issuer, String[] args) {
this.rootCommand.playerProxy(issuer, args);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,16 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.plist") @CommandPermission("redisbungee.command.plist")
public class CommandPlist extends AdventureBaseCommand { public class CommandPlist extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand; public CommandPlist(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
public CommandPlist(LegacyRedisBungeeCommands rootCommand) { }
this.rootCommand = rootCommand;
}
@Default
public void playerList(CommandIssuer issuer, String[] args) {
this.rootCommand.playerList(issuer, args);
}
@Default
public void playerList(CommandIssuer issuer, String[] args) {
this.rootCommand.playerList(issuer, args);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,14 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.sendtoall") @CommandPermission("redisbungee.command.sendtoall")
public class CommandSendToAll extends AdventureBaseCommand { public class CommandSendToAll extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand; public CommandSendToAll(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
public CommandSendToAll(LegacyRedisBungeeCommands rootCommand) { @Default
this.rootCommand = rootCommand; public void sendToAll(CommandIssuer issuer, String[] args) {
} this.rootCommand.sendToAll(issuer, args);
@Default }
public void sendToAll(CommandIssuer issuer, String[] args) {
this.rootCommand.sendToAll(issuer, args);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,14 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.serverid") @CommandPermission("redisbungee.command.serverid")
public class CommandServerId extends AdventureBaseCommand { public class CommandServerId extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand; public CommandServerId(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
public CommandServerId(LegacyRedisBungeeCommands rootCommand) { @Default
this.rootCommand = rootCommand; public void serverId(CommandIssuer issuer) {
} this.rootCommand.serverId(issuer);
@Default }
public void serverId(CommandIssuer issuer) {
this.rootCommand.serverId(issuer);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,17 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.serverids") @CommandPermission("redisbungee.command.serverids")
public class CommandServerIds extends AdventureBaseCommand { public class CommandServerIds extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand; public CommandServerIds(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
public CommandServerIds(LegacyRedisBungeeCommands rootCommand) { }
this.rootCommand = rootCommand;
}
@Default
public void serverIds(CommandIssuer issuer) {
this.rootCommand.serverIds(issuer);
}
@Default
public void serverIds(CommandIssuer issuer) {
this.rootCommand.serverIds(issuer);
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.legacy; package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -20,241 +19,281 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; 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.net.InetAddress;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@CommandAlias("rbl|redisbungeelegacy") @CommandAlias("rbl|redisbungeelegacy")
@CommandPermission("redisbungee.legacy.use") @CommandPermission("redisbungee.legacy.use")
public class LegacyRedisBungeeCommands extends AdventureBaseCommand { public class LegacyRedisBungeeCommands extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin; private final RedisBungeePlugin<?> plugin;
public LegacyRedisBungeeCommands(CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) { public LegacyRedisBungeeCommands(
this.plugin = plugin; CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) {
var commands = plugin.configuration().commandsConfiguration().legacySubCommandsConfiguration(); this.plugin = plugin;
if (!plugin.configuration().commandsConfiguration().redisbungeeLegacyEnabled()) throw new IllegalStateException("someone tried to init me while disabled!"); var commands = plugin.configuration().commandsConfiguration().legacySubCommandsConfiguration();
if (commands == null) throw new NullPointerException("commands config is null!!"); 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.installGlist()) commandManager.registerCommand(new CommandGList(this));
if (commands.installFind()) commandManager.registerCommand(new CommandFind(this)); if (commands.installFind()) commandManager.registerCommand(new CommandFind(this));
if (commands.installIp()) commandManager.registerCommand(new CommandIp(this)); if (commands.installIp()) commandManager.registerCommand(new CommandIp(this));
if (commands.installLastseen()) commandManager.registerCommand(new CommandLastSeen(this)); if (commands.installLastseen()) commandManager.registerCommand(new CommandLastSeen(this));
if (commands.installPlist()) commandManager.registerCommand(new CommandPlist(this)); if (commands.installPlist()) commandManager.registerCommand(new CommandPlist(this));
if (commands.installPproxy()) commandManager.registerCommand(new CommandPProxy(this)); if (commands.installPproxy()) commandManager.registerCommand(new CommandPProxy(this));
if (commands.installSendtoall()) commandManager.registerCommand(new CommandSendToAll(this)); if (commands.installSendtoall()) commandManager.registerCommand(new CommandSendToAll(this));
if (commands.installServerid()) commandManager.registerCommand(new CommandServerId(this)); if (commands.installServerid()) commandManager.registerCommand(new CommandServerId(this));
if (commands.installServerids()) commandManager.registerCommand(new CommandServerIds(this)); if (commands.installServerids()) commandManager.registerCommand(new CommandServerIds(this));
} }
private static final Component NO_PLAYER_SPECIFIED = private static final Component NO_PLAYER_SPECIFIED =
Component.text("You must specify a player name.", NamedTextColor.RED); Component.text("You must specify a player name.", NamedTextColor.RED);
private static final Component PLAYER_NOT_FOUND = private static final Component PLAYER_NOT_FOUND =
Component.text("No such player found.", NamedTextColor.RED); Component.text("No such player found.", NamedTextColor.RED);
private static final Component NO_COMMAND_SPECIFIED = private static final Component NO_COMMAND_SPECIFIED =
Component.text("You must specify a command to be run.", NamedTextColor.RED); Component.text("You must specify a command to be run.", NamedTextColor.RED);
private static String playerPlural(int num) { private static String playerPlural(int num) {
return num == 1 ? num + " player is" : num + " players are"; return num == 1 ? num + " player is" : num + " players are";
} }
@Subcommand("glist") @Subcommand("glist")
@CommandPermission("redisbungee.command.glist") @CommandPermission("redisbungee.command.glist")
public void gList(CommandIssuer issuer, String[] args) { public void gList(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> { plugin.executeAsync(
int count = plugin.getAbstractRedisBungeeApi().getPlayerCount(); () -> {
Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW); int count = plugin.getAbstractRedisBungeeApi().getPlayerCount();
if (args.length > 0 && args[0].equals("showall")) { Component playersOnline =
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW);
Multimap<String, String> human = HashMultimap.create(); if (args.length > 0 && args[0].equals("showall")) {
serverToPlayers.forEach((key, value) -> { Multimap<String, UUID> serverToPlayers =
// if for any reason UUID translation fails just return the uuid as name, to make command finish executing. plugin.getAbstractRedisBungeeApi().getServerToPlayers();
String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false); Multimap<String, String> human = HashMultimap.create();
human.put(key, playerName != null ? playerName : value.toString()); 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())) { for (String server : new TreeSet<>(serverToPlayers.keySet())) {
Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN); Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN);
Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW); Component serverCount =
Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); Component.text(
sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers)); "(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW);
} Component serverPlayers =
sendMessage(issuer, playersOnline); Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
} else { sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers));
sendMessage(issuer, playersOnline);
sendMessage(issuer, Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW));
} }
sendMessage(issuer, playersOnline);
} else {
sendMessage(issuer, playersOnline);
sendMessage(
issuer,
Component.text(
"To see all players online, use /glist showall.", NamedTextColor.YELLOW));
}
}); });
} }
@Subcommand("find") @Subcommand("find")
@CommandPermission("redisbungee.command.find") @CommandPermission("redisbungee.command.find")
public void find(CommandIssuer issuer, String[] args) { public void find(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> { plugin.executeAsync(
if (args.length > 0) { () -> {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); if (args.length > 0) {
if (uuid == null) { UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
sendMessage(issuer, PLAYER_NOT_FOUND); if (uuid == null) {
return; sendMessage(issuer, PLAYER_NOT_FOUND);
} return;
String proxyId = plugin.playerDataManager().getProxyFor(uuid); }
if (proxyId != null) { String proxyId = plugin.playerDataManager().getProxyFor(uuid);
String serverId = plugin.playerDataManager().getServerFor(uuid); if (proxyId != null) {
Component message = Component.text(args[0] + " is on proxy " + proxyId + " on server " + serverId +".", NamedTextColor.BLUE); String serverId = plugin.playerDataManager().getServerFor(uuid);
sendMessage(issuer, message); Component message =
} else { Component.text(
sendMessage(issuer, PLAYER_NOT_FOUND); args[0] + " is on proxy " + proxyId + " on server " + serverId + ".",
} NamedTextColor.BLUE);
sendMessage(issuer, message);
} else { } else {
sendMessage(issuer, NO_PLAYER_SPECIFIED); sendMessage(issuer, PLAYER_NOT_FOUND);
} }
} else {
sendMessage(issuer, NO_PLAYER_SPECIFIED);
}
}); });
}
} @Subcommand("lastseen")
@CommandPermission("redisbungee.command.lastseen")
@Subcommand("lastseen") public void lastSeen(CommandIssuer issuer, String[] args) {
@CommandPermission("redisbungee.command.lastseen") plugin.executeAsync(
public void lastSeen(CommandIssuer issuer, String[] args) { () -> {
plugin.executeAsync(() -> { if (args.length > 0) {
if (args.length > 0) { UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); if (uuid == null) {
if (uuid == null) { sendMessage(issuer, PLAYER_NOT_FOUND);
sendMessage(issuer, PLAYER_NOT_FOUND); return;
return; }
} long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid);
long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid); TextComponent.Builder message = Component.text();
TextComponent.Builder message = Component.text(); if (secs == 0) {
if (secs == 0) { message.color(NamedTextColor.GREEN);
message.color(NamedTextColor.GREEN); message.content(args[0] + " is currently online.");
message.content(args[0] + " is currently online."); } else if (secs != -1) {
} else if (secs != -1) { message.color(NamedTextColor.BLUE);
message.color(NamedTextColor.BLUE); message.content(
message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); 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 { } else {
sendMessage(issuer, NO_PLAYER_SPECIFIED); message.color(NamedTextColor.RED);
message.content(args[0] + " has never been online.");
} }
sendMessage(issuer, message.build());
} else {
sendMessage(issuer, NO_PLAYER_SPECIFIED);
}
}); });
} }
@Subcommand("ip") @Subcommand("ip")
@CommandPermission("redisbungee.command.ip") @CommandPermission("redisbungee.command.ip")
public void ip(CommandIssuer issuer, String[] args) { public void ip(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> { plugin.executeAsync(
if (args.length > 0) { () -> {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); if (args.length > 0) {
if (uuid == null) { UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
sendMessage(issuer, PLAYER_NOT_FOUND); if (uuid == null) {
return; sendMessage(issuer, PLAYER_NOT_FOUND);
} return;
InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid); }
if (ia != null) { InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid);
TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN); if (ia != null) {
sendMessage(issuer, message); TextComponent message =
} else { Component.text(
sendMessage(issuer, PLAYER_NOT_FOUND); args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN);
} sendMessage(issuer, message);
} else { } else {
sendMessage(issuer, NO_PLAYER_SPECIFIED); sendMessage(issuer, PLAYER_NOT_FOUND);
} }
} else {
sendMessage(issuer, NO_PLAYER_SPECIFIED);
}
}); });
} }
@Subcommand("pproxy") @Subcommand("pproxy")
@CommandPermission("redisbungee.command.pproxy") @CommandPermission("redisbungee.command.pproxy")
public void playerProxy(CommandIssuer issuer, String[] args) { public void playerProxy(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> { plugin.executeAsync(
if (args.length > 0) { () -> {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); if (args.length > 0) {
if (uuid == null) { UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
sendMessage(issuer, PLAYER_NOT_FOUND); if (uuid == null) {
return; sendMessage(issuer, PLAYER_NOT_FOUND);
} return;
String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid); }
if (proxy != null) { String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid);
TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN); if (proxy != null) {
sendMessage(issuer, message); TextComponent message =
} else { Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN);
sendMessage(issuer, PLAYER_NOT_FOUND); sendMessage(issuer, message);
}
} else { } else {
sendMessage(issuer, NO_PLAYER_SPECIFIED); 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("sendtoall") @Subcommand("serverid")
@CommandPermission("redisbungee.command.sendtoall") @CommandPermission("redisbungee.command.serverid")
public void sendToAll(CommandIssuer issuer, String[] args) { public void serverId(CommandIssuer issuer) {
if (args.length > 0) { sendMessage(
String command = Joiner.on(" ").skipNulls().join(args); issuer,
plugin.getAbstractRedisBungeeApi().sendProxyCommand(command); Component.text(
TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN); "You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".",
sendMessage(issuer, message); NamedTextColor.YELLOW));
} else { }
sendMessage(issuer, NO_COMMAND_SPECIFIED);
}
} @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("serverid") @Subcommand("plist")
@CommandPermission("redisbungee.command.serverid") @CommandPermission("redisbungee.command.plist")
public void serverId(CommandIssuer issuer) { public void playerList(CommandIssuer issuer, String[] args) {
sendMessage(issuer, Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); plugin.executeAsync(
} () -> {
String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId();
@Subcommand("serverids") if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) {
@CommandPermission("redisbungee.command.serverids") sendMessage(
public void serverIds(CommandIssuer issuer) { issuer,
sendMessage(issuer, Component.text("All Proxies IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); Component.text(
} proxy + " is not a valid proxy. See /serverids for valid proxies.",
NamedTextColor.RED));
return;
@Subcommand("plist") }
@CommandPermission("redisbungee.command.plist") Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy);
public void playerList(CommandIssuer issuer, String[] args) { Component playersOnline =
plugin.executeAsync(() -> { Component.text(
String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); playerPlural(players.size()) + " currently on proxy " + proxy + ".",
if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { NamedTextColor.YELLOW);
sendMessage(issuer, Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED)); if (args.length >= 2 && args[1].equals("showall")) {
return; Multimap<String, UUID> serverToPlayers =
} plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy); Multimap<String, String> human = HashMultimap.create();
Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW); serverToPlayers.forEach(
if (args.length >= 2 && args[1].equals("showall")) { (key, value) -> {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); if (players.contains(value)) {
Multimap<String, String> human = HashMultimap.create(); human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false));
serverToPlayers.forEach((key, value) -> { }
if (players.contains(value)) {
human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false));
}
}); });
for (String server : new TreeSet<>(human.keySet())) { for (String server : new TreeSet<>(human.keySet())) {
TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED); TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED);
TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW); TextComponent serverCount =
TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW);
sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers)); TextComponent serverPlayers =
} Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sendMessage(issuer, playersOnline); sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers));
} else {
sendMessage(issuer, playersOnline);
sendMessage(issuer, Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW));
} }
sendMessage(issuer, playersOnline);
} else {
sendMessage(issuer, playersOnline);
sendMessage(
issuer,
Component.text(
"To see all players online, use /plist " + proxy + " showall.",
NamedTextColor.YELLOW));
}
}); });
}
} }
}

View File

@ -1,26 +1,22 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.utils; package com.imaginarycode.minecraft.redisbungee.commands.utils;
import co.aikar.commands.BaseCommand; import co.aikar.commands.BaseCommand;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
/** /** this just dumb class that wraps the adventure stuff into base command */
* this just dumb class that wraps the adventure stuff into base command
*/
public abstract class AdventureBaseCommand extends BaseCommand { public abstract class AdventureBaseCommand extends BaseCommand {
protected void sendMessage(CommandIssuer issuer, Component component) { protected void sendMessage(CommandIssuer issuer, Component component) {
CommandPlatformHelper.getPlatformHelper().sendMessage(issuer, component); CommandPlatformHelper.getPlatformHelper().sendMessage(issuer, component);
} }
} }

View File

@ -1,36 +1,31 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.commands.utils; package com.imaginarycode.minecraft.redisbungee.commands.utils;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
public abstract class CommandPlatformHelper { public abstract class CommandPlatformHelper {
private static CommandPlatformHelper SINGLETON; private static CommandPlatformHelper SINGLETON;
public abstract void sendMessage(CommandIssuer issuer, Component component); public abstract void sendMessage(CommandIssuer issuer, Component component);
public static void init(CommandPlatformHelper platformHelper) { public static void init(CommandPlatformHelper platformHelper) {
if (SINGLETON != null) { if (SINGLETON != null) {
throw new IllegalStateException("tried to re init Platform Helper"); throw new IllegalStateException("tried to re init Platform Helper");
}
SINGLETON = platformHelper;
}
public static CommandPlatformHelper getPlatformHelper() {
return SINGLETON;
} }
SINGLETON = platformHelper;
}
public static CommandPlatformHelper getPlatformHelper() {
return SINGLETON;
}
} }

View File

@ -1,3 +1,12 @@
/*
* Copyright (c) 2026 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; package com.imaginarycode.minecraft.redisbungee.commands.utils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
@ -6,20 +15,20 @@ import redis.clients.jedis.UnifiedJedis;
public class StopperUUIDCleanupTask extends UUIDCleanupTask { public class StopperUUIDCleanupTask extends UUIDCleanupTask {
public static boolean isRunning = false; public static boolean isRunning = false;
public StopperUUIDCleanupTask(RedisBungeePlugin<?> plugin) { public StopperUUIDCleanupTask(RedisBungeePlugin<?> plugin) {
super(plugin); super(plugin);
} }
@Override
@Override public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { isRunning = true;
isRunning = true; try {
try { super.unifiedJedisTask(unifiedJedis);
super.unifiedJedisTask(unifiedJedis); } catch (Exception ignored) {
} catch (Exception ignored) {}
isRunning = false;
return null;
} }
isRunning = false;
return null;
}
} }

View File

@ -1,7 +1,9 @@
Copyright (c) 2013-present RedisBungee contributors /*
* Copyright (c) 2026 RedisBungee contributors
All rights reserved. This program and the accompanying materials *
are made available under the terms of the Eclipse Public License v1.0 * All rights reserved. This program and the accompanying materials
which accompanies this distribution, and is available at * 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 *
* http://www.eclipse.org/legal/epl-v10.html
*/

View File

@ -3,6 +3,9 @@ plugins {
`maven-publish` `maven-publish`
} }
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies { dependencies {
compileOnly(project(":RedisBungee-API")) compileOnly(project(":RedisBungee-API"))
compileOnly(libs.adventure.api) compileOnly(libs.adventure.api)

View File

@ -1,55 +1,63 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package net.limework.valiobungee.config.lang; package net.limework.valiobungee.config.lang;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.loaders.GenericConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.loaders.GenericConfigLoader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Locale;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Locale;
public interface LangConfigLoader extends GenericConfigLoader { public interface LangConfigLoader extends GenericConfigLoader {
int CONFIG_VERSION = 1; int CONFIG_VERSION = 1;
default void loadLangConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException { default void loadLangConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml"); Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml");
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build(); final YAMLConfigurationLoader yamlConfigurationFileLoader =
ConfigurationNode node = yamlConfigurationFileLoader.load(); YAMLConfigurationLoader.builder().setPath(configFile).build();
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) { ConfigurationNode node = yamlConfigurationFileLoader.load();
handleOldConfig(dataFolder, "lang.yml", "lang.yml"); if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
node = yamlConfigurationFileLoader.load(); handleOldConfig(dataFolder, "lang.yml", "lang.yml");
} node = yamlConfigurationFileLoader.load();
// MINI message serializer
MiniMessage miniMessage = MiniMessage.miniMessage();
Component prefix = miniMessage.deserialize(node.getNode("prefix").getString("<color:red>[<color:yellow>Redis<color:red>Bungee]"));
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));
} }
// MINI message serializer
MiniMessage miniMessage = MiniMessage.miniMessage();
Component prefix =
miniMessage.deserialize(
node.getNode("prefix").getString("<color:red>[<color:yellow>Redis<color:red>Bungee]"));
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);
void onLangConfigLoad(LangConfiguration langConfiguration); onLangConfigLoad(new LangConfiguration(prefix, defaultLocale, useClientLocale, messages));
}
void onLangConfigLoad(LangConfiguration langConfiguration);
} }

View File

@ -1,155 +1,164 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package net.limework.valiobungee.config.lang; package net.limework.valiobungee.config.lang;
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.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
/** /**
* This language support implementation is temporarily * This language support implementation is temporarily until I come up with better system but for
* until I come up with better system but for now we will use Maps instead :/ * now we will use Maps instead :/ Todo: possible usage of adventure api
* Todo: possible usage of adventure api
*/ */
public class LangConfiguration { public class LangConfiguration {
private interface RegistrableMessages { private interface RegistrableMessages {
void register(String id, Locale locale, String miniMessage); void register(String id, Locale locale, String miniMessage);
void test(Locale locale); void test(Locale locale);
default void throwError(Locale locale, String where) { default void throwError(Locale locale, String where) {
throw new IllegalStateException("Language system in `" + where + "` found missing entries for " + locale.toString()); throw new IllegalStateException(
} "Language system in `" + where + "` found missing entries for " + locale.toString());
}
}
public static class Messages implements RegistrableMessages {
private final Map<Locale, Component> LOGGED_IN_FROM_OTHER_LOCATION;
private final Map<Locale, Component> ALREADY_LOGGED_IN;
private final Map<Locale, String> SERVER_CONNECTING;
private final Map<Locale, String> 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 static class Messages implements RegistrableMessages{ public void register(String id, Locale locale, String miniMessage) {
switch (id) {
private final Map<Locale, Component> LOGGED_IN_FROM_OTHER_LOCATION; case "server-not-found" -> SERVER_NOT_FOUND.put(locale, miniMessage);
private final Map<Locale, Component> ALREADY_LOGGED_IN; case "server-connecting" -> SERVER_CONNECTING.put(locale, miniMessage);
private final Map<Locale, String> SERVER_CONNECTING; case "logged-in-other-location" ->
private final Map<Locale, String> SERVER_NOT_FOUND; LOGGED_IN_FROM_OTHER_LOCATION.put(
locale, MiniMessage.miniMessage().deserialize(miniMessage));
private final Locale defaultLocale; case "already-logged-in" ->
ALREADY_LOGGED_IN.put(locale, MiniMessage.miniMessage().deserialize(miniMessage));
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; public Component alreadyLoggedIn(Locale locale) {
if (ALREADY_LOGGED_IN.containsKey(locale)) return ALREADY_LOGGED_IN.get(locale);
private final Locale defaultLanguage; return ALREADY_LOGGED_IN.get(defaultLocale);
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() { // there is no way to know whats client locale during login so just default to use default
return redisBungeePrefix; // locale MESSAGES.
public Component alreadyLoggedIn() {
return this.alreadyLoggedIn(this.defaultLocale);
} }
public Locale defaultLanguage() { public Component loggedInFromOtherLocation(Locale locale) {
return defaultLanguage; if (LOGGED_IN_FROM_OTHER_LOCATION.containsKey(locale))
return LOGGED_IN_FROM_OTHER_LOCATION.get(locale);
return LOGGED_IN_FROM_OTHER_LOCATION.get(defaultLocale);
} }
public boolean useClientLanguage() { // there is no way to know what's client locale during login so just default to use default
return useClientLanguage; // locale MESSAGES.
public Component loggedInFromOtherLocation() {
return this.loggedInFromOtherLocation(this.defaultLocale);
} }
public Messages messages() { public Component serverConnecting(Locale locale, String server) {
return messages; 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;
}
} }

View File

@ -3,6 +3,9 @@ plugins {
alias(libs.plugins.shadow) alias(libs.plugins.shadow)
} }
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies { dependencies {
implementation(project(":RedisBungee-Bungee")) implementation(project(":RedisBungee-Bungee"))
compileOnly(libs.platform.bungeecord) compileOnly(libs.platform.bungeecord)

View File

@ -3,6 +3,9 @@ plugins {
`maven-publish` `maven-publish`
} }
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies { dependencies {
api(project(":RedisBungee-API")) api(project(":RedisBungee-API"))
compileOnly(libs.adventure.platforms.bungeecord) compileOnly(libs.adventure.platforms.bungeecord)

View File

@ -1,22 +1,19 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import net.kyori.adventure.text.Component;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.text.Component;
// this class used to redirect calls to keep the implementation and api separate // this class used to redirect calls to keep the implementation and api separate
public interface ApiPlatformSupport { public interface ApiPlatformSupport {
void kickPlayer(UUID player, Component message); void kickPlayer(UUID player, Component message);
} }

View File

@ -1,16 +1,17 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import java.util.List;
import java.util.UUID;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
@ -19,145 +20,140 @@ import net.md_5.bungee.api.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.UUID;
/** /**
* This platform class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungeeAPI#getRedisBungeeApi()} * This platform class exposes some internal RedisBungee functions. You obtain an instance of this
* or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}. * object by invoking {@link RedisBungeeAPI#getRedisBungeeApi()} or somehow you got the Plugin
* instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}.
* *
* @author tuxed * @author tuxed
* @since 0.2.3 | updated 0.8.0 * @since 0.2.3 | updated 0.8.0
*/ */
public class RedisBungeeAPI extends AbstractRedisBungeeAPI { public class RedisBungeeAPI extends AbstractRedisBungeeAPI {
private static RedisBungeeAPI redisBungeeApi; private static RedisBungeeAPI redisBungeeApi;
private static final BungeeComponentSerializer BUNGEE_COMPONENT_SERIALIZER = BungeeComponentSerializer.get(); private static final BungeeComponentSerializer BUNGEE_COMPONENT_SERIALIZER =
BungeeComponentSerializer.get();
public RedisBungeeAPI(RedisBungeePlugin<?> plugin) { public RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
super(plugin); super(plugin);
if (redisBungeeApi == null) { if (redisBungeeApi == null) {
redisBungeeApi = this; redisBungeeApi = this;
}
} }
}
/** /**
* Get the server where the specified player is playing. This function also deals with the case of local players * Get the server where the specified player is playing. This function also deals with the case of
* as well, and will return local information on them. * local players as well, and will return local information on them.
* *
* @param player a player uuid * @param player a player uuid
* @return {@link ServerInfo} Can be null if proxy can't find it. * @return {@link ServerInfo} Can be null if proxy can't find it.
* @see #getServerNameFor(UUID) * @see #getServerNameFor(UUID)
*/ */
@Nullable @Nullable
public final ServerInfo getServerFor(@NonNull UUID player) { public final ServerInfo getServerFor(@NonNull UUID player) {
String serverName = this.getServerNameFor(player); String serverName = this.getServerNameFor(player);
if (serverName == null) return null; if (serverName == null) return null;
return ((Plugin) this.plugin).getProxy().getServerInfo(serverName); return ((Plugin) this.plugin).getProxy().getServerInfo(serverName);
} }
/** /**
* Kicks a player from the network * Kicks a player from the network calls {@link #getUuidFromName(String)} to get uuid
* calls {@link #getUuidFromName(String)} to get uuid *
* * @param playerName player name
* @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.13.0
* @since 0.13.0 */
*/ public void kickPlayer(String playerName, BaseComponent[] message) {
public void kickPlayer(String playerName, BaseComponent[] message) { kickPlayer(getUuidFromName(playerName), message);
kickPlayer(getUuidFromName(playerName), message); }
}
/** /**
* Kicks a player from the network * Kicks a player from the network
* *
* @param player player uuid * @param player player uuid
* @param message kick message that player will see on kick * @param message kick message that player will see on kick
* @since 0.13.0 * @since 0.13.0
*/ */
public void kickPlayer(UUID player, BaseComponent[] message) { public void kickPlayer(UUID player, BaseComponent[] message) {
kickPlayer(player, BUNGEE_COMPONENT_SERIALIZER.deserialize(message)); kickPlayer(player, BUNGEE_COMPONENT_SERIALIZER.deserialize(message));
} }
/** /**
* Kicks a player from the network * Kicks a player from the network calls {@link #getUuidFromName(String)} to get uuid
* calls {@link #getUuidFromName(String)} to get uuid *
* * @param playerName player name
* @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.12.0
* @since 0.12.0 */
*/ public void kickPlayer(String playerName, Component message) {
public void kickPlayer(String playerName, Component message) { kickPlayer(getUuidFromName(playerName), message);
kickPlayer(getUuidFromName(playerName), message); }
}
/** /**
* Kicks a player from the network * Kicks a player from the network
* *
* @param player player uuid * @param player player uuid
* @param message kick message that player will see on kick * @param message kick message that player will see on kick
* @since 0.12.0 * @since 0.12.0
*/ */
public void kickPlayer(UUID player, Component message) { public void kickPlayer(UUID player, Component message) {
((ApiPlatformSupport) this.plugin).kickPlayer(player, message); ((ApiPlatformSupport) this.plugin).kickPlayer(player, message);
} }
/** /**
* Get the current BungeeCord / Velocity proxy ID for this server. * Get the current BungeeCord / Velocity proxy ID for this server.
* *
* @return the current server ID * @return the current server ID
* @see #getAllServers() * @see #getAllServers()
* @since 0.2.5 * @since 0.2.5
* @deprecated to avoid confusion between A server and A proxy see #getProxyId() * @deprecated to avoid confusion between A server and A proxy see #getProxyId()
*/ */
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public final String getServerId() { public final String getServerId() {
return getProxyId(); return getProxyId();
} }
/** /**
* Get all the linked proxies in this network. * Get all the linked proxies in this network.
* *
* @return the list of all proxies * @return the list of all proxies
* @see #getServerId() * @see #getServerId()
* @since 0.2.5 * @since 0.2.5
* @deprecated to avoid confusion between A server and A proxy see see {@link #getAllProxies()} * @deprecated to avoid confusion between A server and A proxy see see {@link #getAllProxies()}
*/ */
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public final List<String> getAllServers() { public final List<String> getAllServers() {
return getAllProxies(); return getAllProxies();
} }
/** /**
* Register (a) PubSub channel(s), so that you may handle PubSubMessageEvent for it. * Register (a) PubSub channel(s), so that you may handle PubSubMessageEvent for it.
* *
* @param channels the channels to register * @param channels the channels to register
* @since 0.3 * @since 0.3
* @deprecated No longer required * @deprecated No longer required
*/ */
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public final void registerPubSubChannels(String... channels) { public final void registerPubSubChannels(String... channels) {}
}
/** /**
* Unregister (a) PubSub channel(s). * Unregister (a) PubSub channel(s).
* *
* @param channels the channels to unregister * @param channels the channels to unregister
* @since 0.3 * @since 0.3
* @deprecated No longer required * @deprecated No longer required
*/ */
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public final void unregisterPubSubChannels(String... channels) { public final void unregisterPubSubChannels(String... channels) {}
}
/** /**
* Api instance * Api instance
* *
* @return the API instance. * @return the API instance.
* @since 0.6.5 * @since 0.6.5
*/ */
public static RedisBungeeAPI getRedisBungeeApi() { public static RedisBungeeAPI getRedisBungeeApi() {
return redisBungeeApi; return redisBungeeApi;
} }
} }

View File

@ -1,52 +1,51 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import net.md_5.bungee.api.plugin.Event;
import java.util.UUID; import java.util.UUID;
import net.md_5.bungee.api.plugin.Event;
/** /**
* This event is sent when a player connects to a new server. RedisBungee sends the event only when * This event is sent when a player connects to a new server. RedisBungee sends the event only when
* the proxy the player has been connected to is different than the local proxy. * the proxy the player has been connected to is different than the local proxy.
* <p> *
* This event corresponds to {@link net.md_5.bungee.api.event.ServerConnectedEvent}, and is fired * <p>This event corresponds to {@link net.md_5.bungee.api.event.ServerConnectedEvent}, and is fired
* asynchronously. * asynchronously.
* *
* @since 0.3.4 * @since 0.3.4
*/ */
public class PlayerChangedServerNetworkEvent extends Event implements IPlayerChangedServerNetworkEvent { public class PlayerChangedServerNetworkEvent extends Event
private final UUID uuid; implements IPlayerChangedServerNetworkEvent {
private final String previousServer; private final UUID uuid;
private final String server; private final String previousServer;
private final String server;
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) { public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
this.uuid = uuid; this.uuid = uuid;
this.previousServer = previousServer; this.previousServer = previousServer;
this.server = server; this.server = server;
} }
@Override @Override
public UUID getUuid() { public UUID getUuid() {
return uuid; return uuid;
} }
@Override @Override
public String getServer() { public String getServer() {
return server; return server;
} }
@Override @Override
public String getPreviousServer() { public String getPreviousServer() {
return previousServer; return previousServer;
} }
} }

View File

@ -1,38 +1,36 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import java.util.UUID;
import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Event;
import java.util.UUID;
/** /**
* This event is sent when a player joins the network. RedisBungee sends the event only when * This event is sent when a player joins the network. RedisBungee sends the event only when the
* the proxy the player has been connected to is different than the local proxy. * proxy the player has been connected to is different than the local proxy.
* <p> *
* This event corresponds to {@link net.md_5.bungee.api.event.PostLoginEvent}, and is fired * <p>This event corresponds to {@link net.md_5.bungee.api.event.PostLoginEvent}, and is fired
* asynchronously. * asynchronously.
* *
* @since 0.3.4 * @since 0.3.4
*/ */
public class PlayerJoinedNetworkEvent extends Event implements IPlayerJoinedNetworkEvent { public class PlayerJoinedNetworkEvent extends Event implements IPlayerJoinedNetworkEvent {
private final UUID uuid; private final UUID uuid;
public PlayerJoinedNetworkEvent(UUID uuid) { public PlayerJoinedNetworkEvent(UUID uuid) {
this.uuid = uuid; this.uuid = uuid;
} }
@Override @Override
public UUID getUuid() { public UUID getUuid() {
return uuid; return uuid;
} }
} }

View File

@ -1,38 +1,36 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import java.util.UUID;
import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Event;
import java.util.UUID;
/** /**
* This event is sent when a player disconnects. RedisBungee sends the event only when * This event is sent when a player disconnects. RedisBungee sends the event only when the proxy the
* the proxy the player has been connected to is different than the local proxy. * player has been connected to is different than the local proxy.
* <p> *
* This event corresponds to {@link net.md_5.bungee.api.event.PlayerDisconnectEvent}, and is fired * <p>This event corresponds to {@link net.md_5.bungee.api.event.PlayerDisconnectEvent}, and is
* asynchronously. * fired asynchronously.
* *
* @since 0.3.4 * @since 0.3.4
*/ */
public class PlayerLeftNetworkEvent extends Event implements IPlayerLeftNetworkEvent { public class PlayerLeftNetworkEvent extends Event implements IPlayerLeftNetworkEvent {
private final UUID uuid; private final UUID uuid;
public PlayerLeftNetworkEvent(UUID uuid) { public PlayerLeftNetworkEvent(UUID uuid) {
this.uuid = uuid; this.uuid = uuid;
} }
@Override @Override
public UUID getUuid() { public UUID getUuid() {
return uuid; return uuid;
} }
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
@ -15,28 +14,27 @@ import net.md_5.bungee.api.plugin.Event;
/** /**
* This event is posted when a PubSub message is received. * This event is posted when a PubSub message is received.
* <p> *
* <strong>Warning</strong>: This event is fired in a separate thread! * <p><strong>Warning</strong>: This event is fired in a separate thread!
* *
* @since 0.2.6 * @since 0.2.6
*/ */
public class PubSubMessageEvent extends Event implements IPubSubMessageEvent { public class PubSubMessageEvent extends Event implements IPubSubMessageEvent {
private final String channel; private final String channel;
private final String message; private final String message;
public PubSubMessageEvent(String channel, String message) { public PubSubMessageEvent(String channel, String message) {
this.channel = channel; this.channel = channel;
this.message = message; this.message = message;
} }
@Override @Override
public String getChannel() { public String getChannel() {
return channel; return channel;
} }
@Override @Override
public String getMessage() { public String getMessage() {
return message; return message;
} }
} }

View File

@ -1,3 +1,12 @@
/*
* Copyright (c) 2026 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; package com.imaginarycode.minecraft.redisbungee;
import co.aikar.commands.BungeeCommandIssuer; import co.aikar.commands.BungeeCommandIssuer;
@ -8,10 +17,9 @@ import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
public class BungeeCommandPlatformHelper extends CommandPlatformHelper { public class BungeeCommandPlatformHelper extends CommandPlatformHelper {
@Override @Override
public void sendMessage(CommandIssuer issuer, Component component) { public void sendMessage(CommandIssuer issuer, Component component) {
BungeeCommandIssuer bIssuer = (BungeeCommandIssuer) issuer; BungeeCommandIssuer bIssuer = (BungeeCommandIssuer) issuer;
bIssuer.getIssuer().sendMessage(BungeeComponentSerializer.get().serialize(component)); bIssuer.getIssuer().sendMessage(BungeeComponentSerializer.get().serialize(component));
} }
} }

View File

@ -1,25 +1,24 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; 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.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent;
@ -29,106 +28,115 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BungeePlayerDataManager extends PlayerDataManager<ProxiedPlayer> implements Listener { public class BungeePlayerDataManager extends PlayerDataManager<ProxiedPlayer> implements Listener {
private final RedisBungee bPlugin; private final RedisBungee bPlugin;
public BungeePlayerDataManager(RedisBungee plugin) {
super(plugin); public BungeePlayerDataManager(RedisBungee plugin) {
bPlugin = plugin; super(plugin);
bPlugin = plugin;
}
@EventHandler
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
super.handleNetworkPlayerServerChange(event);
}
@EventHandler
public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) {
super.handleNetworkPlayerQuit(event);
}
@EventHandler
public void onNetworkPlayerJoin(PlayerJoinedNetworkEvent event) {
super.handleNetworkPlayerJoin(event);
}
@EventHandler
public void onPubSubMessageEvent(PubSubMessageEvent event) {
super.handlePubSubMessageEvent(event);
}
@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);
}
private final BungeeComponentSerializer BUNGEE_COMPONENT_SERIALIZER =
BungeeComponentSerializer.get();
private static final MiniMessage MINI_MESSAGE_SERIALIZER = MiniMessage.miniMessage();
@Override
public boolean handleSerializedKick(UUID uuid, String serializedMiniMessage) {
ProxiedPlayer player = plugin.getPlayer(uuid);
if (player == null) return false;
// decode the adventure component
if (serializedMiniMessage == null) {
// kick the player too even if the message is invalid
player.disconnect(BUNGEE_COMPONENT_SERIALIZER.serialize(Component.empty()));
plugin.logWarn("unable to decode serialized adventure component because its empty or null");
} else {
Component message = MINI_MESSAGE_SERIALIZER.deserialize(serializedMiniMessage);
player.disconnect(BUNGEE_COMPONENT_SERIALIZER.serialize(message));
} }
return true;
}
@EventHandler public void kickPlayer(UUID player, Component message) {
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) { serializedPlayerKick(player, MINI_MESSAGE_SERIALIZER.serialize(message));
super.handleNetworkPlayerServerChange(event); }
}
@EventHandler @EventHandler
public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { public void onLoginEvent(LoginEvent event) {
super.handleNetworkPlayerQuit(event); event.registerIntent((Plugin) plugin);
} // check if online
if (getLastOnline(event.getConnection().getUniqueId()) == 0) {
@EventHandler // because something can go wrong and proxy somehow does not update player data correctly on
public void onNetworkPlayerJoin(PlayerJoinedNetworkEvent event) { // shutdown
super.handleNetworkPlayerJoin(event); // we have to check proxy if it has the player
} String proxyId = getProxyFor(event.getConnection().getUniqueId());
if (proxyId == null
@EventHandler || !plugin
public void onPubSubMessageEvent(PubSubMessageEvent event) { .proxyDataManager()
super.handlePubSubMessageEvent(event); .isPlayerTrulyOnProxy(proxyId, event.getConnection().getUniqueId())) {
} event.completeIntent((Plugin) plugin);
} else {
@EventHandler if (plugin.configuration().kickWhenOnline()) {
public void onServerConnectedEvent(ServerConnectedEvent event) { kickPlayer(
final String currentServer = event.getServer().getInfo().getName(); event.getConnection().getUniqueId(),
final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName(); bPlugin.langConfiguration().messages().loggedInFromOtherLocation());
super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); // wait 3 seconds before releasing the event
} plugin.executeAsyncAfter(
() -> event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3);
private final BungeeComponentSerializer BUNGEE_COMPONENT_SERIALIZER = BungeeComponentSerializer.get();
private final static MiniMessage MINI_MESSAGE_SERIALIZER = MiniMessage.miniMessage();
@Override
public boolean handleSerializedKick(UUID uuid, String serializedMiniMessage) {
ProxiedPlayer player = plugin.getPlayer(uuid);
if (player == null) return false;
// decode the adventure component
if (serializedMiniMessage == null) {
// kick the player too even if the message is invalid
player.disconnect(BUNGEE_COMPONENT_SERIALIZER.serialize(Component.empty()));
plugin.logWarn("unable to decode serialized adventure component because its empty or null");
} else {
Component message = MINI_MESSAGE_SERIALIZER.deserialize(serializedMiniMessage);
player.disconnect(BUNGEE_COMPONENT_SERIALIZER.serialize(message));
}
return true;
}
public void kickPlayer(UUID player, Component message) {
serializedPlayerKick(player, MINI_MESSAGE_SERIALIZER.serialize(message));
}
@EventHandler
public void onLoginEvent(LoginEvent event) {
event.registerIntent((Plugin) plugin);
// check if online
if (getLastOnline(event.getConnection().getUniqueId()) == 0) {
// because something can go wrong and proxy somehow does not update player data correctly on shutdown
// we have to check proxy if it has the player
String proxyId = getProxyFor(event.getConnection().getUniqueId());
if (proxyId == null || !plugin.proxyDataManager().isPlayerTrulyOnProxy(proxyId, event.getConnection().getUniqueId())) {
event.completeIntent((Plugin) plugin);
} else {
if (plugin.configuration().kickWhenOnline()) {
kickPlayer(event.getConnection().getUniqueId(), bPlugin.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(bPlugin.langConfiguration().messages().alreadyLoggedIn()));
event.completeIntent((Plugin) plugin);
}
}
} else { } else {
event.completeIntent((Plugin) plugin); event.setCancelled(true);
event.setCancelReason(
BungeeComponentSerializer.get()
.serialize(bPlugin.langConfiguration().messages().alreadyLoggedIn()));
event.completeIntent((Plugin) plugin);
} }
}
} } else {
event.completeIntent((Plugin) plugin);
@EventHandler
public void onLoginEvent(PostLoginEvent event) {
super.addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getName(), event.getPlayer().getAddress().getAddress());
}
@EventHandler
public void onDisconnectEvent(PlayerDisconnectEvent event) {
super.removePlayer(event.getPlayer().getUniqueId());
} }
}
@EventHandler
public void onLoginEvent(PostLoginEvent event) {
super.addPlayer(
event.getPlayer().getUniqueId(),
event.getPlayer().getName(),
event.getPlayer().getAddress().getAddress());
}
@EventHandler
public void onDisconnectEvent(PlayerDisconnectEvent event) {
super.removePlayer(event.getPlayer().getUniqueId());
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import co.aikar.commands.BungeeCommandManager; import co.aikar.commands.BungeeCommandManager;
@ -15,8 +14,8 @@ import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
@ -31,6 +30,14 @@ import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetwork
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.logging.Level;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.limework.valiobungee.config.lang.LangConfigLoader; import net.limework.valiobungee.config.lang.LangConfigLoader;
import net.limework.valiobungee.config.lang.LangConfiguration; import net.limework.valiobungee.config.lang.LangConfiguration;
@ -43,327 +50,333 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
import java.io.IOException; public class RedisBungee extends Plugin
import java.lang.reflect.Field; implements RedisBungeePlugin<ProxiedPlayer>,
import java.net.InetAddress; ConfigLoader,
import java.util.HashSet; LangConfigLoader,
import java.util.Set; ApiPlatformSupport {
import java.util.UUID;
import java.util.concurrent.*;
import java.util.logging.Level;
private static RedisBungeeAPI apiStatic;
private AbstractRedisBungeeAPI api;
private RedisBungeeMode redisBungeeMode;
private ProxyDataManager proxyDataManager;
private BungeePlayerDataManager playerDataManager;
private ScheduledTask heartbeatTask;
private ScheduledTask cleanupTask;
private Summoner<?> summoner;
private UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private LangConfiguration langConfiguration;
private BungeeCommandManager commandManager;
public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlayer>, ConfigLoader, LangConfigLoader, ApiPlatformSupport { private final Logger logger = LoggerFactory.getLogger("RedisBungee");
private static RedisBungeeAPI apiStatic; @Override
private AbstractRedisBungeeAPI api; public RedisBungeeConfiguration configuration() {
private RedisBungeeMode redisBungeeMode; return this.configuration;
private ProxyDataManager proxyDataManager; }
private BungeePlayerDataManager playerDataManager;
private ScheduledTask heartbeatTask;
private ScheduledTask cleanupTask;
private Summoner<?> summoner;
private UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private LangConfiguration langConfiguration;
private BungeeCommandManager commandManager;
private final Logger logger = LoggerFactory.getLogger("RedisBungee"); public LangConfiguration langConfiguration() {
return this.langConfiguration;
}
@Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override @Override
public RedisBungeeConfiguration configuration() { public ProxyDataManager proxyDataManager() {
return this.configuration; return this.proxyDataManager;
}
@Override
public PlayerDataManager<ProxiedPlayer> playerDataManager() {
return this.playerDataManager;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public void fireEvent(Object event) {
this.getProxy().getPluginManager().callEvent((Event) event);
}
@Override
public boolean isOnlineMode() {
return this.getProxy().getConfig().isOnlineMode();
}
@Override
public void logInfo(String msg) {
this.logger.info(msg);
}
@Override
public void logInfo(String format, Object... object) {
this.logger.info(format, object);
}
@Override
public void logWarn(String msg) {
this.logger.warn(msg);
}
@Override
public void logWarn(String format, Object... object) {
this.logger.warn(format, object);
}
@Override
public void logFatal(String msg) {
this.logger.error(msg);
}
@Override
public void logFatal(String format, Throwable throwable) {
this.logger.error(format, throwable);
}
@Override
public ProxiedPlayer getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid);
}
@Override
public ProxiedPlayer getPlayer(String name) {
return this.getProxy().getPlayer(name);
}
@Override
public UUID getPlayerUUID(String player) {
return this.getProxy().getPlayer(player).getUniqueId();
}
@Override
public String getPlayerName(UUID player) {
return this.getProxy().getPlayer(player).getName();
}
@Override
public String getPlayerServerName(ProxiedPlayer player) {
return player.getServer().getInfo().getName();
}
@Override
public boolean isPlayerOnAServer(ProxiedPlayer player) {
return player.getServer() != null;
}
@Override
public InetAddress getPlayerIp(ProxiedPlayer player) {
return player.getAddress().getAddress();
}
@Override
public void initialize() {
logInfo("Initializing RedisBungee.....");
logInfo("Version: {}", Constants.VERSION);
ThreadFactory factory = ((ThreadPoolExecutor) getExecutorService()).getThreadFactory();
ScheduledExecutorService service = Executors.newScheduledThreadPool(24, factory);
try {
Field field = Plugin.class.getDeclaredField("service");
field.setAccessible(true);
ExecutorService builtinService = (ExecutorService) field.get(this);
field.set(this, service);
builtinService.shutdownNow();
} catch (IllegalAccessException | NoSuchFieldException e) {
getLogger().log(Level.WARNING, "Can't replace BungeeCord thread pool with our own");
getLogger().log(Level.WARNING, "skipping replacement.....");
} }
try {
public LangConfiguration langConfiguration() { loadConfig(this, getDataFolder().toPath());
return this.langConfiguration; loadLangConfig(this, getDataFolder().toPath());
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
} }
// init the proxy data manager
this.proxyDataManager =
new ProxyDataManager(this) {
@Override
public Set<UUID> getLocalOnlineUUIDs() {
HashSet<UUID> uuids = new HashSet<>();
ProxyServer.getInstance()
.getPlayers()
.forEach((proxiedPlayer) -> uuids.add(proxiedPlayer.getUniqueId()));
return uuids;
}
@Override @Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() { protected void handlePlatformCommandExecution(String command) {
return this.api; logInfo("Dispatching {}", command);
} ProxyServer.getInstance()
.getPluginManager()
@Override .dispatchCommand(RedisBungeeCommandSender.getSingleton(), command);
public ProxyDataManager proxyDataManager() { }
return this.proxyDataManager;
}
@Override
public PlayerDataManager<ProxiedPlayer> playerDataManager() {
return this.playerDataManager;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public void fireEvent(Object event) {
this.getProxy().getPluginManager().callEvent((Event) event);
}
@Override
public boolean isOnlineMode() {
return this.getProxy().getConfig().isOnlineMode();
}
@Override
public void logInfo(String msg) {
this.logger.info(msg);
}
@Override
public void logInfo(String format, Object... object) {
this.logger.info(format, object);
}
@Override
public void logWarn(String msg) {
this.logger.warn(msg);
}
@Override
public void logWarn(String format, Object... object) {
this.logger.warn(format, object);
}
@Override
public void logFatal(String msg) {
this.logger.error(msg);
}
@Override
public void logFatal(String format, Throwable throwable) {
this.logger.error(format, throwable);
}
@Override
public ProxiedPlayer getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid);
}
@Override
public ProxiedPlayer getPlayer(String name) {
return this.getProxy().getPlayer(name);
}
@Override
public UUID getPlayerUUID(String player) {
return this.getProxy().getPlayer(player).getUniqueId();
}
@Override
public String getPlayerName(UUID player) {
return this.getProxy().getPlayer(player).getName();
}
@Override
public String getPlayerServerName(ProxiedPlayer player) {
return player.getServer().getInfo().getName();
}
@Override
public boolean isPlayerOnAServer(ProxiedPlayer player) {
return player.getServer() != null;
}
@Override
public InetAddress getPlayerIp(ProxiedPlayer player) {
return player.getAddress().getAddress();
}
@Override
public void initialize() {
logInfo("Initializing RedisBungee.....");
logInfo("Version: {}", Constants.VERSION);
ThreadFactory factory = ((ThreadPoolExecutor) getExecutorService()).getThreadFactory();
ScheduledExecutorService service = Executors.newScheduledThreadPool(24, factory);
try {
Field field = Plugin.class.getDeclaredField("service");
field.setAccessible(true);
ExecutorService builtinService = (ExecutorService) field.get(this);
field.set(this, service);
builtinService.shutdownNow();
} catch (IllegalAccessException | NoSuchFieldException e) {
getLogger().log(Level.WARNING, "Can't replace BungeeCord thread pool with our own");
getLogger().log(Level.WARNING, "skipping replacement.....");
}
try {
loadConfig(this, getDataFolder().toPath());
loadLangConfig(this, getDataFolder().toPath());
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
}
// init the proxy data manager
this.proxyDataManager = new ProxyDataManager(this) {
@Override
public Set<UUID> getLocalOnlineUUIDs() {
HashSet<UUID> 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); this.playerDataManager = new BungeePlayerDataManager(this);
getProxy().getPluginManager().registerListener(this, this.playerDataManager); getProxy().getPluginManager().registerListener(this, this.playerDataManager);
getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this)); getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this));
// start listening // start listening
getProxy().getScheduler().runAsync(this, proxyDataManager); getProxy().getScheduler().runAsync(this, proxyDataManager);
// heartbeat // heartbeat
this.heartbeatTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS); this.heartbeatTask =
// cleanup getProxy()
this.cleanupTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS); .getScheduler()
// init the http lib .schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS);
InitialUtils.checkRedisVersion(this); // cleanup
uuidTranslator = new UUIDTranslator(this); this.cleanupTask =
getProxy()
.getScheduler()
.schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS);
// init the http lib
InitialUtils.checkRedisVersion(this);
uuidTranslator = new UUIDTranslator(this);
// register plugin messages channel. // register plugin messages channel.
getProxy().registerChannel("legacy:redisbungee"); getProxy().registerChannel("legacy:redisbungee");
getProxy().registerChannel("RedisBungee"); getProxy().registerChannel("RedisBungee");
// init the api // init the api
this.api = new RedisBungeeAPI(this); this.api = new RedisBungeeAPI(this);
apiStatic = (RedisBungeeAPI) this.api; apiStatic = (RedisBungeeAPI) this.api;
// commands // commands
CommandPlatformHelper.init(new BungeeCommandPlatformHelper()); CommandPlatformHelper.init(new BungeeCommandPlatformHelper());
this.commandManager = new BungeeCommandManager(this); this.commandManager = new BungeeCommandManager(this);
CommandLoader.initCommands(this.commandManager, this); CommandLoader.initCommands(this.commandManager, this);
logInfo("RedisBungee initialized successfully "); logInfo("RedisBungee initialized successfully ");
}
@Override
public void stop() {
logInfo("Turning off redis connections.....");
getProxy().getPluginManager().unregisterListeners(this);
if (this.cleanupTask != null) {
this.cleanupTask.cancel();
} }
if (heartbeatTask != null) {
@Override heartbeatTask.cancel();
public void stop() {
logInfo("Turning off redis connections.....");
getProxy().getPluginManager().unregisterListeners(this);
if (this.cleanupTask != null) {
this.cleanupTask.cancel();
}
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
try {
this.proxyDataManager.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
this.summoner.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (this.commandManager != null) {
this.commandManager.unregisterCommands();
}
logInfo("RedisBungee shutdown successfully");
} }
try {
@Override this.proxyDataManager.close();
public Summoner<?> getSummoner() { } catch (Exception e) {
return this.summoner; throw new RuntimeException(e);
} }
try {
@Override this.summoner.close();
public RedisBungeeMode getRedisBungeeMode() { } catch (IOException e) {
return this.redisBungeeMode; throw new RuntimeException(e);
} }
if (this.commandManager != null) {
@Override this.commandManager.unregisterCommands();
public void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().runAsync(this, runnable);
} }
logInfo("RedisBungee shutdown successfully");
}
@Override @Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) { public Summoner<?> getSummoner() {
this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit); return this.summoner;
}
@Override
public RedisBungeeMode getRedisBungeeMode() {
return this.redisBungeeMode;
}
@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 onEnable() {
initialize();
}
@Override
public void onDisable() {
stop();
}
@Override
public IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(
UUID uuid, String previousServer, String server) {
return new PlayerChangedServerNetworkEvent(uuid, previousServer, server);
}
@Override
public IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid) {
return new PlayerJoinedNetworkEvent(uuid);
}
@Override
public IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid) {
return new PlayerLeftNetworkEvent(uuid);
}
@Override
public IPubSubMessageEvent createPubSubEvent(String channel, String message) {
return new PubSubMessageEvent(channel, message);
}
@Override
public void onConfigLoad(
RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode) {
this.configuration = configuration;
this.redisBungeeMode = mode;
this.summoner = summoner;
}
@Override
public void onLangConfigLoad(LangConfiguration langConfiguration) {
this.langConfiguration = langConfiguration;
}
@Override
public String platformId() {
return "bungeecord";
}
@Override
public void kickPlayer(UUID player, Component message) {
this.playerDataManager.kickPlayer(player, message);
}
/**
* This returns an instance of {@link RedisBungeeAPI}
*
* @return the {@link AbstractRedisBungeeAPI} object instance.
* @deprecated Please use {@link RedisBungeeAPI#getRedisBungeeApi()} this class intended to for
* old plugins that no longer updated.
*/
@Deprecated
public static RedisBungeeAPI getApi() {
return apiStatic;
}
@Deprecated
public JedisPool getPool() {
if (api.getMode() == RedisBungeeMode.SINGLE) {
JedisPool jedisPool = ((JedisPooledSummoner) getSummoner()).getCompatibilityJedisPool();
if (jedisPool == null) {
throw new IllegalStateException(
"JedisPool compatibility mode is disabled, Please enable it in the RedisBungee config.yml");
}
return jedisPool;
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
} }
}
@Override
public void onEnable() {
initialize();
}
@Override
public void onDisable() {
stop();
}
@Override
public IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
return new PlayerChangedServerNetworkEvent(uuid, previousServer, server);
}
@Override
public IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid) {
return new PlayerJoinedNetworkEvent(uuid);
}
@Override
public IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid) {
return new PlayerLeftNetworkEvent(uuid);
}
@Override
public IPubSubMessageEvent createPubSubEvent(String channel, String message) {
return new PubSubMessageEvent(channel, message);
}
@Override
public void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode) {
this.configuration = configuration;
this.redisBungeeMode = mode;
this.summoner = summoner;
}
@Override
public void onLangConfigLoad(LangConfiguration langConfiguration) {
this.langConfiguration = langConfiguration;
}
@Override
public String platformId() {
return "bungeecord";
}
@Override
public void kickPlayer(UUID player, Component message) {
this.playerDataManager.kickPlayer(player, message);
}
/**
* This returns an instance of {@link RedisBungeeAPI}
*
* @return the {@link AbstractRedisBungeeAPI} object instance.
* @deprecated Please use {@link RedisBungeeAPI#getRedisBungeeApi()} this class intended to for old plugins that no longer updated.
*/
@Deprecated
public static RedisBungeeAPI getApi() {
return apiStatic;
}
@Deprecated
public JedisPool getPool() {
if (api.getMode() == RedisBungeeMode.SINGLE) {
JedisPool jedisPool = ((JedisPooledSummoner) getSummoner()).getCompatibilityJedisPool();
if (jedisPool == null) {
throw new IllegalStateException("JedisPool compatibility mode is disabled, Please enable it in the RedisBungee config.yml");
}
return jedisPool;
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
}
}
} }

View File

@ -1,84 +1,68 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
public class RedisBungeeCommandSender implements CommandSender { public class RedisBungeeCommandSender implements CommandSender {
private static final RedisBungeeCommandSender singleton; private static final RedisBungeeCommandSender singleton;
static { static {
singleton = new RedisBungeeCommandSender(); singleton = new RedisBungeeCommandSender();
} }
public static RedisBungeeCommandSender getSingleton() { public static RedisBungeeCommandSender getSingleton() {
return singleton; return singleton;
} }
@Override @Override
public String getName() { public String getName() {
return "RedisBungee"; return "RedisBungee";
} }
@Override @Override
public void sendMessage(String s) { public void sendMessage(String s) {}
} @Override
public void sendMessages(String... strings) {}
@Override @Override
public void sendMessages(String... strings) { public void sendMessage(BaseComponent... baseComponents) {}
} @Override
public void sendMessage(BaseComponent baseComponent) {}
@Override @Override
public void sendMessage(BaseComponent... baseComponents) { public Collection<String> getGroups() {
return null;
}
} @Override
public void addGroups(String... strings) {}
@Override @Override
public void sendMessage(BaseComponent baseComponent) { public void removeGroups(String... strings) {}
} @Override
public boolean hasPermission(String s) {
return true;
}
@Override @Override
public Collection<String> getGroups() { public void setPermission(String s, boolean b) {}
return null;
}
@Override @Override
public void addGroups(String... strings) { public Collection<String> getPermissions() {
return Collections.emptySet();
} }
@Override
public void removeGroups(String... strings) {
}
@Override
public boolean hasPermission(String s) {
return true;
}
@Override
public void setPermission(String s, boolean b) {
}
@Override
public Collection<String> getPermissions() {
return Collections.emptySet();
}
} }

View File

@ -1,23 +1,24 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.*;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.HandleMotdOrder; import com.imaginarycode.minecraft.redisbungee.api.config.HandleMotdOrder;
import java.util.*;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.AbstractReconnectHandler; import net.md_5.bungee.api.AbstractReconnectHandler;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
@ -31,162 +32,191 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority; import net.md_5.bungee.event.EventPriority;
import java.util.*;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.*;
public class RedisBungeeListener implements Listener { public class RedisBungeeListener implements Listener {
private final RedisBungee plugin; private final RedisBungee plugin;
public RedisBungeeListener(RedisBungee plugin) { public RedisBungeeListener(RedisBungee plugin) {
this.plugin = plugin; this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPingFirst(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.FIRST) {
return;
} }
onPing0(event);
}
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.NORMAL)
public void onPingFirst(ProxyPingEvent event) { public void onPingNormal(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.FIRST) { if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.NORMAL) {
return; return;
}
onPing0(event);
} }
onPing0(event);
}
@EventHandler(priority = EventPriority.NORMAL) @EventHandler(priority = EventPriority.HIGHEST)
public void onPingNormal(ProxyPingEvent event) { public void onPingLast(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.NORMAL) { if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.LAST) {
return; return;
}
onPing0(event);
} }
onPing0(event);
}
@EventHandler(priority = EventPriority.HIGHEST) private void onPing0(ProxyPingEvent event) {
public void onPingLast(ProxyPingEvent event) { if (!plugin.configuration().handleMotd()) return;
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.LAST) { if (plugin
return; .configuration()
} .getExemptAddresses()
onPing0(event); .contains(event.getConnection().getAddress().getAddress())) return;
} ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
private void onPing0(ProxyPingEvent event) { if (forced != null && event.getConnection().getListener().isPingPassthrough()) return;
if (!plugin.configuration().handleMotd()) return; event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers());
if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getAddress().getAddress())) return; }
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
if (forced != null && event.getConnection().getListener().isPingPassthrough()) return; @SuppressWarnings("UnstableApiUsage")
event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers()); @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);
@SuppressWarnings("UnstableApiUsage") String subchannel = in.readUTF();
@EventHandler ByteArrayDataOutput out = ByteStreams.newDataOutput();
public void onPluginMessage(PluginMessageEvent event) { String type;
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(); switch (subchannel) {
ByteArrayDataOutput out = ByteStreams.newDataOutput(); case "PlayerList" -> {
String type; out.writeUTF("PlayerList");
Set<UUID> original = Collections.emptySet();
switch (subchannel) { type = in.readUTF();
case "PlayerList" -> { if (type.equals("ALL")) {
out.writeUTF("PlayerList"); out.writeUTF("ALL");
Set<UUID> original = Collections.emptySet(); original = plugin.proxyDataManager().networkPlayers();
type = in.readUTF(); } else {
if (type.equals("ALL")) { out.writeUTF(type);
out.writeUTF("ALL"); try {
original = plugin.proxyDataManager().networkPlayers(); original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} else { } catch (IllegalArgumentException ignored) {
out.writeUTF(type); }
try {
original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> 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<String, UUID> 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<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> 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;
}
} }
Set<String> players = new HashSet<>();
((Server) event.getSender()).sendData(currentChannel, out.toByteArray()); for (UUID uuid : original)
}); players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false));
} out.writeUTF(Joiner.on(',').join(players));
} }
case "PlayerCount" -> {
@EventHandler out.writeUTF("PlayerCount");
public void onServerConnectEvent(ServerConnectEvent event) { type = in.readUTF();
if (event.getReason() == ServerConnectEvent.Reason.JOIN_PROXY && plugin.configuration().handleReconnectToLastServer()) { if (type.equals("ALL")) {
ProxiedPlayer player = event.getPlayer(); out.writeUTF("ALL");
String lastServer = plugin.playerDataManager().getLastServerFor(event.getPlayer().getUniqueId()); out.writeInt(plugin.proxyDataManager().totalNetworkPlayers());
if (lastServer == null) return; } else {
player.sendMessage(BungeeComponentSerializer.get().serialize(plugin.langConfiguration().messages().serverConnecting(player.getLocale(), lastServer))); out.writeUTF(type);
ServerInfo serverInfo = ProxyServer.getInstance().getServerInfo(lastServer); try {
if (serverInfo == null) { out.writeInt(
player.sendMessage(BungeeComponentSerializer.get().serialize(plugin.langConfiguration().messages().serverNotFound(player.getLocale(), lastServer))); 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<String, UUID> 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<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> 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; return;
}
} }
event.setTarget(serverInfo);
} ((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);
}
}
} }

View File

@ -4,6 +4,9 @@ plugins {
alias(libs.plugins.run.velocity) alias(libs.plugins.run.velocity)
} }
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies { dependencies {
implementation(project(":RedisBungee-Velocity")) implementation(project(":RedisBungee-Velocity"))
compileOnly(libs.platform.velocity) compileOnly(libs.platform.velocity)

View File

@ -1,45 +1,43 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.permission.Tristate;
import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.permission.PermissionChecker;
public class RedisBungeeCommandSource implements CommandSource { public class RedisBungeeCommandSource implements CommandSource {
private static final RedisBungeeCommandSource singleton; private static final RedisBungeeCommandSource singleton;
private final PermissionChecker permissionChecker = PermissionChecker.always(net.kyori.adventure.util.TriState.TRUE); private final PermissionChecker permissionChecker =
PermissionChecker.always(net.kyori.adventure.util.TriState.TRUE);
static { static {
singleton = new RedisBungeeCommandSource(); singleton = new RedisBungeeCommandSource();
} }
public static RedisBungeeCommandSource getSingleton() { public static RedisBungeeCommandSource getSingleton() {
return singleton; return singleton;
} }
@Override
public boolean hasPermission(String permission) {
return this.permissionChecker.test(permission);
}
@Override @Override
public boolean hasPermission(String permission) { public Tristate getPermissionValue(String s) {
return this.permissionChecker.test(permission); return Tristate.TRUE;
} }
@Override @Override
public Tristate getPermissionValue(String s) { public PermissionChecker getPermissionChecker() {
return Tristate.TRUE; return this.permissionChecker;
} }
@Override
public PermissionChecker getPermissionChecker() {
return this.permissionChecker;
}
} }

View File

@ -1,22 +1,23 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.HandleMotdOrder; import com.imaginarycode.minecraft.redisbungee.api.config.HandleMotdOrder;
import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
@ -27,173 +28,196 @@ import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.proxy.server.ServerPing;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; 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 { public class RedisBungeeListener {
private final RedisBungeeVelocityPlugin plugin; private final RedisBungeeVelocityPlugin plugin;
public RedisBungeeListener(RedisBungeeVelocityPlugin plugin) { public RedisBungeeListener(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
}
@Subscribe(order = PostOrder.FIRST)
public void onPingFirst(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.FIRST) {
return;
}
onPing0(event);
}
@Subscribe(order = PostOrder.NORMAL)
public void onPingNormal(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.NORMAL) {
return;
}
onPing0(event);
}
@Subscribe(order = PostOrder.LAST)
public void onPingLast(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.LAST) {
return;
}
onPing0(event);
}
private void onPing0(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;
} }
@Subscribe(order = PostOrder.FIRST) event.setResult(PluginMessageEvent.ForwardResult.handled());
public void onPingFirst(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.FIRST) {
return;
}
onPing0(event);
}
@Subscribe(order = PostOrder.NORMAL) plugin.executeAsync(
public void onPingNormal(ProxyPingEvent event) { () -> {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.NORMAL) { ByteArrayDataInput in = event.dataAsDataStream();
return;
}
onPing0(event);
}
@Subscribe(order = PostOrder.LAST) String subchannel = in.readUTF();
public void onPingLast(ProxyPingEvent event) { ByteArrayDataOutput out = ByteStreams.newDataOutput();
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.LAST) { String type;
return;
}
onPing0(event);
}
private void onPing0(ProxyPingEvent event) { switch (subchannel) {
if (!plugin.configuration().handleMotd()) return; case "PlayerList" -> {
if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) return; out.writeUTF("PlayerList");
Set<UUID> original = Collections.emptySet();
ServerPing.Builder ping = event.getPing().asBuilder(); type = in.readUTF();
ping.onlinePlayers(plugin.proxyDataManager().totalNetworkPlayers()); if (type.equals("ALL")) {
event.setPing(ping.build()); out.writeUTF("ALL");
} original = plugin.proxyDataManager().networkPlayers();
} else {
@Subscribe out.writeUTF(type);
public void onPluginMessage(PluginMessageEvent event) { try {
if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) { original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
return; } catch (IllegalArgumentException ignored) {
}
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<UUID> 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<String> players = original.stream()
.map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false))
.collect(Collectors.toSet());
out.writeUTF(Joiner.on(',').join(players));
} }
case "PlayerCount" -> { }
out.writeUTF("PlayerCount"); Set<String> players =
type = in.readUTF(); original.stream()
if (type.equals("ALL")) { .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false))
out.writeUTF("ALL"); .collect(Collectors.toSet());
out.writeInt(plugin.proxyDataManager().totalNetworkPlayers()); out.writeUTF(Joiner.on(',').join(players));
} else { }
out.writeUTF(type); case "PlayerCount" -> {
try { out.writeUTF("PlayerCount");
out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); type = in.readUTF();
} catch (IllegalArgumentException e) { if (type.equals("ALL")) {
out.writeInt(0); out.writeUTF("ALL");
} out.writeInt(plugin.proxyDataManager().totalNetworkPlayers());
} } else {
} out.writeUTF(type);
case "LastOnline" -> { try {
String user = in.readUTF(); out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size());
out.writeUTF("LastOnline"); } catch (IllegalArgumentException e) {
out.writeUTF(user); out.writeInt(0);
out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
}
case "ServerPlayers" -> {
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> 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<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> 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))));
} }
}
}
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<String, UUID> multimap =
plugin.getAbstractRedisBungeeApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT" -> includesUsers = false;
case "PLAYERS" -> includesUsers = true;
default -> { default -> {
return; // TODO: Should I raise an error?
return;
} }
}
out.writeUTF(type1);
if (includesUsers) {
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : multimap.entries()) {
human.put(
entry.getKey(),
plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
}
serializeMultimap(human, true, out);
} else {
serializeMultiset(multimap.keys(), out);
}
} }
try { case "Proxy" -> {
// ServerConnection throws IllegalStateException when connection dies somehow so just ignore :/ out.writeUTF("Proxy");
((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); out.writeUTF(plugin.configuration().getProxyId());
} catch (IllegalStateException ignored) {
} }
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;
}
}
try {
// ServerConnection throws IllegalStateException when connection dies somehow so just
// ignore :/
((ServerConnection) event.getSource())
.sendPluginMessage(event.getIdentifier(), out.toByteArray());
} catch (IllegalStateException ignored) {
}
}); });
}
@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<RegisteredServer> 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);
} }
}
@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<RegisteredServer> 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);
}
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import co.aikar.commands.VelocityCommandManager; import co.aikar.commands.VelocityCommandManager;
@ -16,10 +15,8 @@ import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; 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.loaders.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
@ -27,6 +24,8 @@ import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; 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.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
@ -43,12 +42,6 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.scheduler.ScheduledTask; import com.velocitypowered.api.scheduler.ScheduledTask;
import net.kyori.adventure.text.Component;
import net.limework.valiobungee.config.lang.LangConfiguration;
import net.limework.valiobungee.config.lang.LangConfigLoader;
import org.slf4j.Logger;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.InetAddress; import java.net.InetAddress;
@ -59,307 +52,333 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component;
import net.limework.valiobungee.config.lang.LangConfigLoader;
import net.limework.valiobungee.config.lang.LangConfiguration;
import org.slf4j.Logger;
import redis.clients.jedis.exceptions.JedisConnectionException;
@Plugin(id = "redisbungee", name = "RedisBungee", version = Constants.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"}) @Plugin(
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, ConfigLoader, LangConfigLoader, ApiPlatformSupport { id = "redisbungee",
private final ProxyServer server; name = "RedisBungee",
private final Logger logger; version = Constants.VERSION,
private final Path dataFolder; url = "https://github.com/ProxioDev/RedisBungee",
private final AbstractRedisBungeeAPI api; authors = {"astei", "ProxioDev"})
private Summoner<?> jedisSummoner; public class RedisBungeeVelocityPlugin
private RedisBungeeMode redisBungeeMode; implements RedisBungeePlugin<Player>, ConfigLoader, LangConfigLoader, ApiPlatformSupport {
private final UUIDTranslator uuidTranslator; private final ProxyServer server;
private RedisBungeeConfiguration configuration; private final Logger logger;
private LangConfiguration langConfiguration; private final Path dataFolder;
private final AbstractRedisBungeeAPI api;
private Summoner<?> jedisSummoner;
private RedisBungeeMode redisBungeeMode;
private final UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private LangConfiguration langConfiguration;
private final ProxyDataManager proxyDataManager; private final ProxyDataManager proxyDataManager;
private final VelocityPlayerDataManager playerDataManager; private final VelocityPlayerDataManager playerDataManager;
private ScheduledTask cleanUpTask; private ScheduledTask cleanUpTask;
private ScheduledTask heartbeatTask; private ScheduledTask heartbeatTask;
public static final List<ChannelIdentifier> IDENTIFIERS = List.of( public static final List<ChannelIdentifier> IDENTIFIERS =
MinecraftChannelIdentifier.create("legacy", "redisbungee"), List.of(
new LegacyChannelIdentifier("RedisBungee"), MinecraftChannelIdentifier.create("legacy", "redisbungee"),
// This is needed for clients before 1.13 new LegacyChannelIdentifier("RedisBungee"),
new LegacyChannelIdentifier("legacy:redisbungee") // This is needed for clients before 1.13
); new LegacyChannelIdentifier("legacy:redisbungee"));
private VelocityCommandManager commandManager; private VelocityCommandManager commandManager;
@Inject @Inject
public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { public RedisBungeeVelocityPlugin(
this.server = server; ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
this.logger = logger; this.server = server;
this.dataFolder = dataDirectory; this.logger = logger;
logInfo("Version: {}", Constants.VERSION); this.dataFolder = dataDirectory;
try { logInfo("Version: {}", Constants.VERSION);
loadConfig(this, dataDirectory); try {
loadLangConfig(this, dataDirectory); loadConfig(this, dataDirectory);
} catch (IOException e) { loadLangConfig(this, dataDirectory);
throw new RuntimeException("Unable to load/save config", e); } catch (IOException e) {
} catch (JedisConnectionException e) { throw new RuntimeException("Unable to load/save config", e);
throw new RuntimeException("Unable to connect to your Redis server!", e); } catch (JedisConnectionException e) {
} throw new RuntimeException("Unable to connect to your Redis server!", e);
this.api = new RedisBungeeAPI(this); }
InitialUtils.checkRedisVersion(this); this.api = new RedisBungeeAPI(this);
this.proxyDataManager = new ProxyDataManager(this) { InitialUtils.checkRedisVersion(this);
@Override this.proxyDataManager =
public Set<UUID> getLocalOnlineUUIDs() { new ProxyDataManager(this) {
HashSet<UUID> players = new HashSet<>(); @Override
server.getAllPlayers().forEach(player -> players.add(player.getUniqueId())); public Set<UUID> getLocalOnlineUUIDs() {
return players; HashSet<UUID> players = new HashSet<>();
} server.getAllPlayers().forEach(player -> players.add(player.getUniqueId()));
return players;
}
@Override @Override
protected void handlePlatformCommandExecution(String command) { protected void handlePlatformCommandExecution(String command) {
server.getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), command); server
} .getCommandManager()
.executeAsync(RedisBungeeCommandSource.getSingleton(), command);
}
}; };
this.playerDataManager = new VelocityPlayerDataManager(this); this.playerDataManager = new VelocityPlayerDataManager(this);
uuidTranslator = new UUIDTranslator(this); uuidTranslator = new UUIDTranslator(this);
}
@Override
public Summoner<?> getSummoner() {
return this.jedisSummoner;
}
@Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override
public ProxyDataManager proxyDataManager() {
return this.proxyDataManager;
}
@Override
public PlayerDataManager<Player> playerDataManager() {
return this.playerDataManager;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().buildTask(this, runnable).schedule();
}
@Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
this.getProxy().getScheduler().buildTask(this, runnable).delay(time, timeUnit).schedule();
}
@Override
public void fireEvent(Object event) {
this.getProxy().getEventManager().fireAndForget(event);
}
@Override
public boolean isOnlineMode() {
return this.getProxy().getConfiguration().isOnlineMode();
}
@Override
public void logInfo(String msg) {
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;
}
public LangConfiguration langConfiguration() {
return this.langConfiguration;
}
@Override
public Player getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid).orElse(null);
}
@Override
public Player getPlayer(String name) {
return this.getProxy().getPlayer(name).orElse(null);
}
@Override
public UUID getPlayerUUID(String player) {
return this.getProxy().getPlayer(player).map(Player::getUniqueId).orElse(null);
}
@Override
public String getPlayerName(UUID player) {
return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null);
}
@Override
public String getPlayerServerName(Player player) {
return player
.getCurrentServer()
.map(serverConnection -> serverConnection.getServerInfo().getName())
.orElse(null);
}
@Override
public boolean isPlayerOnAServer(Player player) {
return player.getCurrentServer().isPresent();
}
@Override
public InetAddress getPlayerIp(Player player) {
return player.getRemoteAddress().getAddress();
}
@Override
public void initialize() {
logInfo("Initializing RedisBungee.....");
// start heartbeat task
// 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();
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);
// load commands
CommandPlatformHelper.init(new VelocityCommandPlatformHelper());
this.commandManager = new VelocityCommandManager(this.getProxy(), this);
CommandLoader.initCommands(this.commandManager, this);
logInfo("RedisBungee initialized successfully ");
}
@Override
public void stop() {
logInfo("Turning off redis connections.....");
// Poison the PubSub listener
if (cleanUpTask != null) {
cleanUpTask.cancel();
}
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
try {
this.proxyDataManager.close();
this.jedisSummoner.close();
} catch (Exception e) {
throw new RuntimeException(e);
} }
if (commandManager != null) commandManager.unregisterCommands();
logInfo("RedisBungee shutdown complete");
}
@Override @Override
public Summoner<?> getSummoner() { public void onConfigLoad(
return this.jedisSummoner; RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode) {
} this.jedisSummoner = summoner;
this.configuration = configuration;
this.redisBungeeMode = mode;
}
@Override @Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() { public void onLangConfigLoad(LangConfiguration langConfiguration) {
return this.api; this.langConfiguration = langConfiguration;
} }
@Override @Override
public ProxyDataManager proxyDataManager() { public RedisBungeeMode getRedisBungeeMode() {
return this.proxyDataManager; return this.redisBungeeMode;
} }
@Override @Subscribe(order = PostOrder.FIRST)
public PlayerDataManager<Player> playerDataManager() { public void onProxyInitializeEvent(ProxyInitializeEvent event) {
return this.playerDataManager; initialize();
} }
@Override @Subscribe(order = PostOrder.LAST)
public UUIDTranslator getUuidTranslator() { public void onProxyShutdownEvent(ProxyShutdownEvent event) {
return this.uuidTranslator; stop();
} }
@Override
public IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(
UUID uuid, String previousServer, String server) {
return new PlayerChangedServerNetworkEvent(uuid, previousServer, server);
}
@Override @Override
public void executeAsync(Runnable runnable) { public IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid) {
this.getProxy().getScheduler().buildTask(this, runnable).schedule(); return new PlayerJoinedNetworkEvent(uuid);
} }
@Override @Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) { public IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid) {
this.getProxy().getScheduler().buildTask(this, runnable).delay(time, timeUnit).schedule(); return new PlayerLeftNetworkEvent(uuid);
} }
@Override @Override
public void fireEvent(Object event) { public IPubSubMessageEvent createPubSubEvent(String channel, String message) {
this.getProxy().getEventManager().fireAndForget(event); return new PubSubMessageEvent(channel, message);
} }
@Override public ProxyServer getProxy() {
public boolean isOnlineMode() { return server;
return this.getProxy().getConfiguration().isOnlineMode(); }
}
@Override @Override
public void logInfo(String msg) { public void kickPlayer(UUID player, Component message) {
this.getLogger().info(msg); this.playerDataManager.kickPlayer(player, message);
} }
@Override public Logger getLogger() {
public void logInfo(String format, Object... object) { return logger;
logger.info(format, object); }
}
@Override public Path getDataFolder() {
public void logWarn(String msg) { return this.dataFolder;
this.getLogger().warn(msg); }
}
@Override public InputStream getResourceAsStream(String name) {
public void logWarn(String format, Object... object) { return this.getClass().getClassLoader().getResourceAsStream(name);
logger.warn(format, object); }
}
@Override @Override
public void logFatal(String msg) { public String platformId() {
this.getLogger().error(msg); return "velocity";
} }
@Override
public void logFatal(String format, Throwable throwable) {
logger.error(format, throwable);
}
@Override
public RedisBungeeConfiguration configuration() {
return this.configuration;
}
public LangConfiguration langConfiguration() {
return this.langConfiguration;
}
@Override
public Player getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid).orElse(null);
}
@Override
public Player getPlayer(String name) {
return this.getProxy().getPlayer(name).orElse(null);
}
@Override
public UUID getPlayerUUID(String player) {
return this.getProxy().getPlayer(player).map(Player::getUniqueId).orElse(null);
}
@Override
public String getPlayerName(UUID player) {
return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null);
}
@Override
public String getPlayerServerName(Player player) {
return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
}
@Override
public boolean isPlayerOnAServer(Player player) {
return player.getCurrentServer().isPresent();
}
@Override
public InetAddress getPlayerIp(Player player) {
return player.getRemoteAddress().getAddress();
}
@Override
public void initialize() {
logInfo("Initializing RedisBungee.....");
// start heartbeat task
// 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();
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);
// load commands
CommandPlatformHelper.init(new VelocityCommandPlatformHelper());
this.commandManager = new VelocityCommandManager(this.getProxy(), this);
CommandLoader.initCommands(this.commandManager, this);
logInfo("RedisBungee initialized successfully ");
}
@Override
public void stop() {
logInfo("Turning off redis connections.....");
// Poison the PubSub listener
if (cleanUpTask != null) {
cleanUpTask.cancel();
}
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
try {
this.proxyDataManager.close();
this.jedisSummoner.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
if (commandManager != null) commandManager.unregisterCommands();
logInfo("RedisBungee shutdown complete");
}
@Override
public void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode) {
this.jedisSummoner = summoner;
this.configuration = configuration;
this.redisBungeeMode = mode;
}
@Override
public void onLangConfigLoad(LangConfiguration langConfiguration) {
this.langConfiguration = langConfiguration;
}
@Override
public RedisBungeeMode getRedisBungeeMode() {
return this.redisBungeeMode;
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyInitializeEvent(ProxyInitializeEvent event) {
initialize();
}
@Subscribe(order = PostOrder.LAST)
public void onProxyShutdownEvent(ProxyShutdownEvent event) {
stop();
}
@Override
public IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
return new PlayerChangedServerNetworkEvent(uuid, previousServer, server);
}
@Override
public IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid) {
return new PlayerJoinedNetworkEvent(uuid);
}
@Override
public IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid) {
return new PlayerLeftNetworkEvent(uuid);
}
@Override
public IPubSubMessageEvent createPubSubEvent(String channel, String message) {
return new PubSubMessageEvent(channel, message);
}
public ProxyServer getProxy() {
return server;
}
@Override
public void kickPlayer(UUID player, Component message) {
this.playerDataManager.kickPlayer(player, message);
}
public Logger getLogger() {
return logger;
}
public Path getDataFolder() {
return this.dataFolder;
}
public InputStream getResourceAsStream(String name) {
return this.getClass().getClassLoader().getResourceAsStream(name);
}
@Override
public String platformId() {
return "velocity";
}
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import co.aikar.commands.CommandIssuer; import co.aikar.commands.CommandIssuer;
@ -17,10 +16,9 @@ import net.kyori.adventure.text.Component;
public class VelocityCommandPlatformHelper extends CommandPlatformHelper { public class VelocityCommandPlatformHelper extends CommandPlatformHelper {
@Override @Override
public void sendMessage(CommandIssuer issuer, Component component) { public void sendMessage(CommandIssuer issuer, Component component) {
VelocityCommandIssuer vIssuer = (VelocityCommandIssuer) issuer; VelocityCommandIssuer vIssuer = (VelocityCommandIssuer) issuer;
vIssuer.getIssuer().sendMessage(component); vIssuer.getIssuer().sendMessage(component);
} }
} }

View File

@ -1,13 +1,12 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager;
@ -23,108 +22,119 @@ import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
public class VelocityPlayerDataManager extends PlayerDataManager<Player> { public class VelocityPlayerDataManager extends PlayerDataManager<Player> {
private final RedisBungeeVelocityPlugin vplugin; private final RedisBungeeVelocityPlugin vplugin;
public VelocityPlayerDataManager(RedisBungeeVelocityPlugin plugin) { public VelocityPlayerDataManager(RedisBungeeVelocityPlugin plugin) {
super(plugin); super(plugin);
this.vplugin = plugin; this.vplugin = plugin;
}
@Subscribe
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
handleNetworkPlayerServerChange(event);
}
@Subscribe
public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) {
handleNetworkPlayerQuit(event);
}
@Subscribe
public void onNetworkPlayerJoin(PlayerJoinedNetworkEvent event) {
handleNetworkPlayerJoin(event);
}
@Subscribe
public void onPubSubMessageEvent(PubSubMessageEvent event) {
handlePubSubMessageEvent(event);
}
@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 private static final MiniMessage MINI_MESSAGE_SERIALIZER = MiniMessage.miniMessage();
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
handleNetworkPlayerServerChange(event); @Override
public boolean handleSerializedKick(UUID uuid, String serializedMiniMessage) {
Player player = plugin.getPlayer(uuid);
if (player == null) return false;
// decode the adventure component
if (serializedMiniMessage == null) {
// kick the player too even if the message is invalid
player.disconnect(Component.empty());
plugin.logWarn("unable to decode serialized adventure component because its empty or null");
} else {
Component message = MINI_MESSAGE_SERIALIZER.deserialize(serializedMiniMessage);
player.disconnect(message);
} }
return true;
}
@Subscribe public void kickPlayer(UUID player, Component message) {
public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { serializedPlayerKick(player, MINI_MESSAGE_SERIALIZER.serialize(message));
handleNetworkPlayerQuit(event); }
}
@Subscribe @Subscribe
public void onNetworkPlayerJoin(PlayerJoinedNetworkEvent event) { public void onLoginEvent(LoginEvent event, Continuation continuation) {
handleNetworkPlayerJoin(event); // check if online
} if (getLastOnline(event.getPlayer().getUniqueId()) == 0) {
// because something can go wrong and proxy somehow does not update player data correctly on
@Subscribe // shutdown
public void onPubSubMessageEvent(PubSubMessageEvent event) { // we have to check proxy if it has the player
handlePubSubMessageEvent(event); String proxyId = getProxyFor(event.getPlayer().getUniqueId());
} if (proxyId == null
|| !plugin
@Subscribe .proxyDataManager()
public void onServerConnectedEvent(ServerConnectedEvent event) { .isPlayerTrulyOnProxy(proxyId, event.getPlayer().getUniqueId())) {
final String currentServer = event.getServer().getServerInfo().getName(); continuation.resume();
final String oldServer; } else {
if (event.getPreviousServer().isPresent()) { if (plugin.configuration().kickWhenOnline()) {
oldServer = event.getPreviousServer().get().getServerInfo().getName(); kickPlayer(
event.getPlayer().getUniqueId(),
vplugin.langConfiguration().messages().loggedInFromOtherLocation());
// wait 3 seconds before releasing the event
plugin.executeAsyncAfter(continuation::resume, TimeUnit.SECONDS, 3);
} else { } else {
oldServer = null; event.setResult(
ResultedEvent.ComponentResult.denied(
vplugin.langConfiguration().messages().alreadyLoggedIn()));
continuation.resume();
} }
super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); }
} else {
continuation.resume();
} }
}
private final static MiniMessage MINI_MESSAGE_SERIALIZER = MiniMessage.miniMessage(); @Subscribe
@Override public void onLoginEvent(PostLoginEvent event) {
public boolean handleSerializedKick(UUID uuid, String serializedMiniMessage) { addPlayer(
Player player = plugin.getPlayer(uuid); event.getPlayer().getUniqueId(),
if (player == null) return false; event.getPlayer().getUsername(),
// decode the adventure component event.getPlayer().getRemoteAddress().getAddress());
if (serializedMiniMessage == null) { }
// kick the player too even if the message is invalid
player.disconnect(Component.empty());
plugin.logWarn("unable to decode serialized adventure component because its empty or null");
} else {
Component message = MINI_MESSAGE_SERIALIZER.deserialize(serializedMiniMessage);
player.disconnect(message);
}
return true;
}
public void kickPlayer(UUID player, Component message) { @Subscribe
serializedPlayerKick(player, MINI_MESSAGE_SERIALIZER.serialize(message)); public void onDisconnectEvent(DisconnectEvent event) {
} if (event.getLoginStatus() == DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN
|| event.getLoginStatus() == DisconnectEvent.LoginStatus.PRE_SERVER_JOIN) {
@Subscribe removePlayer(event.getPlayer().getUniqueId());
public void onLoginEvent(LoginEvent event, Continuation continuation) {
// check if online
if (getLastOnline(event.getPlayer().getUniqueId()) == 0) {
// because something can go wrong and proxy somehow does not update player data correctly on shutdown
// we have to check proxy if it has the player
String proxyId = getProxyFor(event.getPlayer().getUniqueId());
if (proxyId == null || !plugin.proxyDataManager().isPlayerTrulyOnProxy(proxyId, event.getPlayer().getUniqueId())) {
continuation.resume();
} else {
if (plugin.configuration().kickWhenOnline()) {
kickPlayer(event.getPlayer().getUniqueId(), vplugin.langConfiguration().messages().loggedInFromOtherLocation());
// wait 3 seconds before releasing the event
plugin.executeAsyncAfter(continuation::resume, TimeUnit.SECONDS, 3);
} else {
event.setResult(ResultedEvent.ComponentResult.denied(vplugin.langConfiguration().messages().alreadyLoggedIn()));
continuation.resume();
}
}
} else {
continuation.resume();
}
}
@Subscribe
public void onLoginEvent(PostLoginEvent event) {
addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getUsername(), event.getPlayer().getRemoteAddress().getAddress());
}
@Subscribe
public void onDisconnectEvent(DisconnectEvent event) {
if (event.getLoginStatus() == DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN || event.getLoginStatus() == DisconnectEvent.LoginStatus.PRE_SERVER_JOIN) {
removePlayer(event.getPlayer().getUniqueId());
}
} }
}
} }

View File

@ -3,6 +3,9 @@ plugins {
`maven-publish` `maven-publish`
} }
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies { dependencies {
api(project(":RedisBungee-API")) { api(project(":RedisBungee-API")) {
// Since velocity already includes guava / configurate / guava exlude them // Since velocity already includes guava / configurate / guava exlude them

View File

@ -1,25 +1,22 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import net.kyori.adventure.text.Component;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.text.Component;
// this class used to redirect calls to keep the implementation and api separate // this class used to redirect calls to keep the implementation and api separate
public interface ApiPlatformSupport { public interface ApiPlatformSupport {
ProxyServer getProxy(); ProxyServer getProxy();
void kickPlayer(UUID player, Component message);
void kickPlayer(UUID player, Component message);
} }

View File

@ -1,89 +1,89 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import java.util.UUID;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
/** /**
* This platform class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungeeAPI#getRedisBungeeApi()} * This platform class exposes some internal RedisBungee functions. You obtain an instance of this
* or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}. * object by invoking {@link RedisBungeeAPI#getRedisBungeeApi()} or somehow you got the Plugin
* instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}.
* *
* @author tuxed * @author tuxed
* @since 0.2.3 * @since 0.2.3
*/ */
public class RedisBungeeAPI extends AbstractRedisBungeeAPI { public class RedisBungeeAPI extends AbstractRedisBungeeAPI {
private static RedisBungeeAPI redisBungeeApi; private static RedisBungeeAPI redisBungeeApi;
public RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
super(plugin);
if (redisBungeeApi == null) {
redisBungeeApi = this;
}
public RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
super(plugin);
if (redisBungeeApi == null) {
redisBungeeApi = this;
} }
}
/** /**
* Get the server where the specified player is playing. This function also deals with the case of local players * Get the server where the specified player is playing. This function also deals with the case of
* as well, and will return local information on them. * local players as well, and will return local information on them.
* *
* @param player a player uuid * @param player a player uuid
* @return {@link ServerInfo} Can be null if proxy can't find it. * @return {@link ServerInfo} Can be null if proxy can't find it.
* @see #getServerNameFor(UUID) * @see #getServerNameFor(UUID)
*/ */
@Nullable @Nullable
public final ServerInfo getServerFor(@NonNull UUID player) { public final ServerInfo getServerFor(@NonNull UUID player) {
String serverName = this.getServerNameFor(player); String serverName = this.getServerNameFor(player);
if (serverName == null) return null; if (serverName == null) return null;
return ((ApiPlatformSupport) this.plugin).getProxy().getServer(serverName).map((RegisteredServer::getServerInfo)).orElse(null); return ((ApiPlatformSupport) this.plugin)
} .getProxy()
.getServer(serverName)
.map((RegisteredServer::getServerInfo))
.orElse(null);
}
/** /**
* Kicks a player from the network * Kicks a player from the network calls {@link #getUuidFromName(String)} to get uuid
* calls {@link #getUuidFromName(String)} to get uuid *
* * @param playerName player name
* @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.12.0
* @since 0.12.0 */
*/ public void kickPlayer(String playerName, Component message) {
public void kickPlayer(String playerName, Component message) { kickPlayer(getUuidFromName(playerName), message);
kickPlayer(getUuidFromName(playerName), message); }
}
/** /**
* Kicks a player from the network * Kicks a player from the network
* *
* @param playerUUID player name * @param playerUUID player name
* @param message kick message that player will see on kick * @param message kick message that player will see on kick
* @since 0.12.0 * @since 0.12.0
*/ */
public void kickPlayer(UUID playerUUID, Component message) { public void kickPlayer(UUID playerUUID, Component message) {
((ApiPlatformSupport) this.plugin).kickPlayer(playerUUID, message); ((ApiPlatformSupport) this.plugin).kickPlayer(playerUUID, message);
} }
/**
/** * Api instance
* Api instance *
* * @return the API instance.
* @return the API instance. * @since 0.6.5
* @since 0.6.5 */
*/ public static RedisBungeeAPI getRedisBungeeApi() {
public static RedisBungeeAPI getRedisBungeeApi() { return redisBungeeApi;
return redisBungeeApi; }
}
} }

View File

@ -1,52 +1,49 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import java.util.UUID; import java.util.UUID;
/** /**
* This event is sent when a player connects to a new server. RedisBungee sends the event only when * This event is sent when a player connects to a new server. RedisBungee sends the event only when
* the proxy the player has been connected to is different than the local proxy. * the proxy the player has been connected to is different than the local proxy.
* <p> *
* This event corresponds to {@link com.velocitypowered.api.event.player.ServerConnectedEvent}, and is fired * <p>This event corresponds to {@link com.velocitypowered.api.event.player.ServerConnectedEvent},
* asynchronously. * and is fired asynchronously.
* *
* @since 0.3.4 * @since 0.3.4
*/ */
public class PlayerChangedServerNetworkEvent implements IPlayerChangedServerNetworkEvent { public class PlayerChangedServerNetworkEvent implements IPlayerChangedServerNetworkEvent {
private final UUID uuid; private final UUID uuid;
private final String previousServer; private final String previousServer;
private final String server; private final String server;
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) { public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
this.uuid = uuid; this.uuid = uuid;
this.previousServer = previousServer; this.previousServer = previousServer;
this.server = server; this.server = server;
} }
@Override @Override
public UUID getUuid() { public UUID getUuid() {
return uuid; return uuid;
} }
@Override @Override
public String getServer() { public String getServer() {
return server; return server;
} }
@Override @Override
public String getPreviousServer() { public String getPreviousServer() {
return previousServer; return previousServer;
} }
} }

View File

@ -1,38 +1,35 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import java.util.UUID; import java.util.UUID;
/** /**
* This event is sent when a player joins the network. RedisBungee sends the event only when * This event is sent when a player joins the network. RedisBungee sends the event only when the
* the proxy the player has been connected to is different than the local proxy. * proxy the player has been connected to is different than the local proxy.
* <p> *
* This event corresponds to {@link com.velocitypowered.api.event.connection.PostLoginEvent}, and is fired * <p>This event corresponds to {@link com.velocitypowered.api.event.connection.PostLoginEvent}, and
* asynchronously. * is fired asynchronously.
* *
* @since 0.3.4 * @since 0.3.4
*/ */
public class PlayerJoinedNetworkEvent implements IPlayerJoinedNetworkEvent { public class PlayerJoinedNetworkEvent implements IPlayerJoinedNetworkEvent {
private final UUID uuid; private final UUID uuid;
public PlayerJoinedNetworkEvent(UUID uuid) { public PlayerJoinedNetworkEvent(UUID uuid) {
this.uuid = uuid; this.uuid = uuid;
} }
@Override @Override
public UUID getUuid() { public UUID getUuid() {
return uuid; return uuid;
} }
} }

View File

@ -1,38 +1,35 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import java.util.UUID; import java.util.UUID;
/** /**
* This event is sent when a player disconnects. RedisBungee sends the event only when * This event is sent when a player disconnects. RedisBungee sends the event only when the proxy the
* the proxy the player has been connected to is different than the local proxy. * player has been connected to is different than the local proxy.
* <p> *
* This event corresponds to {@link com.velocitypowered.api.event.connection.DisconnectEvent}, and is fired * <p>This event corresponds to {@link com.velocitypowered.api.event.connection.DisconnectEvent},
* asynchronously. * and is fired asynchronously.
* *
* @since 0.3.4 * @since 0.3.4
*/ */
public class PlayerLeftNetworkEvent implements IPlayerLeftNetworkEvent { public class PlayerLeftNetworkEvent implements IPlayerLeftNetworkEvent {
private final UUID uuid; private final UUID uuid;
public PlayerLeftNetworkEvent(UUID uuid) { public PlayerLeftNetworkEvent(UUID uuid) {
this.uuid = uuid; this.uuid = uuid;
} }
@Override @Override
public UUID getUuid() { public UUID getUuid() {
return uuid; return uuid;
} }
} }

View File

@ -1,42 +1,39 @@
/* /*
* Copyright (c) 2013-present RedisBungee contributors * Copyright (c) 2026 RedisBungee contributors
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* *
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
package com.imaginarycode.minecraft.redisbungee.events; package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
/** /**
* This event is posted when a PubSub message is received. * This event is posted when a PubSub message is received.
* <p> *
* <strong>Warning</strong>: This event is fired in a separate thread! * <p><strong>Warning</strong>: This event is fired in a separate thread!
* *
* @since 0.2.6 * @since 0.2.6
*/ */
public class PubSubMessageEvent implements IPubSubMessageEvent { public class PubSubMessageEvent implements IPubSubMessageEvent {
private final String channel; private final String channel;
private final String message; private final String message;
public PubSubMessageEvent(String channel, String message) { public PubSubMessageEvent(String channel, String message) {
this.channel = channel; this.channel = channel;
this.message = message; this.message = message;
} }
@Override @Override
public String getChannel() { public String getChannel() {
return channel; return channel;
} }
@Override @Override
public String getMessage() { public String getMessage() {
return message; return message;
} }
} }

View File

@ -9,10 +9,12 @@ rootProject.name = "ValioBungee"
fun configureProject(name: String) { fun configureProject(name: String) {
val projectName = ":valiobungee-$name" val projectName = ":valiobungee-$name"
include(projectName) configureProject(projectName,name)
project(projectName).projectDir = file(name) }
fun configureProject(name: String, path: String) {
include(name)
project(name).projectDir = file(path)
} }
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
@ -24,4 +26,15 @@ dependencyResolutionManagement {
} }
} }
sequenceOf("core", "api").forEach{configureProject(it)} // main project stuff
sequenceOf("core", "api").forEach{configureProject(it)}
// RedisBunggee Project
configureProject(":RedisBungee-API", "redisbungee/api")
configureProject(":RedisBungee-Lang", "redisbungee/lang")
configureProject(":RedisBungee-Commands", "redisbungee/commands")
configureProject(":RedisBungee-Bungee", "redisbungee/proxies/bungeecord/bungeecord-api")
configureProject(":RedisBungee-Proxy-Bungee", "redisbungee/proxies/bungeecord")
configureProject(":RedisBungee-Velocity", "redisbungee/proxies/velocity/velocity-api")
configureProject(":RedisBungee-Proxy-Velocity", "redisbungee/proxies/velocity")