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
name: RedisBungee-Bungee
# Destination path
path: proxies/bungeecord/build/libs/*
path: redisbungee/proxies/bungeecord/build/libs/*
- name: Upload Velocity
uses: actions/upload-artifact@v4.4.0
with:
name: RedisBungee-Velocity
path: proxies/velocity/build/libs/*
path: redisbungee/proxies/velocity/build/libs/*
- name: Upload API
uses: actions/upload-artifact@v4.4.0
with:
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> {
var redisBungeeProjects = sequenceOf("RedisBungee-API", "RedisBungee-Lang", "RedisBungee-Commands", "RedisBungee-Bungee", "RedisBungee-Proxy-Bungee", "RedisBungee-Velocity", "RedisBungee-Proxy-Velocity")
java {
removeUnusedImports()
googleJavaFormat()
if (project.name == "valiobungee-api") {
licenseHeaderFile(file("copyright_header.txt"))
} else if (redisBungeeProjects.contains(project.name)) {
licenseHeaderFile(rootProject.file("redisbungee/copyright_header.txt"))
} else {
licenseHeaderFile(rootProject.file("copyright_header.txt"))
}

View File

@ -1,3 +1,6 @@
group=net.limework
version=1.0.0-SNAPSHOT
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
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]
blossom = "net.kyori.blossom:2.2.0"
indragit = "net.kyori.indra.git:4.0.0"
shadow = "com.gradleup.shadow:9.3.1"
spotless = "com.diffplug.spotless:8.2.0"
protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" }
run-velocity = { id = "xyz.jpenilla.run-velocity", version = "2.3.1" }
[libraries]
redisson = "org.redisson:redisson:4.3.0"
# protobuf
protobuf = { group = "com.google.protobuf", name = "protobuf-java", 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
slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
# testing
testing-juipter = "org.junit.jupiter:junit-jupiter:6.0.3"
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 {
`java-library`
`maven-publish`
@ -8,6 +6,10 @@ plugins {
}
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies {
api(libs.guava)
api(libs.jedis)
@ -22,8 +24,8 @@ sourceSets {
main {
blossom {
javaSources {
property("version", "$version")
property("git-commit", indraGit.commit().toString())
property("version", version.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
*
* 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
*/
* 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;
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.RedisBungeePlugin;
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.Nullable;
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.
* overall its general contains all methods needed by external usage.
@ -31,309 +29,321 @@ import java.util.*;
*/
@SuppressWarnings("unused")
public abstract class AbstractRedisBungeeAPI {
protected final RedisBungeePlugin<?> plugin;
private static AbstractRedisBungeeAPI abstractRedisBungeeAPI;
protected final RedisBungeePlugin<?> plugin;
private static AbstractRedisBungeeAPI abstractRedisBungeeAPI;
public AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
// this does make sure that no one can replace first initiated API class.
if (abstractRedisBungeeAPI == null) {
abstractRedisBungeeAPI = this;
}
this.plugin = plugin;
public AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
// this does make sure that no one can replace first initiated API class.
if (abstractRedisBungeeAPI == null) {
abstractRedisBungeeAPI = this;
}
this.plugin = plugin;
}
/**
* Get a combined count of all players on this network.
*
* @return a count of all players found
*/
public final int getPlayerCount() {
return plugin.proxyDataManager().totalNetworkPlayers();
/**
* Get a combined count of all players on this network.
*
* @return a count of all players found
*/
public final int getPlayerCount() {
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,
* 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 a full list of players on all servers.
*
* @return a immutable Multimap with all players found on this network
* @since 0.2.5
*/
public final Multimap<String, UUID> getServerToPlayers() {
return plugin.playerDataManager().serversToPlayers();
}
/**
* 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 list of players on the server with the given name.
*
* @param server a server name
* @return a Set with all players found on this server
*/
public final Set<UUID> getPlayersOnServer(@NonNull String server) {
return ImmutableSet.copyOf(getServerToPlayers().get(server));
}
/**
* 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 list of players on the specified proxy.
*
* @param proxyID proxy id
* @return a Set with all UUIDs found on this proxy
*/
public final Set<UUID> getPlayersOnProxy(@NonNull String proxyID) {
return plugin.proxyDataManager().getPlayersOn(proxyID);
}
/**
* 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;
}
/**
* Convenience method: Checks if the specified player is online.
*
* @param player a player name
* @return if the player is online
*/
public final boolean isPlayerOnline(@NonNull UUID player) {
return getLastOnline(player) == 0;
}
/**
* Get a full list of players on all servers.
*
* @return a immutable Multimap with all players found on this network
* @since 0.2.5
*/
public final Multimap<String, UUID> getServerToPlayers() {
return plugin.playerDataManager().serversToPlayers();
}
/**
* Get the {@link java.net.InetAddress} associated with this player.
*
* @param player the player to fetch the IP for
* @return an {@link java.net.InetAddress} if the player is online, null otherwise
* @since 0.2.4
*/
public final InetAddress getPlayerIp(@NonNull UUID player) {
return plugin.playerDataManager().getIpFor(player);
}
/**
* Get a list of players on the server with the given name.
*
* @param server a server name
* @return a Set with all players found on this server
*/
public final Set<UUID> getPlayersOnServer(@NonNull String server) {
return ImmutableSet.copyOf(getServerToPlayers().get(server));
}
/**
* Get the RedisBungee proxy ID this player is connected to.
*
* @param player the player to fetch the IP for
* @return the proxy the player is connected to, or null if they are offline
* @since 0.3.3
*/
public final String getProxy(@NonNull UUID player) {
return plugin.playerDataManager().getProxyFor(player);
}
/**
* Get a list of players on the specified proxy.
*
* @param proxyID proxy id
* @return a Set with all UUIDs found on this proxy
*/
public final Set<UUID> getPlayersOnProxy(@NonNull String proxyID) {
return plugin.proxyDataManager().getPlayersOn(proxyID);
}
/**
* Sends a proxy command to all proxies.
*
* @param command the command to send and execute
* @see #sendProxyCommand(String, String)
* @since 0.2.5
*/
public final void sendProxyCommand(@NonNull String command) {
sendProxyCommand("allservers", command);
}
/**
* Convenience method: Checks if the specified player is online.
*
* @param player a player name
* @return if the player is online
*/
public final boolean isPlayerOnline(@NonNull UUID player) {
return getLastOnline(player) == 0;
}
/**
* Sends a proxy command to the proxy with the given ID. "allservers" means all proxies.
*
* @param proxyId a proxy ID
* @param command the command to send and execute
* @see #getProxyId()
* @see #getAllProxies()
* @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.
*
* @param player the player to fetch the IP for
* @return an {@link java.net.InetAddress} if the player is online, null otherwise
* @since 0.2.4
*/
public final InetAddress getPlayerIp(@NonNull UUID player) {
return plugin.playerDataManager().getIpFor(player);
}
/**
* Sends a message to a PubSub channel which makes PubSubMessageEvent fire.
*
* <p>Note: Since 0.12.0 registering a channel api is no longer required
*
* @param channel The PubSub channel
* @param message the message body to send
* @since 0.3.3
*/
public final void sendChannelMessage(@NonNull String channel, @NonNull String message) {
plugin.proxyDataManager().sendChannelMessage(channel, message);
}
/**
* Get the RedisBungee proxy ID this player is connected to.
*
* @param player the player to fetch the IP for
* @return the proxy the player is connected to, or null if they are offline
* @since 0.3.3
*/
public final String getProxy(@NonNull UUID player) {
return plugin.playerDataManager().getProxyFor(player);
}
/**
* Get the current BungeeCord / Velocity proxy ID for this server.
*
* @return the current server ID
* @see #getAllProxies()
* @since 0.8.0
*/
public final String getProxyId() {
return plugin.proxyDataManager().proxyId();
}
/**
* Sends a proxy command to all proxies.
*
* @param command the command to send and execute
* @see #sendProxyCommand(String, String)
* @since 0.2.5
*/
public final void sendProxyCommand(@NonNull String command) {
sendProxyCommand("allservers", command);
}
/**
* Get all the linked proxies in this network.
*
* @return the list of all proxies
* @see #getProxyId()
* @since 0.8.0
*/
public final List<String> getAllProxies() {
return plugin.proxyDataManager().proxiesIds();
}
/**
* Sends a proxy command to the proxy with the given ID. "allservers" means all proxies.
*
* @param proxyId a proxy ID
* @param command the command to send and execute
* @see #getProxyId()
* @see #getAllProxies()
* @since 0.2.5
*/
public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) {
plugin.proxyDataManager().sendCommandTo(proxyId, command);
}
/**
* 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.
*
* <p>For the common use case of translating a list of UUIDs into names, use {@link
* #getHumanPlayersOnline()} instead.
*
* <p>If performance is a concern, use {@link #getNameFromUuid(java.util.UUID, boolean)} as this
* allows you to disable Mojang lookups.
*
* @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.
* <p>
* Note: Since 0.12.0 registering a channel api is no longer required
*
* @param channel The PubSub channel
* @param message the message body to send
* @since 0.3.3
*/
public final void sendChannelMessage(@NonNull String channel, @NonNull String message) {
plugin.proxyDataManager().sendChannelMessage(channel, message);
}
/**
* Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function can
* fall back to Mojang as a last resort if {@code expensiveLookups} is true, so calls
* <strong>may</strong> be blocking.
*
* <p>For the common use case of translating the list of online players into names, use {@link
* #getHumanPlayersOnline()}.
*
* <p>If performance is a concern, set {@code expensiveLookups} to false as this will disable
* lookups via Mojang.
*
* @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.
*
* @return the current server ID
* @see #getAllProxies()
* @since 0.8.0
*/
public final String getProxyId() {
return plugin.proxyDataManager().proxyId();
}
/**
* 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.
*
* <p>If performance is a concern, see {@link #getUuidFromName(String, boolean)}, which disables
* the following functions:
*
* <ul>
* <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.
*
* @return the list of all proxies
* @see #getProxyId()
* @since 0.8.0
*/
public final List<String> getAllProxies() {
return plugin.proxyDataManager().proxiesIds();
}
/**
* 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
* <strong>may</strong> be blocking.
*
* <p>If performance is a concern, set {@code expensiveLookups} to false to disable searching
* Mojang and searching for usernames case-insensitively.
*
* @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
* as a last resort, so calls <strong>may</strong> be blocking.
* <p>
* For the common use case of translating a list of UUIDs into names, use {@link #getHumanPlayersOnline()} instead.
* <p>
* If performance is a concern, use {@link #getNameFromUuid(java.util.UUID, boolean)} as this allows you to disable Mojang lookups.
*
* @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);
}
/**
* 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();
}
/**
* Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function can fall back to Mojang
* as a last resort if {@code expensiveLookups} is true, so calls <strong>may</strong> be blocking.
* <p>
* For the common use case of translating the list of online players into names, use {@link #getHumanPlayersOnline()}.
* <p>
* If performance is a concern, set {@code expensiveLookups} to false as this will disable lookups via Mojang.
*
* @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);
}
/**
* 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);
}
/**
* 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.
* <p>
* If performance is a concern, see {@link #getUuidFromName(String, boolean)}, which disables the following functions:
* <ul>
* <li>Searching local entries case-insensitively</li>
* <li>Searching Mojang</li>
* </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);
}
/**
* 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);
}
/**
* 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 <strong>may</strong> be blocking.
* <p>
* If performance is a concern, set {@code expensiveLookups} to false to disable searching Mojang and searching for usernames
* case-insensitively.
*
* @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);
}
/**
* 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();
}
/**
* 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;
}
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
*
* 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
*/
* 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;
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.IPubSubMessageEvent;
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 redis.clients.jedis.ClusterPipeline;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
public abstract class PlayerDataManager<P> {
protected final RedisBungeePlugin<P> plugin;
private final Object SERVERS_TO_PLAYERS_KEY = new Object();
private final UnifiedJedis unifiedJedis;
private final String proxyId;
private final String networkId;
private final LoadingCache<UUID, String> serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis);
private final LoadingCache<UUID, String> lastServerCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastServerFromRedis);
private final LoadingCache<UUID, String> proxyCache = 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);
protected final RedisBungeePlugin<P> plugin;
private final Object SERVERS_TO_PLAYERS_KEY = new Object();
private final UnifiedJedis unifiedJedis;
private final String proxyId;
private final String networkId;
private final LoadingCache<UUID, String> serverCache =
Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis);
private final LoadingCache<UUID, String> lastServerCache =
Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastServerFromRedis);
private final LoadingCache<UUID, String> proxyCache =
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) {
this.plugin = plugin;
this.unifiedJedis = plugin.proxyDataManager().unifiedJedis();
this.proxyId = plugin.proxyDataManager().proxyId();
this.networkId = plugin.proxyDataManager().networkId();
}
public PlayerDataManager(RedisBungeePlugin<P> plugin) {
this.plugin = plugin;
this.unifiedJedis = plugin.proxyDataManager().unifiedJedis();
this.proxyId = plugin.proxyDataManager().proxyId();
this.networkId = plugin.proxyDataManager().networkId();
}
// handle network wide
// server change
// public void onPlayerChangedServerNetworkEvent
// handle network wide
// server change
// public void onPlayerChangedServerNetworkEvent
// public void onNetworkPlayerQuit
// public void onNetworkPlayerQuit
// public void onNetworkPlayerJoin
// public void onNetworkPlayerJoin
// local events
// public void onPubSubMessageEvent
// local events
// 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) {
this.serverCache.invalidate(event.getUuid());
this.lastServerCache.invalidate(event.getUuid());
protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) {
this.serverCache.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.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
// 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.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid());
this.ipCache.invalidate(event.getUuid());
this.lastOnlineCache.invalidate(event.getUuid());
protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid());
this.ipCache.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.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
// 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.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handleNetworkPlayerJoin(IPlayerJoinedNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid());
this.ipCache.invalidate(event.getUuid());
this.lastOnlineCache.invalidate(event.getUuid());
protected void handleNetworkPlayerJoin(IPlayerJoinedNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid());
this.ipCache.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.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
// 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.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handlePubSubMessageEvent(IPubSubMessageEvent event) {
switch (event.getChannel()) {
case "redisbungee-serverchange" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
String from = null;
if (data.has("from")) from = data.getString("from");
String to = data.getString("to");
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
}
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());
protected void handlePubSubMessageEvent(IPubSubMessageEvent event) {
switch (event.getChannel()) {
case "redisbungee-serverchange" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
String from = null;
if (data.has("from")) from = data.getString("from");
String to = data.getString("to");
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
handleServerChangeRedis(uuid, to);
}
// must check if player is on the local proxy
// 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());
}
case "redisbungee-player-join" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
this.plugin.proxyDataManager().addPlayer(uuid);
}
protected void removePlayer(UUID 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());
}
case "redisbungee-player-leave" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("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) {
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "proxy");
}
// must check if player is on the local 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) {
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "server");
}
// 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());
}
protected String getLastServerFromRedis(UUID uuid) {
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-server");
}
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 InetAddress getIpAddressFromRedis(UUID uuid) {
String ip = unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "ip");
if (ip == null) return null;
return InetAddresses.forString(ip);
}
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));
this.plugin.proxyDataManager().addPlayer(uuid);
}
protected long getLastOnlineFromRedis(UUID uuid) {
String unixString = unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-online");
if (unixString == null) return -1;
return Long.parseLong(unixString);
}
protected void removePlayer(UUID 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));
this.plugin.proxyDataManager().removePlayer(uuid);
}
public String getLastServerFor(UUID uuid) {
return this.lastServerCache.get(uuid);
}
protected String getProxyFromRedis(UUID uuid) {
return unifiedJedis.hget(
"redisbungee::" + this.networkId + "::player::" + uuid + "::data", "proxy");
}
public String getServerFor(UUID uuid) {
return this.serverCache.get(uuid);
}
protected String getServerFromRedis(UUID uuid) {
return unifiedJedis.hget(
"redisbungee::" + this.networkId + "::player::" + uuid + "::data", "server");
}
public String getProxyFor(UUID uuid) {
return this.proxyCache.get(uuid);
}
protected String getLastServerFromRedis(UUID uuid) {
return unifiedJedis.hget(
"redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-server");
}
public InetAddress getIpFor(UUID uuid) {
return this.ipCache.get(uuid);
}
protected InetAddress getIpAddressFromRedis(UUID 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) {
return this.lastOnlineCache.get(uuid);
}
protected long getLastOnlineFromRedis(UUID 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() {
return this.serverToPlayersCache.get(SERVERS_TO_PLAYERS_KEY);
}
public String getLastServerFor(UUID uuid) {
return this.lastServerCache.get(uuid);
}
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();
public String getServerFor(UUID uuid) {
return this.serverCache.get(uuid);
}
@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);
});
}
pipeline.sync();
responses.forEach((uuid, response) -> {
String key = response.get();
if (key == null) return;
public String getProxyFor(UUID uuid) {
return this.proxyCache.get(uuid);
}
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
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);
builder.put(key, uuid);
});
return builder.build();
}
}
@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
*
* 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
*/
* 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;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
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.tasks.RedisPipelineTask;
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.params.XAddParams;
import redis.clients.jedis.params.XReadParams;
import redis.clients.jedis.resps.StreamEntry;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.base.Preconditions.checkArgument;
public abstract class ProxyDataManager implements Runnable {
private static final int MAX_ENTRIES = 10000;
private 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:
// Proxy id, heartbeat (unix epoch from instant), players as int
private final ConcurrentHashMap<String, HeartbeatPayload.HeartbeatData> heartbeats = new ConcurrentHashMap<>();
// data:
// Proxy id, heartbeat (unix epoch from instant), players as int
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
private final UUID dataManagerUUID = UUID.randomUUID();
// This different from proxy id, just to detect if there is duplicate proxy using same proxy id
private final UUID dataManagerUUID = UUID.randomUUID();
protected final RedisBungeePlugin<?> plugin;
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) {
this.plugin = plugin;
this.proxyId = this.plugin.configuration().getProxyId();
this.unifiedJedis = plugin.getSummoner().obtainResource();
this.networkId = plugin.configuration().networkId();
this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream";
this.destroyProxyMembers();
public ProxyDataManager(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
this.proxyId = this.plugin.configuration().getProxyId();
this.unifiedJedis = plugin.getSummoner().obtainResource();
this.networkId = plugin.configuration().networkId();
this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream";
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) {
checkArgument(proxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID");
if (proxyId.equals(this.proxyId)) return this.getLocalOnlineUUIDs();
if (!this.heartbeats.containsKey(proxyId)) {
return new HashSet<>(); // return empty hashset or null?
}
return getProxyMembers(proxyId);
public List<String> proxiesIds() {
return Collections.list(this.heartbeats.keys());
}
public synchronized void sendCommandTo(String proxyToRun, String command) {
if (isClosed()) return;
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
// due proxy shutdown shenanigans
public boolean isPlayerTrulyOnProxy(String proxyId, UUID uuid) {
return unifiedJedis.sismember("redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players", uuid.toString());
}
public synchronized void sendChannelMessage(String channel, String message) {
if (isClosed()) return;
publishPayload(new PubSubPayload(this.proxyId, channel, message));
}
// call every 1 second
public synchronized void publishHeartbeat() {
if (isClosed()) return;
HeartbeatPayload.HeartbeatData heartbeatData =
new HeartbeatPayload.HeartbeatData(
Instant.now().getEpochSecond(), this.getLocalOnlineUUIDs().size());
this.heartbeats.put(this.proxyId(), heartbeatData);
publishPayload(new HeartbeatPayload(this.proxyId, heartbeatData));
}
public List<String> proxiesIds() {
return Collections.list(this.heartbeats.keys());
}
public synchronized void sendCommandTo(String proxyToRun, String command) {
if (isClosed()) return;
if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) {
handlePlatformCommandExecution(command);
}
publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command));
}
public synchronized void sendChannelMessage(String channel, String message) {
if (isClosed()) return;
publishPayload(new PubSubPayload(this.proxyId, channel, message));
}
// call every 1 second
public synchronized void publishHeartbeat() {
if (isClosed()) return;
HeartbeatPayload.HeartbeatData heartbeatData = new HeartbeatPayload.HeartbeatData(Instant.now().getEpochSecond(), this.getLocalOnlineUUIDs().size());
this.heartbeats.put(this.proxyId(), heartbeatData);
publishPayload(new HeartbeatPayload(this.proxyId, heartbeatData));
}
public Set<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);
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);
}
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) {
plugin.logWarn("found {} that does not belong to this proxy removing it from the corrected set", uuid);
removeString.add(uuid.toString());
}
try {
new RedisPipelineTask<Void>(plugin) {
@Override
public Void doPooledPipeline(Pipeline pipeline) {
Set<String> removeString = new HashSet<>();
for (UUID uuid : remove) {
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;
}
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;
}
@Override
public Void clusterPipeline(ClusterPipeline pipeline) {
Set<String> removeString = new HashSet<>();
for (UUID uuid : remove) {
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);
@Override
public Void clusterPipeline(ClusterPipeline pipeline) {
Set<String> removeString = new HashSet<>();
for (UUID uuid : remove) {
removeString.add(uuid.toString());
}
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;
}
// 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);
}
@Override
public Void clusterPipeline(ClusterPipeline pipeline) {
for (String deadProxy : deadProxies) {
pipeline.del(
"redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players");
}
pipeline.sync();
return null;
}
}.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void handleProxyDeath(DeathPayload payload) {
cleanProxy(payload.senderProxy());
}
private void cleanProxy(String id) {
if (id.equals(this.proxyId())) {
return;
}
for (UUID uuid : getProxyMembers(id))
plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
this.heartbeats.remove(id);
plugin.logInfo("Proxy {} has disconnected", id);
}
private void handleChannelMessage(PubSubPayload payload) {
String channel = payload.channel();
String message = payload.message();
this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message));
}
protected abstract void handlePlatformCommandExecution(String command);
private void handleCommand(RunCommandPayload payload) {
String proxyToRun = payload.proxyToRun();
String command = payload.command();
if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) {
handlePlatformCommandExecution(command);
}
}
public void addPlayer(UUID uuid) {
this.unifiedJedis.sadd(
"redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players",
uuid.toString());
}
public void removePlayer(UUID uuid) {
this.unifiedJedis.srem(
"redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players",
uuid.toString());
}
private void destroyProxyMembers() {
unifiedJedis.del(
"redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players");
}
private Set<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 {
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
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);
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
}
}
}
private void handleProxyDeath(DeathPayload payload) {
cleanProxy(payload.senderProxy());
}
public void close() {
closed.set(true);
this.publishDeath();
this.heartbeats.clear();
this.destroyProxyMembers();
}
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);
}
public boolean isClosed() {
return closed.get();
}
private void handleChannelMessage(PubSubPayload payload) {
String channel = payload.channel();
String message = payload.message();
this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message));
}
public String proxyId() {
return proxyId;
}
protected abstract void handlePlatformCommandExecution(String command);
public UnifiedJedis unifiedJedis() {
return unifiedJedis;
}
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 {
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;
}
public String networkId() {
return networkId;
}
}

View File

@ -1,15 +1,15 @@
/*
* 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
*/
* 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;
public enum RedisBungeeMode {
SINGLE, CLUSTER
SINGLE,
CLUSTER
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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;
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.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import java.net.InetAddress;
import java.util.UUID;
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
* <p>
* Reason this is interface because some proxies implementations require the user to extend class for plugins for example bungeecord.
* 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
*
* <p>Reason this is interface because some proxies implementations require the user to extend class
* for plugins for example bungeecord.
*
* @author Ham1255
* @since 0.7.0
*/
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);
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
String platformId();
String platformId();
}

View File

@ -1,17 +1,16 @@
/*
* 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
*/
* 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.config;
public enum HandleMotdOrder {
FIRST,
NORMAL,
LAST
FIRST,
NORMAL,
LAST
}

View File

@ -1,93 +1,109 @@
/*
* 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
*/
* 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.config;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.util.List;
public class RedisBungeeConfiguration {
private final String proxyId;
private final List<InetAddress> exemptAddresses;
private final boolean kickWhenOnline;
private final String proxyId;
private final List<InetAddress> exemptAddresses;
private final boolean kickWhenOnline;
private final boolean handleReconnectToLastServer;
private final boolean handleMotd;
private final HandleMotdOrder handleMotdOrder;
private final boolean handleReconnectToLastServer;
private final boolean handleMotd;
private final HandleMotdOrder handleMotdOrder;
private final CommandsConfiguration commandsConfiguration;
private final String networkId;
private final CommandsConfiguration commandsConfiguration;
private final String networkId;
public RedisBungeeConfiguration(String networkId, String proxyId, List<String> exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, HandleMotdOrder handleMotdOrder, CommandsConfiguration commandsConfiguration) {
this.proxyId = proxyId;
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
for (String s : exemptAddresses) {
addressBuilder.add(InetAddresses.forString(s));
}
this.exemptAddresses = addressBuilder.build();
this.kickWhenOnline = kickWhenOnline;
this.handleReconnectToLastServer = handleReconnectToLastServer;
this.handleMotd = handleMotd;
this.handleMotdOrder = handleMotdOrder;
this.commandsConfiguration = commandsConfiguration;
this.networkId = networkId;
public RedisBungeeConfiguration(
String networkId,
String proxyId,
List<String> exemptAddresses,
boolean kickWhenOnline,
boolean handleReconnectToLastServer,
boolean handleMotd,
HandleMotdOrder handleMotdOrder,
CommandsConfiguration commandsConfiguration) {
this.proxyId = proxyId;
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
for (String s : exemptAddresses) {
addressBuilder.add(InetAddresses.forString(s));
}
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() {
return proxyId;
}
public String getProxyId() {
return proxyId;
}
public List<InetAddress> getExemptAddresses() {
return exemptAddresses;
}
public List<InetAddress> getExemptAddresses() {
return exemptAddresses;
}
public boolean kickWhenOnline() {
return kickWhenOnline;
}
public boolean kickWhenOnline() {
return kickWhenOnline;
}
public boolean handleMotd() {
return this.handleMotd;
}
public boolean handleMotd() {
return this.handleMotd;
}
public HandleMotdOrder handleMotdOrder() {
return handleMotdOrder;
}
public HandleMotdOrder handleMotdOrder() {
return handleMotdOrder;
}
public boolean handleReconnectToLastServer() {
return this.handleReconnectToLastServer;
}
public boolean handleReconnectToLastServer() {
return this.handleReconnectToLastServer;
}
public record CommandsConfiguration(boolean redisbungeeEnabled, boolean redisbungeeLegacyEnabled,
LegacySubCommandsConfiguration legacySubCommandsConfiguration) {
public record CommandsConfiguration(
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,
boolean lastseenEnabled, boolean plistEnabled, boolean pproxyEnabled,
boolean sendtoallEnabled, boolean serveridEnabled,
boolean serveridsEnabled, boolean installFind, boolean installGlist, boolean installIp,
boolean installLastseen, boolean installPlist, boolean installPproxy,
boolean installSendtoall, boolean installServerid,
boolean installServerids) {
}
public CommandsConfiguration commandsConfiguration() {
return commandsConfiguration;
}
public CommandsConfiguration commandsConfiguration() {
return commandsConfiguration;
}
public String networkId() {
return networkId;
}
public String networkId() {
return networkId;
}
}

View File

@ -1,16 +1,14 @@
/*
* 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
*/
* 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.config.loaders;
import com.google.common.reflect.TypeToken;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
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.JedisPooledSummoner;
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.objectmapping.ObjectMappingException;
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.PooledConnectionProvider;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
public interface ConfigLoader extends GenericConfigLoader {
int CONFIG_VERSION = 2;
int CONFIG_VERSION = 2;
default void loadConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml");
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
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);
String redisPassword = node.getNode("redis-password").getString("");
String redisUsername = node.getNode("redis-username").getString("");
String networkId = node.getNode("network-id").getString("main");
String proxyId = node.getNode("proxy-id").getString("proxy-1");
default void loadConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml");
final YAMLConfigurationLoader yamlConfigurationFileLoader =
YAMLConfigurationLoader.builder().setPath(configFile).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
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);
String redisPassword = node.getNode("redis-password").getString("");
String redisUsername = node.getNode("redis-username").getString("");
String networkId = node.getNode("network-id").getString("main");
String proxyId = node.getNode("proxy-id").getString("proxy-1");
final int maxConnections = node.getNode("max-redis-connections").getInt(10);
List<String> exemptAddresses;
try {
exemptAddresses = node.getNode("exempt-ip-addresses").getList(TypeToken.of(String.class));
} catch (ObjectMappingException e) {
exemptAddresses = Collections.emptyList();
}
final int maxConnections = node.getNode("max-redis-connections").getInt(10);
List<String> exemptAddresses;
try {
exemptAddresses = node.getNode("exempt-ip-addresses").getList(TypeToken.of(String.class));
} catch (ObjectMappingException e) {
exemptAddresses = Collections.emptyList();
}
// check redis password
if ((redisPassword.isEmpty() || redisPassword.equals("none"))) {
redisPassword = null;
plugin.logWarn("password is empty");
}
if ((redisUsername.isEmpty() || redisUsername.equals("none"))) {
redisUsername = null;
}
// env var
String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID");
if (proxyIdFromEnv != null) {
plugin.logInfo("Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID", proxyId, proxyIdFromEnv);
proxyId = proxyIdFromEnv;
}
// check redis password
if ((redisPassword.isEmpty() || redisPassword.equals("none"))) {
redisPassword = null;
plugin.logWarn("password is empty");
}
if ((redisUsername.isEmpty() || redisUsername.equals("none"))) {
redisUsername = null;
}
// env var
String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID");
if (proxyIdFromEnv != null) {
plugin.logInfo(
"Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID",
proxyId,
proxyIdFromEnv);
proxyId = proxyIdFromEnv;
}
String networkIdFromEnv = System.getenv("REDISBUNGEE_NETWORK_ID");
if (networkIdFromEnv != null) {
plugin.logInfo("Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID", networkId, networkIdFromEnv);
networkId = networkIdFromEnv;
}
String networkIdFromEnv = System.getenv("REDISBUNGEE_NETWORK_ID");
if (networkIdFromEnv != null) {
plugin.logInfo(
"Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID",
networkId,
networkIdFromEnv);
networkId = networkIdFromEnv;
}
// Configuration sanity checks.
if (proxyId == null || proxyId.isEmpty()) {
String genId = UUID.randomUUID().toString();
plugin.logInfo("Generated proxy id " + genId + " and saving it to config.");
node.getNode("proxy-id").setValue(genId);
yamlConfigurationFileLoader.save(node);
proxyId = genId;
plugin.logInfo("proxy id was generated: " + proxyId);
} else {
plugin.logInfo("Loaded proxy id " + proxyId);
}
// Configuration sanity checks.
if (proxyId == null || proxyId.isEmpty()) {
String genId = UUID.randomUUID().toString();
plugin.logInfo("Generated proxy id " + genId + " and saving it to config.");
node.getNode("proxy-id").setValue(genId);
yamlConfigurationFileLoader.save(node);
proxyId = genId;
plugin.logInfo("proxy id was generated: " + proxyId);
} else {
plugin.logInfo("Loaded proxy id " + proxyId);
}
if (networkId.isEmpty()) {
networkId = "main";
plugin.logWarn("network id was empty and replaced with 'main'");
}
if (networkId.isEmpty()) {
networkId = "main";
plugin.logWarn("network id was empty and replaced with 'main'");
}
plugin.logInfo("Loaded network id " + networkId);
// TO avoid proxies from different platforms from seeing each other.
networkId = plugin.platformId() + "-" + networkId;
plugin.logInfo("Platform is {} so network id is now is {}", plugin.platformId(), networkId);
plugin.logInfo("Loaded network id " + networkId);
// TO avoid proxies from different platforms from seeing each other.
networkId = 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 handleMotd = node.getNode("handle-motd").getBoolean(true);
plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer);
plugin.logInfo("handle motd: {}", handleMotd);
boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean();
boolean handleMotd = node.getNode("handle-motd").getBoolean(true);
plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer);
plugin.logInfo("handle motd: {}", handleMotd);
HandleMotdOrder handleMotdOrder = HandleMotdOrder.NORMAL;
String handleMotdOrderName = node.getNode("handle-motd-priority").getString();
if (handleMotdOrderName != null) {
try {
handleMotdOrder = HandleMotdOrder.valueOf(handleMotdOrderName.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
plugin.logWarn("handle motd order value '{}' is unsupported (allowed: {})", handleMotdOrderName, HandleMotdOrder.values());
}
}
plugin.logInfo("handle motd order: {}", handleMotdOrder);
HandleMotdOrder handleMotdOrder = HandleMotdOrder.NORMAL;
String handleMotdOrderName = node.getNode("handle-motd-priority").getString();
if (handleMotdOrderName != null) {
try {
handleMotdOrder = HandleMotdOrder.valueOf(handleMotdOrderName.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
plugin.logWarn(
"handle motd order value '{}' is unsupported (allowed: {})",
handleMotdOrderName,
HandleMotdOrder.values());
}
}
plugin.logInfo("handle motd order: {}", handleMotdOrder);
// commands
boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(true);
boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(false);
// commands
boolean redisBungeeEnabled =
node.getNode("commands", "redisbungee", "enabled").getBoolean(true);
boolean redisBungeeLegacyEnabled =
node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(false);
boolean glistEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled").getBoolean(false);
boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(false);
boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(false);
boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(false);
boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(false);
boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(false);
boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(false);
boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(false);
boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(false);
boolean glistEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled")
.getBoolean(false);
boolean findEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled")
.getBoolean(false);
boolean lastseenEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled")
.getBoolean(false);
boolean ipEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled")
.getBoolean(false);
boolean pproxyEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled")
.getBoolean(false);
boolean sendToAllEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled")
.getBoolean(false);
boolean serverIdEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled")
.getBoolean(false);
boolean serverIdsEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled")
.getBoolean(false);
boolean pListEnabled =
node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled")
.getBoolean(false);
boolean installGlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install").getBoolean(false);
boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(false);
boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(false);
boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(false);
boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(false);
boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(false);
boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(false);
boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(false);
boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(false);
boolean installGlist =
node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install")
.getBoolean(false);
boolean installFind =
node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install")
.getBoolean(false);
boolean installLastseen =
node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install")
.getBoolean(false);
boolean installIp =
node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install")
.getBoolean(false);
boolean installPproxy =
node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install")
.getBoolean(false);
boolean installSendToAll =
node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install")
.getBoolean(false);
boolean installServerid =
node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install")
.getBoolean(false);
boolean installServerIds =
node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install")
.getBoolean(false);
boolean installPlist =
node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install")
.getBoolean(false);
RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(networkId, proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, handleMotdOrder,
RedisBungeeConfiguration configuration =
new RedisBungeeConfiguration(
networkId,
proxyId,
exemptAddresses,
kickWhenOnline,
reconnectToLastServer,
handleMotd,
handleMotdOrder,
new RedisBungeeConfiguration.CommandsConfiguration(
redisBungeeEnabled, redisBungeeLegacyEnabled,
redisBungeeEnabled,
redisBungeeLegacyEnabled,
new RedisBungeeConfiguration.LegacySubCommandsConfiguration(
findEnabled, glistEnabled, ipEnabled,
lastseenEnabled, pListEnabled, pproxyEnabled,
sendToAllEnabled, serverIdEnabled, serverIdsEnabled,
installFind, installGlist, installIp,
installLastseen, installPlist, installPproxy,
installSendToAll, installServerid, installServerIds)
));
Summoner<?> summoner;
RedisBungeeMode redisBungeeMode;
if (useSSL) {
plugin.logInfo("Using ssl");
}
if (node.getNode("cluster-mode-enabled").getBoolean(false)) {
plugin.logInfo("RedisBungee MODE: CLUSTER");
Set<HostAndPort> hostAndPortSet = new HashSet<>();
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(maxConnections);
poolConfig.setBlockWhenExhausted(true);
node.getNode("redis-cluster-servers").getChildrenList().forEach((childNode) -> {
findEnabled,
glistEnabled,
ipEnabled,
lastseenEnabled,
pListEnabled,
pproxyEnabled,
sendToAllEnabled,
serverIdEnabled,
serverIdsEnabled,
installFind,
installGlist,
installIp,
installLastseen,
installPlist,
installPproxy,
installSendToAll,
installServerid,
installServerIds)));
Summoner<?> summoner;
RedisBungeeMode redisBungeeMode;
if (useSSL) {
plugin.logInfo("Using ssl");
}
if (node.getNode("cluster-mode-enabled").getBoolean(false)) {
plugin.logInfo("RedisBungee MODE: CLUSTER");
Set<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();
String host = hostAndPort.get("host").getString();
int port = hostAndPort.get("port").getInt();
hostAndPortSet.add(new HostAndPort(host, port));
});
plugin.logInfo(hostAndPortSet.size() + " cluster nodes were specified");
if (hostAndPortSet.isEmpty()) {
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));
redisBungeeMode = RedisBungeeMode.CLUSTER;
} else {
plugin.logInfo("RedisBungee MODE: SINGLE");
final String redisServer = node.getNode("redis-server").getString("127.0.0.1");
final int redisPort = node.getNode("redis-port").getInt(6379);
if (redisServer != null && redisServer.isEmpty()) {
throw new RuntimeException("No redis server specified");
}
JedisPool jedisPool = null;
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3));
config.setBlockWhenExhausted(true);
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);
});
plugin.logInfo(hostAndPortSet.size() + " cluster nodes were specified");
if (hostAndPortSet.isEmpty()) {
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));
redisBungeeMode = RedisBungeeMode.CLUSTER;
} else {
plugin.logInfo("RedisBungee MODE: SINGLE");
final String redisServer = node.getNode("redis-server").getString("127.0.0.1");
final int redisPort = node.getNode("redis-port").getInt(6379);
if (redisServer != null && redisServer.isEmpty()) {
throw new RuntimeException("No redis server specified");
}
JedisPool jedisPool = null;
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3));
config.setBlockWhenExhausted(true);
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
*
* 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
*/
* 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.config.loaders;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import org.jetbrains.annotations.Nullable;
public interface GenericConfigLoader {
// CHANGES on every reboot
String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond();
// CHANGES on every reboot
String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond();
default Path createConfigFile(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
if (Files.notExists(dataFolder)) {
Files.createDirectory(dataFolder);
}
Path file = dataFolder.resolve(configFile);
if (Files.notExists(file) && defaultResourceID != null) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream(defaultResourceID)) {
Files.createFile(file);
assert in != null;
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
}
return file;
default Path createConfigFile(
Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
if (Files.notExists(dataFolder)) {
Files.createDirectory(dataFolder);
}
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);
Path file = dataFolder.resolve(configFile);
if (Files.notExists(file) && defaultResourceID != null) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream(defaultResourceID)) {
Files.createFile(file);
assert in != null;
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
}
return file;
}
default void handleOldConfig(
Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
Path oldConfigFolder = dataFolder.resolve("old_config");
if (Files.notExists(oldConfigFolder)) {
Files.createDirectory(oldConfigFolder);
}
Path randomStoreConfigDirectory = oldConfigFolder.resolve(RANDOM_OLD);
if (Files.notExists(randomStoreConfigDirectory)) {
Files.createDirectory(randomStoreConfigDirectory);
}
Path oldConfigPath = dataFolder.resolve(configFile);
Files.move(oldConfigPath, randomStoreConfigDirectory.resolve(configFile));
createConfigFile(dataFolder, configFile, defaultResourceID);
}
}

View File

@ -1,33 +1,33 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
* 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.events;
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
* @since 0.7.0
*/
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);
void fireEvent(Object event);
IPubSubMessageEvent createPubSubEvent(String channel, String message);
void fireEvent(Object event);
}

View File

@ -1,23 +1,21 @@
/*
* 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
*/
* 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.events;
import java.util.UUID;
public interface IPlayerChangedServerNetworkEvent extends RedisBungeeEvent {
UUID getUuid();
UUID getUuid();
String getServer();
String getPreviousServer();
String getServer();
String getPreviousServer();
}

View File

@ -1,19 +1,17 @@
/*
* 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
*/
* 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.events;
import java.util.UUID;
public interface IPlayerJoinedNetworkEvent extends RedisBungeeEvent {
UUID getUuid();
UUID getUuid();
}

View File

@ -1,19 +1,17 @@
/*
* 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
*/
* 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.events;
import java.util.UUID;
public interface IPlayerLeftNetworkEvent extends RedisBungeeEvent {
UUID getUuid();
UUID getUuid();
}

View File

@ -1,20 +1,17 @@
/*
* 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
*/
* 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.events;
public interface IPubSubMessageEvent extends RedisBungeeEvent {
String getChannel();
String getMessage();
String getChannel();
String getMessage();
}

View File

@ -1,14 +1,12 @@
/*
* 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
*/
* 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.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;
public abstract class AbstractPayload {
private final String senderProxy;
private final String senderProxy;
public AbstractPayload(String proxyId) {
this.senderProxy = proxyId;
}
public AbstractPayload(String proxyId) {
this.senderProxy = proxyId;
}
public AbstractPayload(String senderProxy, String className) {
this.senderProxy = senderProxy;
}
public AbstractPayload(String senderProxy, String className) {
this.senderProxy = senderProxy;
}
public String senderProxy() {
return senderProxy;
}
public String getClassName() {
return getClass().getName();
}
public String senderProxy() {
return senderProxy;
}
public String getClassName() {
return getClass().getName();
}
}

View File

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

View File

@ -1,31 +1,28 @@
/*
* 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
*/
* 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.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.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) {
super(proxyId);
this.data = data;
}
public HeartbeatData data() {
return data;
}
public HeartbeatData data() {
return data;
}
}

View File

@ -1,34 +1,32 @@
/*
* 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
*/
* 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.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class PubSubPayload extends AbstractPayload {
private final String channel;
private final String message;
private final String channel;
private final String message;
public PubSubPayload(String proxyId, String channel, String message) {
super(proxyId);
this.channel = channel;
this.message = message;
}
public PubSubPayload(String proxyId, String channel, String message) {
super(proxyId);
this.channel = channel;
this.message = message;
}
public String channel() {
return channel;
}
public String channel() {
return channel;
}
public String message() {
return message;
}
public String message() {
return message;
}
}

View File

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

View File

@ -1,36 +1,36 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
* 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.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload;
import java.lang.reflect.Type;
public class DeathPayloadSerializer implements JsonSerializer<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
public DeathPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
return new DeathPayload(senderProxy);
}
@Override
public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
return jsonObject;
}
@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
*
* 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
*/
* 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.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload;
import java.lang.reflect.Type;
public class HeartbeatPayloadSerializer implements JsonSerializer<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
public HeartbeatPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
long heartbeat = jsonObject.get("heartbeat").getAsLong();
int players = jsonObject.get("players").getAsInt();
return new HeartbeatPayload(senderProxy, new HeartbeatPayload.HeartbeatData(heartbeat, players));
}
@Override
public JsonElement serialize(HeartbeatPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
jsonObject.add("heartbeat", new JsonPrimitive(src.data().heartbeat()));
jsonObject.add("players", new JsonPrimitive(src.data().players()));
return jsonObject;
}
@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
*
* 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
*/
* 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.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload;
import java.lang.reflect.Type;
public class PubSubPayloadSerializer implements JsonSerializer<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
public PubSubPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
String channel = jsonObject.get("channel").getAsString();
String message = jsonObject.get("message").getAsString();
return new PubSubPayload(senderProxy, channel, message);
}
@Override
public JsonElement serialize(PubSubPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
jsonObject.add("channel", new JsonPrimitive(src.channel()));
jsonObject.add("message", context.serialize(src.message()));
return jsonObject;
}
@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
*
* 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
*/
* 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.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload;
import java.lang.reflect.Type;
public class RunCommandPayloadSerializer implements JsonSerializer<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
public RunCommandPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
String proxyToRun = jsonObject.get("proxy-to-run").getAsString();
String command = jsonObject.get("command").getAsString();
return new RunCommandPayload(senderProxy, proxyToRun, command);
}
@Override
public JsonElement serialize(RunCommandPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
jsonObject.add("proxy-to-run", new JsonPrimitive(src.proxyToRun()));
jsonObject.add("command", context.serialize(src.command()));
return jsonObject;
}
@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
*
* 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
*/
* 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.summoners;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import java.io.IOException;
import java.time.Duration;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.providers.ClusterConnectionProvider;
public class JedisClusterSummoner implements Summoner<JedisCluster> {
private final ClusterConnectionProvider clusterConnectionProvider;
private final ClusterConnectionProvider clusterConnectionProvider;
public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) {
this.clusterConnectionProvider = clusterConnectionProvider;
// test the connection
JedisCluster jedisCluster = obtainResource();
jedisCluster.set("random_data", "0");
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));
}
public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) {
this.clusterConnectionProvider = clusterConnectionProvider;
// test the connection
JedisCluster jedisCluster = obtainResource();
jedisCluster.set("random_data", "0");
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));
}
}

View File

@ -1,60 +1,55 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
* 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.summoners;
import java.io.IOException;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.PooledConnectionProvider;
import java.io.IOException;
public class JedisPooledSummoner implements Summoner<JedisPooled> {
private final PooledConnectionProvider connectionProvider;
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");
private final PooledConnectionProvider connectionProvider;
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");
}
@Override
public JedisPooled obtainResource() {
// create UnClosable JedisPool *disposable*
return new NotClosableJedisPooled(this.connectionProvider);
}
public JedisPool getCompatibilityJedisPool() {
return this.jedisPool;
}
@Override
public void close() throws IOException {
if (this.jedisPool != null) {
this.jedisPool.close();
}
this.connectionProvider.close();
@Override
public JedisPooled obtainResource() {
// create UnClosable JedisPool *disposable*
return new NotClosableJedisPooled(this.connectionProvider);
}
public JedisPool getCompatibilityJedisPool() {
return this.jedisPool;
}
@Override
public void close() throws IOException {
if (this.jedisPool != null) {
this.jedisPool.close();
}
this.connectionProvider.close();
}
}

View File

@ -1,29 +1,25 @@
/*
* 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
*/
* 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.summoners;
import java.time.Duration;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import java.time.Duration;
public class NotClosableJedisCluster extends JedisCluster {
NotClosableJedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) {
super(provider, maxAttempts, maxTotalRetriesDuration);
}
NotClosableJedisCluster(
ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) {
super(provider, maxAttempts, maxTotalRetriesDuration);
}
@Override
public void close() {
}
@Override
public void close() {}
}

View File

@ -1,26 +1,22 @@
/*
* 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
*/
* 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.summoners;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.PooledConnectionProvider;
public class NotClosableJedisPooled extends JedisPooled {
NotClosableJedisPooled(PooledConnectionProvider provider) {
super(provider);
}
NotClosableJedisPooled(PooledConnectionProvider provider) {
super(provider);
}
@Override
public void close() {
}
@Override
public void close() {}
}

View File

@ -1,19 +1,16 @@
/*
* 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
*/
* 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.summoners;
import redis.clients.jedis.UnifiedJedis;
import java.io.Closeable;
import redis.clients.jedis.UnifiedJedis;
/**
* 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 {
P obtainResource();
P obtainResource();
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.tasks;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
@ -16,34 +15,30 @@ import redis.clients.jedis.*;
public abstract class RedisPipelineTask<T> extends RedisTask<T> {
public RedisPipelineTask(AbstractRedisBungeeAPI api) {
super(api);
}
public RedisPipelineTask(AbstractRedisBungeeAPI api) {
super(api);
public RedisPipelineTask(RedisBungeePlugin<?> plugin) {
super(plugin);
}
@Override
public T unifiedJedisTask(UnifiedJedis unifiedJedis) {
if (unifiedJedis instanceof JedisPooled pooled) {
try (Pipeline pipeline = pooled.pipelined()) {
return doPooledPipeline(pipeline);
}
} else if (unifiedJedis instanceof JedisCluster jedisCluster) {
try (ClusterPipeline pipeline = jedisCluster.pipelined()) {
return clusterPipeline(pipeline);
}
}
public RedisPipelineTask(RedisBungeePlugin<?> plugin) {
super(plugin);
}
@Override
public T unifiedJedisTask(UnifiedJedis unifiedJedis) {
if (unifiedJedis instanceof JedisPooled pooled) {
try (Pipeline pipeline = pooled.pipelined()) {
return doPooledPipeline(pipeline);
}
} else if (unifiedJedis instanceof JedisCluster jedisCluster) {
try (ClusterPipeline pipeline = jedisCluster.pipelined()) {
return clusterPipeline(pipeline);
}
}
return null;
}
public abstract T doPooledPipeline(Pipeline pipeline);
public abstract T clusterPipeline(ClusterPipeline pipeline);
return null;
}
public abstract T doPooledPipeline(Pipeline pipeline);
public abstract T clusterPipeline(ClusterPipeline pipeline);
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.tasks;
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.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import java.util.concurrent.Callable;
import redis.clients.jedis.UnifiedJedis;
import java.util.concurrent.Callable;
/**
* Since Jedis now have UnifiedJedis which basically extended by cluster / single connections classes
* can help us to have shared code.
* Since Jedis now have UnifiedJedis which basically extended by cluster / single connections
* classes can help us to have shared code.
*/
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
public V call() throws Exception {
return this.execute();
@Override
public V call() throws Exception {
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());
}
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;
}
return null;
}
}

View File

@ -1,56 +1,54 @@
/*
* 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
*/
* 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.tasks;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.CachedUUIDEntry;
import java.util.ArrayList;
import redis.clients.jedis.UnifiedJedis;
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();
private final RedisBungeePlugin<?> plugin;
public UUIDCleanupTask(RedisBungeePlugin<?> plugin) {
super(plugin);
this.plugin = plugin;
}
// this code is inspired from https://github.com/minecrafter/redisbungeeclean
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
final long number = unifiedJedis.hlen("uuid-cache");
plugin.logInfo("Found {} entries", number);
ArrayList<String> fieldsToRemove = new ArrayList<>();
unifiedJedis.hgetAll("uuid-cache").forEach((field, data) -> {
// this code is inspired from https://github.com/minecrafter/redisbungeeclean
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
final long number = unifiedJedis.hlen("uuid-cache");
plugin.logInfo("Found {} entries", number);
ArrayList<String> fieldsToRemove = new ArrayList<>();
unifiedJedis
.hgetAll("uuid-cache")
.forEach(
(field, data) -> {
CachedUUIDEntry cachedUUIDEntry = gson.fromJson(data, CachedUUIDEntry.class);
if (cachedUUIDEntry.expired()) {
fieldsToRemove.add(field);
fieldsToRemove.add(field);
}
});
if (!fieldsToRemove.isEmpty()) {
unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0]));
}
plugin.logInfo("deleted {} entries", fieldsToRemove.size());
} catch (JedisException e) {
plugin.logFatal("There was an error fetching information", e);
}
return null;
});
if (!fieldsToRemove.isEmpty()) {
unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0]));
}
plugin.logInfo("deleted {} entries", fieldsToRemove.size());
} catch (JedisException e) {
plugin.logFatal("There was an error fetching information", e);
}
return null;
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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;
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.UnifiedJedis;
public class InitialUtils {
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
// This is more portable than INFO <section>
String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO));
for (String s : info.split("\r\n")) {
if (s.startsWith("redis_version:")) {
String version = s.split(":")[1];
plugin.logInfo("Redis server version: " + version);
if (!RedisUtil.isRedisVersionRight(version)) {
plugin.logFatal("Your version of Redis (" + version + ") is not at least version " + RedisUtil.MAJOR_VERSION + "." + RedisUtil.MINOR_VERSION + " RedisBungee requires a newer version of Redis.");
throw new RuntimeException("Unsupported Redis version detected");
}
long uuidCacheSize = unifiedJedis.hlen("uuid-cache");
if (uuidCacheSize > 750000) {
plugin.logInfo("Looks like you have a really big UUID cache! Run '/rb clean' to remove expired cache entries");
}
break;
}
}
return null;
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
// This is more portable than INFO <section>
String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO));
for (String s : info.split("\r\n")) {
if (s.startsWith("redis_version:")) {
String version = s.split(":")[1];
plugin.logInfo("Redis server version: " + version);
if (!RedisUtil.isRedisVersionRight(version)) {
plugin.logFatal(
"Your version of Redis ("
+ version
+ ") is not at least version "
+ RedisUtil.MAJOR_VERSION
+ "."
+ RedisUtil.MINOR_VERSION
+ " RedisBungee requires a newer version of Redis.");
throw new RuntimeException("Unsupported Redis version detected");
}
}.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;
import com.google.common.annotations.VisibleForTesting;
@VisibleForTesting
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 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 final int MAJOR_VERSION = 6;
public static final int MINOR_VERSION = 2;
public static boolean isRedisVersionRight(String redisVersion) {
String[] args = redisVersion.split("\\.");
if (args.length < 2) {
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*
@Deprecated
public static boolean canUseLua(String redisVersion) {
// Need to use >=3 to use Lua optimizations.
return isRedisVersionRight(redisVersion);
}
if (major > MAJOR_VERSION) return true;
return major == MAJOR_VERSION && minor >= MINOR_VERSION;
}
// 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;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.io.ByteArrayDataOutput;
import java.util.Collection;
import java.util.Map;
public class MultiMapSerialization {
public static void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
output.writeInt(collection.elementSet().size());
for (Multiset.Entry<String> entry : collection.entrySet()) {
output.writeUTF(entry.getElement());
output.writeInt(entry.getCount());
}
public static void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
output.writeInt(collection.elementSet().size());
for (Multiset.Entry<String> entry : collection.entrySet()) {
output.writeUTF(entry.getElement());
output.writeInt(entry.getCount());
}
}
@SuppressWarnings("SameParameterValue")
public static void serializeMultimap(Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) {
output.writeInt(collection.keySet().size());
for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) {
output.writeUTF(entry.getKey());
if (includeNames) {
serializeCollection(entry.getValue(), output);
} else {
output.writeInt(entry.getValue().size());
}
}
@SuppressWarnings("SameParameterValue")
public static void serializeMultimap(
Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) {
output.writeInt(collection.keySet().size());
for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) {
output.writeUTF(entry.getKey());
if (includeNames) {
serializeCollection(entry.getValue(), output);
} else {
output.writeInt(entry.getValue().size());
}
}
}
public static void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) {
output.writeInt(collection.size());
for (Object o : collection) {
output.writeUTF(o.toString());
}
public static void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) {
output.writeInt(collection.size());
for (Object o : collection) {
output.writeUTF(o.toString());
}
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.uuid;
import java.util.Calendar;
@ -15,7 +14,7 @@ import java.util.UUID;
public record CachedUUIDEntry(String name, UUID uuid, Calendar expiry) {
public boolean expired() {
return Calendar.getInstance().after(expiry);
}
public boolean expired() {
return Calendar.getInstance().after(expiry);
}
}

View File

@ -1,55 +1,54 @@
/*
* 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
*/
* 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.uuid;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
public class NameFetcher {
private static final OkHttpClient httpClient = new OkHttpClient();
private static final Gson gson = new Gson();
private static final OkHttpClient httpClient = new OkHttpClient();
private static final Gson gson = new Gson();
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
String name = getName(uuid);
if (name == null) return Collections.emptyList();
return Collections.singletonList(name);
}
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
String name = getName(uuid);
if (name == null) return Collections.emptyList();
return Collections.singletonList(name);
}
public static String getName(UUID uuid) throws IOException {
String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString();
Request request = new Request.Builder()
.addHeader("User-Agent", "RedisBungee-ProxioDev")
.url(url)
.get()
.build();
ResponseBody body = httpClient.newCall(request).execute().body();
String response = body.string();
body.close();
public static String getName(UUID uuid) throws IOException {
String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString();
Request request =
new Request.Builder()
.addHeader("User-Agent", "RedisBungee-ProxioDev")
.url(url)
.get()
.build();
ResponseBody body = httpClient.newCall(request).execute().body();
String response = body.string();
body.close();
JsonObject json = gson.fromJson(response, JsonObject.class);
if (!json.has("success") || !json.get("success").getAsBoolean()) return null;
if (!json.has("data")) return null;
JsonObject data = json.getAsJsonObject("data");
if (!data.has("player")) return null;
JsonObject player = data.getAsJsonObject("player");
if (!player.has("username")) return null;
JsonObject json = gson.fromJson(response, JsonObject.class);
if (!json.has("success") || !json.get("success").getAsBoolean()) return null;
if (!json.has("data")) return null;
JsonObject data = json.getAsJsonObject("data");
if (!data.has("player")) return null;
JsonObject player = data.getAsJsonObject("player");
if (!player.has("username")) return null;
return player.get("username").getAsString();
}
return player.get("username").getAsString();
}
}

View File

@ -1,71 +1,79 @@
/*
* 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
*/
* 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.uuid;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import okhttp3.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import okhttp3.*;
/* Credits to evilmidget38 for this class. I modified it to use Gson. */
public class UUIDFetcher implements Callable<Map<String, UUID>> {
private static final double PROFILES_PER_REQUEST = 100;
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
private static final MediaType JSON = MediaType.parse("application/json");
private final List<String> names;
private final boolean rateLimiting;
private static final Gson gson = new Gson();
private static final OkHttpClient httpClient = new OkHttpClient();
private static final double PROFILES_PER_REQUEST = 100;
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
private static final MediaType JSON = MediaType.parse("application/json");
private final List<String> names;
private final boolean rateLimiting;
private static final Gson gson = new Gson();
private static final OkHttpClient httpClient = new OkHttpClient();
private UUIDFetcher(List<String> names, boolean rateLimiting) {
this.names = ImmutableList.copyOf(names);
this.rateLimiting = rateLimiting;
}
private UUIDFetcher(List<String> names, boolean rateLimiting) {
this.names = ImmutableList.copyOf(names);
this.rateLimiting = rateLimiting;
}
public UUIDFetcher(List<String> names) {
this(names, true);
}
public UUIDFetcher(List<String> names) {
this(names, true);
}
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));
}
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));
}
public Map<String, UUID> call() throws Exception {
Map<String, UUID> uuidMap = new HashMap<>();
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
for (int i = 0; i < requests; i++) {
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();
ResponseBody responseBody = httpClient.newCall(request).execute().body();
String response = responseBody.string();
responseBody.close();
Profile[] array = gson.fromJson(response, Profile[].class);
for (Profile profile : array) {
UUID uuid = UUIDFetcher.getUUID(profile.id);
uuidMap.put(profile.name, uuid);
}
if (rateLimiting && i != requests - 1) {
Thread.sleep(100L);
}
}
return uuidMap;
public Map<String, UUID> call() throws Exception {
Map<String, UUID> uuidMap = new HashMap<>();
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
for (int i = 0; i < requests; i++) {
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();
ResponseBody responseBody = httpClient.newCall(request).execute().body();
String response = responseBody.string();
responseBody.close();
Profile[] array = gson.fromJson(response, Profile[].class);
for (Profile profile : array) {
UUID uuid = UUIDFetcher.getUUID(profile.id);
uuidMap.put(profile.name, uuid);
}
if (rateLimiting && i != requests - 1) {
Thread.sleep(100L);
}
}
return uuidMap;
}
private static class Profile {
String id;
String name;
}
private static class Profile {
String id;
String name;
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.uuid;
import com.google.common.base.Charsets;
@ -15,194 +14,188 @@ import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import org.checkerframework.checker.nullness.qual.NonNull;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisException;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
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 {
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 MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
private final RedisBungeePlugin<?> plugin;
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();
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 MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
private final RedisBungeePlugin<?> plugin;
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) {
this.plugin = plugin;
public UUIDTranslator(RedisBungeePlugin<?> 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) {
// 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);
// Check if we can exit early
if (UUID_PATTERN.matcher(player).find()) {
return UUID.fromString(player);
}
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);
if (MOJANGIAN_UUID_PATTERN.matcher(player).find()) {
// Reconstruct the UUID
return UUIDFetcher.getUUID(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);
}
// 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 if we can exit early
if (UUID_PATTERN.matcher(player).find()) {
return UUID.fromString(player);
}
if (MOJANGIAN_UUID_PATTERN.matcher(player).find()) {
// Reconstruct the UUID
return UUIDFetcher.getUUID(player);
}
// 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;
// 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();
}
}
};
// 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) {
// 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;
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();
}
}
};
// 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;
}
}
};
// 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) {
addToMaps(name, uuid);
String json = gson.toJson(uuidToNameMap.get(uuid));
unifiedJedis.hset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
return null; // Nope, game over!
}
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`
}
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies {
compileOnly(project(":RedisBungee-API"))
implementation(libs.acf.core)

View File

@ -1,50 +1,48 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
* 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;
import co.aikar.commands.CommandContexts;
import co.aikar.commands.CommandManager;
import co.aikar.commands.InvalidCommandArgument;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.legacy.LegacyRedisBungeeCommands;
import java.util.UUID;
public class CommandLoader {
public static void initCommands(CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) {
registerContexts(commandManager);
var commandsConfiguration = plugin.configuration().commandsConfiguration();
if (commandsConfiguration.redisbungeeEnabled()) {
commandManager.registerCommand(new CommandRedisBungee(plugin));
}
if (commandsConfiguration.redisbungeeLegacyEnabled()) {
commandManager.registerCommand(new LegacyRedisBungeeCommands(commandManager,plugin));
}
commandManager.registerCommand(new CommandRedisBungeeDebug(plugin));
public static void initCommands(
CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) {
registerContexts(commandManager);
var commandsConfiguration = plugin.configuration().commandsConfiguration();
if (commandsConfiguration.redisbungeeEnabled()) {
commandManager.registerCommand(new CommandRedisBungee(plugin));
}
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");
}
if (commandsConfiguration.redisbungeeLegacyEnabled()) {
commandManager.registerCommand(new LegacyRedisBungeeCommands(commandManager, plugin));
}
commandManager.registerCommand(new CommandRedisBungeeDebug(plugin));
}
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
*
* 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
*/
* 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;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.RegisteredCommand;
import co.aikar.commands.annotation.*;
import com.google.common.primitives.Ints;
import com.imaginarycode.minecraft.redisbungee.Constants;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
import com.imaginarycode.minecraft.redisbungee.commands.utils.StopperUUIDCleanupTask;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
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.tag.resolver.Placeholder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@CommandAlias("rb|redisbungee")
@CommandPermission("redisbungee.command.use")
@Description("Main command")
public class CommandRedisBungee extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin;
private final RedisBungeePlugin<?> plugin;
public CommandRedisBungee(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
public CommandRedisBungee(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
@Default
@Subcommand("info|version|git")
@Description("information about current redisbungee build")
public void info(CommandIssuer issuer) {
final String message = """
@Default
@Subcommand("info|version|git")
@Description("information about current redisbungee build")
public void info(CommandIssuer issuer) {
final String message =
"""
<color:aqua>This proxy is running RedisBungee Limework's fork
<color:gold>========================================
<color:aqua>RedisBungee version: <color:green><version>
@ -61,129 +59,166 @@ public class CommandRedisBungee extends AdventureBaseCommand {
Placeholder.component(
"commit",
Component.text(Constants.GIT_COMMIT.substring(0, 8))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, Constants.getGithubCommitLink()))
.hoverEvent(HoverEvent.showText(Component.text("Click me to open: " + Constants.getGithubCommitLink())))
)));
.clickEvent(
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>......
@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>";
sendMessage(
issuer,
Component.text("cleanup is Starting, you should see the output status in the proxy console")
.color(NamedTextColor.GOLD));
plugin.executeAsync(new StopperUUIDCleanupTask(plugin));
}
TextComponent.Builder message = Component.text();
message.append(MiniMessage.miniMessage().deserialize(barFormat));
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()));
}
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()))
));
}
});
@Subcommand("show")
@Description("Shows proxies in this network")
public void showProxies(CommandIssuer issuer, String[] args) {
final String closer = "<color:gold>========================================";
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")
@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;
}
sendMessage(issuer,
Component.text("cleanup is Starting, you should see the output status in the proxy console").color(NamedTextColor.GOLD));
plugin.executeAsync(new StopperUUIDCleanupTask(plugin));
// 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--;
}
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()));
while (left > 0) {
builder.appendNewline();
left--;
}
@Subcommand("show")
@Description("Shows proxies in this network")
public void showProxies(CommandIssuer issuer, String[] args) {
final String closer = "<color:gold>========================================";
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 :(";
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 (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());
}
}

View File

@ -1,20 +1,18 @@
/*
* 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
*/
* 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;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.*;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
import java.util.UUID;
@CommandAlias("rbd|redisbungeedebug")
@ -22,25 +20,29 @@ import java.util.UUID;
@Description("debug commands")
public class CommandRedisBungeeDebug extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin;
private final RedisBungeePlugin<?> plugin;
public CommandRedisBungeeDebug(RedisBungeePlugin<?> 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");
}
public CommandRedisBungeeDebug(RedisBungeePlugin<?> 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");
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.find")
public class CommandFind extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandFind(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void find(CommandIssuer issuer, String[] args) {
rootCommand.find(issuer, args);
}
public CommandFind(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void find(CommandIssuer issuer, String[] args) {
rootCommand.find(issuer, args);
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.glist")
public class CommandGList extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandGList(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void gList(CommandIssuer issuer, String[] args) {
rootCommand.gList(issuer, args);
}
public CommandGList(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void gList(CommandIssuer issuer, String[] args) {
rootCommand.gList(issuer, args);
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.ip")
public class CommandIp extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandIp(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
public CommandIp(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void ip(CommandIssuer issuer, String[] args) {
this.rootCommand.ip(issuer, args);
}
@Default
public void ip(CommandIssuer issuer, String[] args) {
this.rootCommand.ip(issuer, args);
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,15 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.lastseen")
public class CommandLastSeen extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandLastSeen(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
public CommandLastSeen(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
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
*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -19,15 +18,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandAlias("pproxy")
@CommandPermission("redisbungee.command.pproxy")
public class CommandPProxy extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandPProxy(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void playerProxy(CommandIssuer issuer, String[] args) {
this.rootCommand.playerProxy(issuer,args);
}
public CommandPProxy(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@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
*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,16 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.plist")
public class CommandPlist extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandPlist(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void playerList(CommandIssuer issuer, String[] args) {
this.rootCommand.playerList(issuer, args);
}
public CommandPlist(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@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
*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,14 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.sendtoall")
public class CommandSendToAll extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandSendToAll(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
public CommandSendToAll(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
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
*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,14 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.serverid")
public class CommandServerId extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandServerId(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
public CommandServerId(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
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
*
* 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
*/
* 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.legacy;
import co.aikar.commands.CommandIssuer;
@ -20,17 +19,14 @@ import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseComma
@CommandPermission("redisbungee.command.serverids")
public class CommandServerIds extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
private final LegacyRedisBungeeCommands rootCommand;
public CommandServerIds(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void serverIds(CommandIssuer issuer) {
this.rootCommand.serverIds(issuer);
}
public CommandServerIds(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void serverIds(CommandIssuer issuer) {
this.rootCommand.serverIds(issuer);
}
}

View File

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

View File

@ -1,26 +1,22 @@
/*
* 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
*/
* 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;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.CommandIssuer;
import net.kyori.adventure.text.Component;
/**
* this just dumb class that wraps the adventure stuff into base command
*/
/** this just dumb class that wraps the adventure stuff into base command */
public abstract class AdventureBaseCommand extends BaseCommand {
protected void sendMessage(CommandIssuer issuer, Component component) {
CommandPlatformHelper.getPlatformHelper().sendMessage(issuer, component);
}
protected void sendMessage(CommandIssuer issuer, Component component) {
CommandPlatformHelper.getPlatformHelper().sendMessage(issuer, component);
}
}

View File

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

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

View File

@ -1,7 +1,9 @@
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
/*
* 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
*/

View File

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

View File

@ -1,55 +1,63 @@
/*
* 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
*/
* 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 net.limework.valiobungee.config.lang;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
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.minimessage.MiniMessage;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Locale;
public interface LangConfigLoader extends GenericConfigLoader {
int CONFIG_VERSION = 1;
int CONFIG_VERSION = 1;
default void loadLangConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml");
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
handleOldConfig(dataFolder, "lang.yml", "lang.yml");
node = yamlConfigurationFileLoader.load();
}
// MINI message serializer
MiniMessage miniMessage = MiniMessage.miniMessage();
Component prefix = miniMessage.deserialize(node.getNode("prefix").getString("<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));
default void loadLangConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml");
final YAMLConfigurationLoader yamlConfigurationFileLoader =
YAMLConfigurationLoader.builder().setPath(configFile).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
handleOldConfig(dataFolder, "lang.yml", "lang.yml");
node = yamlConfigurationFileLoader.load();
}
// MINI message serializer
MiniMessage miniMessage = MiniMessage.miniMessage();
Component prefix =
miniMessage.deserialize(
node.getNode("prefix").getString("<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
*
* 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
*/
* 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 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.Locale;
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
* until I come up with better system but for now we will use Maps instead :/
* Todo: possible usage of adventure api
* This language support implementation is temporarily until I come up with better system but for
* now we will use Maps instead :/ Todo: possible usage of adventure api
*/
public class LangConfiguration {
private interface RegistrableMessages {
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) {
throw new IllegalStateException("Language system in `" + where + "` found missing entries for " + locale.toString());
}
default void throwError(Locale locale, String where) {
throw new IllegalStateException(
"Language system in `" + where + "` found missing entries for " + locale.toString());
}
}
public static class Messages implements RegistrableMessages {
private final Map<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{
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 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");
}
}
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));
}
}
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 alreadyLoggedIn(Locale locale) {
if (ALREADY_LOGGED_IN.containsKey(locale)) return ALREADY_LOGGED_IN.get(locale);
return ALREADY_LOGGED_IN.get(defaultLocale);
}
public Component redisBungeePrefix() {
return redisBungeePrefix;
// 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 Locale defaultLanguage() {
return defaultLanguage;
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);
}
public boolean useClientLanguage() {
return useClientLanguage;
// 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 Messages messages() {
return messages;
public Component serverConnecting(Locale locale, String server) {
String miniMessage;
if (SERVER_CONNECTING.containsKey(locale)) {
miniMessage = SERVER_CONNECTING.get(locale);
} else {
miniMessage = SERVER_CONNECTING.get(defaultLocale);
}
return MiniMessage.miniMessage()
.deserialize(miniMessage, Placeholder.parsed("server", server));
}
public Component serverConnecting(String server) {
return this.serverConnecting(this.defaultLocale, server);
}
public Component serverNotFound(Locale locale, String server) {
String miniMessage;
if (SERVER_NOT_FOUND.containsKey(locale)) {
miniMessage = SERVER_NOT_FOUND.get(locale);
} else {
miniMessage = SERVER_NOT_FOUND.get(defaultLocale);
}
return MiniMessage.miniMessage()
.deserialize(miniMessage, Placeholder.parsed("server", server));
}
public Component serverNotFound(String server) {
return this.serverNotFound(this.defaultLocale, server);
}
// tests locale if set CORRECTLY or just throw if not
public void test(Locale locale) {
if (!(LOGGED_IN_FROM_OTHER_LOCATION.containsKey(locale)
&& ALREADY_LOGGED_IN.containsKey(locale)
&& SERVER_CONNECTING.containsKey(locale)
&& SERVER_NOT_FOUND.containsKey(locale))) {
throwError(locale, "messages");
}
}
}
private final Component redisBungeePrefix;
private final Locale defaultLanguage;
private final boolean useClientLanguage;
private final Messages messages;
public LangConfiguration(
Component redisBungeePrefix,
Locale defaultLanguage,
boolean useClientLanguage,
Messages messages) {
this.redisBungeePrefix = redisBungeePrefix;
this.defaultLanguage = defaultLanguage;
this.useClientLanguage = useClientLanguage;
this.messages = messages;
}
public Component redisBungeePrefix() {
return redisBungeePrefix;
}
public Locale defaultLanguage() {
return defaultLanguage;
}
public boolean useClientLanguage() {
return useClientLanguage;
}
public Messages messages() {
return messages;
}
}

View File

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

View File

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

View File

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

View File

@ -1,52 +1,51 @@
/*
* 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
*/
* 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.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import net.md_5.bungee.api.plugin.Event;
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
* 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.
*
* @since 0.3.4
*/
public class PlayerChangedServerNetworkEvent extends Event implements IPlayerChangedServerNetworkEvent {
private final UUID uuid;
private final String previousServer;
private final String server;
public class PlayerChangedServerNetworkEvent extends Event
implements IPlayerChangedServerNetworkEvent {
private final UUID uuid;
private final String previousServer;
private final String server;
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
this.uuid = uuid;
this.previousServer = previousServer;
this.server = server;
}
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
this.uuid = uuid;
this.previousServer = previousServer;
this.server = server;
}
@Override
public UUID getUuid() {
return uuid;
}
@Override
public UUID getUuid() {
return uuid;
}
@Override
public String getServer() {
return server;
}
@Override
public String getServer() {
return server;
}
@Override
public String getPreviousServer() {
return previousServer;
}
@Override
public String getPreviousServer() {
return previousServer;
}
}

View File

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

View File

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

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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.events;
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.
* <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
*/
public class PubSubMessageEvent extends Event implements IPubSubMessageEvent {
private final String channel;
private final String message;
private final String channel;
private final String message;
public PubSubMessageEvent(String channel, String message) {
this.channel = channel;
this.message = message;
}
public PubSubMessageEvent(String channel, String message) {
this.channel = channel;
this.message = message;
}
@Override
public String getChannel() {
return channel;
}
@Override
public String getChannel() {
return channel;
}
@Override
public String getMessage() {
return message;
}
@Override
public String getMessage() {
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;
import co.aikar.commands.BungeeCommandIssuer;
@ -8,10 +17,9 @@ import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
public class BungeeCommandPlatformHelper extends CommandPlatformHelper {
@Override
public void sendMessage(CommandIssuer issuer, Component component) {
BungeeCommandIssuer bIssuer = (BungeeCommandIssuer) issuer;
bIssuer.getIssuer().sendMessage(BungeeComponentSerializer.get().serialize(component));
}
@Override
public void sendMessage(CommandIssuer issuer, Component component) {
BungeeCommandIssuer bIssuer = (BungeeCommandIssuer) issuer;
bIssuer.getIssuer().sendMessage(BungeeComponentSerializer.get().serialize(component));
}
}

View File

@ -1,25 +1,24 @@
/*
* 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
*/
* 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;
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.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
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.minimessage.MiniMessage;
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.event.LoginEvent;
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.event.EventHandler;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BungeePlayerDataManager extends PlayerDataManager<ProxiedPlayer> implements Listener {
private final RedisBungee bPlugin;
public BungeePlayerDataManager(RedisBungee plugin) {
super(plugin);
bPlugin = plugin;
private final RedisBungee bPlugin;
public BungeePlayerDataManager(RedisBungee 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 onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
super.handleNetworkPlayerServerChange(event);
}
public void kickPlayer(UUID player, Component message) {
serializedPlayerKick(player, MINI_MESSAGE_SERIALIZER.serialize(message));
}
@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 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);
}
}
@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.completeIntent((Plugin) plugin);
event.setCancelled(true);
event.setCancelReason(
BungeeComponentSerializer.get()
.serialize(bPlugin.langConfiguration().messages().alreadyLoggedIn()));
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());
}
} 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());
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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;
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.RedisBungeeMode;
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.loaders.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
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.PlayerLeftNetworkEvent;
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.limework.valiobungee.config.lang.LangConfigLoader;
import net.limework.valiobungee.config.lang.LangConfiguration;
@ -43,327 +50,333 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;
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;
public class RedisBungee extends Plugin
implements RedisBungeePlugin<ProxiedPlayer>,
ConfigLoader,
LangConfigLoader,
ApiPlatformSupport {
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;
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;
@Override
public RedisBungeeConfiguration configuration() {
return this.configuration;
}
private final Logger logger = LoggerFactory.getLogger("RedisBungee");
public LangConfiguration langConfiguration() {
return this.langConfiguration;
}
@Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override
public RedisBungeeConfiguration configuration() {
return this.configuration;
@Override
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.....");
}
public LangConfiguration langConfiguration() {
return this.langConfiguration;
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
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override
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);
}
@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, new RedisBungeeListener(this));
// start listening
getProxy().getScheduler().runAsync(this, proxyDataManager);
// heartbeat
this.heartbeatTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS);
// cleanup
this.cleanupTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS);
// init the http lib
InitialUtils.checkRedisVersion(this);
uuidTranslator = new UUIDTranslator(this);
getProxy().getPluginManager().registerListener(this, this.playerDataManager);
getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this));
// start listening
getProxy().getScheduler().runAsync(this, proxyDataManager);
// heartbeat
this.heartbeatTask =
getProxy()
.getScheduler()
.schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS);
// cleanup
this.cleanupTask =
getProxy()
.getScheduler()
.schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS);
// init the http lib
InitialUtils.checkRedisVersion(this);
uuidTranslator = new UUIDTranslator(this);
// register plugin messages channel.
getProxy().registerChannel("legacy:redisbungee");
getProxy().registerChannel("RedisBungee");
// register plugin messages channel.
getProxy().registerChannel("legacy:redisbungee");
getProxy().registerChannel("RedisBungee");
// init the api
this.api = new RedisBungeeAPI(this);
apiStatic = (RedisBungeeAPI) this.api;
// init the api
this.api = new RedisBungeeAPI(this);
apiStatic = (RedisBungeeAPI) this.api;
// commands
CommandPlatformHelper.init(new BungeeCommandPlatformHelper());
this.commandManager = new BungeeCommandManager(this);
CommandLoader.initCommands(this.commandManager, this);
// commands
CommandPlatformHelper.init(new BungeeCommandPlatformHelper());
this.commandManager = new BungeeCommandManager(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();
}
@Override
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");
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
@Override
public Summoner<?> getSummoner() {
return this.summoner;
try {
this.proxyDataManager.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
@Override
public RedisBungeeMode getRedisBungeeMode() {
return this.redisBungeeMode;
try {
this.summoner.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
@Override
public void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().runAsync(this, runnable);
if (this.commandManager != null) {
this.commandManager.unregisterCommands();
}
logInfo("RedisBungee shutdown successfully");
}
@Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit);
@Override
public Summoner<?> getSummoner() {
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
*
* 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
*/
* 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;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import java.util.Collection;
import java.util.Collections;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
public class RedisBungeeCommandSender implements CommandSender {
private static final RedisBungeeCommandSender singleton;
private static final RedisBungeeCommandSender singleton;
static {
singleton = new RedisBungeeCommandSender();
}
static {
singleton = new RedisBungeeCommandSender();
}
public static RedisBungeeCommandSender getSingleton() {
return singleton;
}
public static RedisBungeeCommandSender getSingleton() {
return singleton;
}
@Override
public String getName() {
return "RedisBungee";
}
@Override
public String getName() {
return "RedisBungee";
}
@Override
public void sendMessage(String s) {
@Override
public void sendMessage(String s) {}
}
@Override
public void sendMessages(String... strings) {}
@Override
public void sendMessages(String... strings) {
@Override
public void sendMessage(BaseComponent... baseComponents) {}
}
@Override
public void sendMessage(BaseComponent baseComponent) {}
@Override
public void sendMessage(BaseComponent... baseComponents) {
@Override
public Collection<String> getGroups() {
return null;
}
}
@Override
public void addGroups(String... strings) {}
@Override
public void sendMessage(BaseComponent baseComponent) {
@Override
public void removeGroups(String... strings) {}
}
@Override
public boolean hasPermission(String s) {
return true;
}
@Override
public Collection<String> getGroups() {
return null;
}
@Override
public void setPermission(String s, boolean b) {}
@Override
public void addGroups(String... strings) {
}
@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();
}
@Override
public Collection<String> getPermissions() {
return Collections.emptySet();
}
}

View File

@ -1,23 +1,24 @@
/*
* 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
*/
* 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;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.*;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.HandleMotdOrder;
import java.util.*;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.AbstractReconnectHandler;
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.EventPriority;
import java.util.*;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.*;
public class RedisBungeeListener implements Listener {
private final RedisBungee plugin;
private final RedisBungee plugin;
public RedisBungeeListener(RedisBungee plugin) {
this.plugin = plugin;
public RedisBungeeListener(RedisBungee 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)
public void onPingFirst(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.FIRST) {
return;
}
onPing0(event);
@EventHandler(priority = EventPriority.NORMAL)
public void onPingNormal(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.NORMAL) {
return;
}
onPing0(event);
}
@EventHandler(priority = EventPriority.NORMAL)
public void onPingNormal(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.NORMAL) {
return;
}
onPing0(event);
@EventHandler(priority = EventPriority.HIGHEST)
public void onPingLast(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.LAST) {
return;
}
onPing0(event);
}
@EventHandler(priority = EventPriority.HIGHEST)
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().getAddress().getAddress())) return;
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
private void onPing0(ProxyPingEvent event) {
if (!plugin.configuration().handleMotd()) return;
if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getAddress().getAddress())) return;
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
if (forced != null && event.getConnection().getListener().isPingPassthrough()) return;
event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers());
}
if (forced != null && event.getConnection().getListener().isPingPassthrough()) return;
event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers());
}
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void onPluginMessage(PluginMessageEvent event) {
if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee"))
&& event.getSender() instanceof Server) {
final String currentChannel = event.getTag();
final byte[] data = Arrays.copyOf(event.getData(), event.getData().length);
plugin.executeAsync(
() -> {
ByteArrayDataInput in = ByteStreams.newDataInput(data);
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void onPluginMessage(PluginMessageEvent event) {
if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) {
final String currentChannel = event.getTag();
final byte[] data = Arrays.copyOf(event.getData(), event.getData().length);
plugin.executeAsync(() -> {
ByteArrayDataInput in = ByteStreams.newDataInput(data);
String subchannel = in.readUTF();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String type;
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 = 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;
}
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) {
}
}
((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)));
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;
}
}
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)
}
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies {
implementation(project(":RedisBungee-Velocity"))
compileOnly(libs.platform.velocity)

View File

@ -1,45 +1,43 @@
/*
* 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
*/
* 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;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import net.kyori.adventure.permission.PermissionChecker;
public class RedisBungeeCommandSource implements CommandSource {
private static final RedisBungeeCommandSource singleton;
private final PermissionChecker permissionChecker = PermissionChecker.always(net.kyori.adventure.util.TriState.TRUE);
private static final RedisBungeeCommandSource singleton;
private final PermissionChecker permissionChecker =
PermissionChecker.always(net.kyori.adventure.util.TriState.TRUE);
static {
singleton = new RedisBungeeCommandSource();
}
static {
singleton = new RedisBungeeCommandSource();
}
public static RedisBungeeCommandSource getSingleton() {
return singleton;
}
public static RedisBungeeCommandSource getSingleton() {
return singleton;
}
@Override
public boolean hasPermission(String permission) {
return this.permissionChecker.test(permission);
}
@Override
public boolean hasPermission(String permission) {
return this.permissionChecker.test(permission);
}
@Override
public Tristate getPermissionValue(String s) {
return Tristate.TRUE;
}
@Override
public Tristate getPermissionValue(String s) {
return Tristate.TRUE;
}
@Override
public PermissionChecker getPermissionChecker() {
return this.permissionChecker;
}
@Override
public PermissionChecker getPermissionChecker() {
return this.permissionChecker;
}
}

View File

@ -1,22 +1,23 @@
/*
* 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
*/
* 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;
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.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.HandleMotdOrder;
import com.velocitypowered.api.event.PostOrder;
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.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import java.util.*;
import java.util.stream.Collectors;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset;
public class RedisBungeeListener {
private final RedisBungeeVelocityPlugin plugin;
private final RedisBungeeVelocityPlugin plugin;
public RedisBungeeListener(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
public RedisBungeeListener(RedisBungeeVelocityPlugin 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)
public void onPingFirst(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.FIRST) {
return;
}
onPing0(event);
}
event.setResult(PluginMessageEvent.ForwardResult.handled());
@Subscribe(order = PostOrder.NORMAL)
public void onPingNormal(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.NORMAL) {
return;
}
onPing0(event);
}
plugin.executeAsync(
() -> {
ByteArrayDataInput in = event.dataAsDataStream();
@Subscribe(order = PostOrder.LAST)
public void onPingLast(ProxyPingEvent event) {
if (plugin.configuration().handleMotdOrder() != HandleMotdOrder.LAST) {
return;
}
onPing0(event);
}
String subchannel = in.readUTF();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String type;
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;
}
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));
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) {
}
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))));
}
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");
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 -> {
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 {
// ServerConnection throws IllegalStateException when connection dies somehow so just ignore :/
((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray());
} catch (IllegalStateException ignored) {
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;
}
}
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
*
* 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
*/
* 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;
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.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.CommandLoader;
import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper;
import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader;
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.IPlayerJoinedNetworkEvent;
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.util.InitialUtils;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import com.imaginarycode.minecraft.redisbungee.commands.CommandLoader;
import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
@ -43,12 +42,6 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.scheduler.ScheduledTask;
import net.kyori.adventure.text.Component;
import 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.InputStream;
import java.net.InetAddress;
@ -59,307 +52,333 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
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"})
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, ConfigLoader, LangConfigLoader, ApiPlatformSupport {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private final AbstractRedisBungeeAPI api;
private Summoner<?> jedisSummoner;
private RedisBungeeMode redisBungeeMode;
private final UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private LangConfiguration langConfiguration;
@Plugin(
id = "redisbungee",
name = "RedisBungee",
version = Constants.VERSION,
url = "https://github.com/ProxioDev/RedisBungee",
authors = {"astei", "ProxioDev"})
public class RedisBungeeVelocityPlugin
implements RedisBungeePlugin<Player>, ConfigLoader, LangConfigLoader, ApiPlatformSupport {
private final ProxyServer server;
private final Logger logger;
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 heartbeatTask;
private ScheduledTask cleanUpTask;
private ScheduledTask heartbeatTask;
public static final List<ChannelIdentifier> IDENTIFIERS = List.of(
MinecraftChannelIdentifier.create("legacy", "redisbungee"),
new LegacyChannelIdentifier("RedisBungee"),
// This is needed for clients before 1.13
new LegacyChannelIdentifier("legacy:redisbungee")
);
public static final List<ChannelIdentifier> IDENTIFIERS =
List.of(
MinecraftChannelIdentifier.create("legacy", "redisbungee"),
new LegacyChannelIdentifier("RedisBungee"),
// This is needed for clients before 1.13
new LegacyChannelIdentifier("legacy:redisbungee"));
private VelocityCommandManager commandManager;
private VelocityCommandManager commandManager;
@Inject
public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
this.server = server;
this.logger = logger;
this.dataFolder = dataDirectory;
logInfo("Version: {}", Constants.VERSION);
try {
loadConfig(this, dataDirectory);
loadLangConfig(this, dataDirectory);
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
} catch (JedisConnectionException e) {
throw new RuntimeException("Unable to connect to your Redis server!", e);
}
this.api = new RedisBungeeAPI(this);
InitialUtils.checkRedisVersion(this);
this.proxyDataManager = new ProxyDataManager(this) {
@Override
public Set<UUID> getLocalOnlineUUIDs() {
HashSet<UUID> players = new HashSet<>();
server.getAllPlayers().forEach(player -> players.add(player.getUniqueId()));
return players;
}
@Inject
public RedisBungeeVelocityPlugin(
ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
this.server = server;
this.logger = logger;
this.dataFolder = dataDirectory;
logInfo("Version: {}", Constants.VERSION);
try {
loadConfig(this, dataDirectory);
loadLangConfig(this, dataDirectory);
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
} catch (JedisConnectionException e) {
throw new RuntimeException("Unable to connect to your Redis server!", e);
}
this.api = new RedisBungeeAPI(this);
InitialUtils.checkRedisVersion(this);
this.proxyDataManager =
new ProxyDataManager(this) {
@Override
public Set<UUID> getLocalOnlineUUIDs() {
HashSet<UUID> players = new HashSet<>();
server.getAllPlayers().forEach(player -> players.add(player.getUniqueId()));
return players;
}
@Override
protected void handlePlatformCommandExecution(String command) {
server.getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), command);
}
@Override
protected void handlePlatformCommandExecution(String command) {
server
.getCommandManager()
.executeAsync(RedisBungeeCommandSource.getSingleton(), command);
}
};
this.playerDataManager = new VelocityPlayerDataManager(this);
uuidTranslator = new UUIDTranslator(this);
this.playerDataManager = new VelocityPlayerDataManager(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
public Summoner<?> getSummoner() {
return this.jedisSummoner;
}
@Override
public void onConfigLoad(
RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode) {
this.jedisSummoner = summoner;
this.configuration = configuration;
this.redisBungeeMode = mode;
}
@Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override
public void onLangConfigLoad(LangConfiguration langConfiguration) {
this.langConfiguration = langConfiguration;
}
@Override
public ProxyDataManager proxyDataManager() {
return this.proxyDataManager;
}
@Override
public RedisBungeeMode getRedisBungeeMode() {
return this.redisBungeeMode;
}
@Override
public PlayerDataManager<Player> playerDataManager() {
return this.playerDataManager;
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyInitializeEvent(ProxyInitializeEvent event) {
initialize();
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@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 void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().buildTask(this, runnable).schedule();
}
@Override
public IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid) {
return new PlayerJoinedNetworkEvent(uuid);
}
@Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
this.getProxy().getScheduler().buildTask(this, runnable).delay(time, timeUnit).schedule();
}
@Override
public IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid) {
return new PlayerLeftNetworkEvent(uuid);
}
@Override
public void fireEvent(Object event) {
this.getProxy().getEventManager().fireAndForget(event);
}
@Override
public IPubSubMessageEvent createPubSubEvent(String channel, String message) {
return new PubSubMessageEvent(channel, message);
}
@Override
public boolean isOnlineMode() {
return this.getProxy().getConfiguration().isOnlineMode();
}
public ProxyServer getProxy() {
return server;
}
@Override
public void logInfo(String msg) {
this.getLogger().info(msg);
}
@Override
public void kickPlayer(UUID player, Component message) {
this.playerDataManager.kickPlayer(player, message);
}
@Override
public void logInfo(String format, Object... object) {
logger.info(format, object);
}
public Logger getLogger() {
return logger;
}
@Override
public void logWarn(String msg) {
this.getLogger().warn(msg);
}
public Path getDataFolder() {
return this.dataFolder;
}
@Override
public void logWarn(String format, Object... object) {
logger.warn(format, object);
}
public InputStream getResourceAsStream(String name) {
return this.getClass().getClassLoader().getResourceAsStream(name);
}
@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
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";
}
@Override
public String platformId() {
return "velocity";
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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;
import co.aikar.commands.CommandIssuer;
@ -17,10 +16,9 @@ import net.kyori.adventure.text.Component;
public class VelocityCommandPlatformHelper extends CommandPlatformHelper {
@Override
public void sendMessage(CommandIssuer issuer, Component component) {
VelocityCommandIssuer vIssuer = (VelocityCommandIssuer) issuer;
vIssuer.getIssuer().sendMessage(component);
}
@Override
public void sendMessage(CommandIssuer issuer, Component component) {
VelocityCommandIssuer vIssuer = (VelocityCommandIssuer) issuer;
vIssuer.getIssuer().sendMessage(component);
}
}

View File

@ -1,13 +1,12 @@
/*
* 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
*/
* 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;
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.player.ServerConnectedEvent;
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.concurrent.TimeUnit;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
public class VelocityPlayerDataManager extends PlayerDataManager<Player> {
private final RedisBungeeVelocityPlugin vplugin;
private final RedisBungeeVelocityPlugin vplugin;
public VelocityPlayerDataManager(RedisBungeeVelocityPlugin plugin) {
super(plugin);
this.vplugin = plugin;
public VelocityPlayerDataManager(RedisBungeeVelocityPlugin plugin) {
super(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
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
handleNetworkPlayerServerChange(event);
private static final MiniMessage MINI_MESSAGE_SERIALIZER = MiniMessage.miniMessage();
@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 onNetworkPlayerQuit(PlayerLeftNetworkEvent event) {
handleNetworkPlayerQuit(event);
}
public void kickPlayer(UUID player, Component message) {
serializedPlayerKick(player, MINI_MESSAGE_SERIALIZER.serialize(message));
}
@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();
@Subscribe
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 {
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();
@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 onLoginEvent(PostLoginEvent event) {
addPlayer(
event.getPlayer().getUniqueId(),
event.getPlayer().getUsername(),
event.getPlayer().getRemoteAddress().getAddress());
}
public void kickPlayer(UUID player, Component message) {
serializedPlayerKick(player, MINI_MESSAGE_SERIALIZER.serialize(message));
}
@Subscribe
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());
}
@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`
}
version = property("redisbungee-version").toString()
group = property("redisbungee-group").toString()
dependencies {
api(project(":RedisBungee-API")) {
// Since velocity already includes guava / configurate / guava exlude them

View File

@ -1,25 +1,22 @@
/*
* 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
*/
* 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;
import com.velocitypowered.api.proxy.ProxyServer;
import net.kyori.adventure.text.Component;
import java.util.UUID;
import net.kyori.adventure.text.Component;
// this class used to redirect calls to keep the implementation and api separate
public interface ApiPlatformSupport {
ProxyServer getProxy();
void kickPlayer(UUID player, Component message);
ProxyServer getProxy();
void kickPlayer(UUID player, Component message);
}

View File

@ -1,89 +1,89 @@
/*
* 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
*/
* 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;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
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()}
* or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}.
* This platform class exposes some internal RedisBungee functions. You obtain an instance of this
* 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
* @since 0.2.3
*/
public class RedisBungeeAPI extends AbstractRedisBungeeAPI {
private static RedisBungeeAPI redisBungeeApi;
public RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
super(plugin);
if (redisBungeeApi == null) {
redisBungeeApi = this;
}
private static RedisBungeeAPI redisBungeeApi;
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
* as well, and will return local information on them.
*
* @param player a player uuid
* @return {@link ServerInfo} Can be null if proxy can't find it.
* @see #getServerNameFor(UUID)
*/
@Nullable
public final ServerInfo getServerFor(@NonNull UUID player) {
String serverName = this.getServerNameFor(player);
if (serverName == null) return null;
return ((ApiPlatformSupport) this.plugin).getProxy().getServer(serverName).map((RegisteredServer::getServerInfo)).orElse(null);
}
/**
* 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 {@link ServerInfo} Can be null if proxy can't find it.
* @see #getServerNameFor(UUID)
*/
@Nullable
public final ServerInfo getServerFor(@NonNull UUID player) {
String serverName = this.getServerNameFor(player);
if (serverName == null) return null;
return ((ApiPlatformSupport) this.plugin)
.getProxy()
.getServer(serverName)
.map((RegisteredServer::getServerInfo))
.orElse(null);
}
/**
* Kicks a player from the network
* calls {@link #getUuidFromName(String)} to get uuid
*
* @param playerName player name
* @param message kick message that player will see on kick
* @since 0.12.0
*/
public void kickPlayer(String playerName, Component message) {
kickPlayer(getUuidFromName(playerName), message);
}
/**
* Kicks a player from the network calls {@link #getUuidFromName(String)} to get uuid
*
* @param playerName player name
* @param message kick message that player will see on kick
* @since 0.12.0
*/
public void kickPlayer(String playerName, Component message) {
kickPlayer(getUuidFromName(playerName), message);
}
/**
* Kicks a player from the network
*
* @param playerUUID player name
* @param message kick message that player will see on kick
* @since 0.12.0
*/
public void kickPlayer(UUID playerUUID, Component message) {
((ApiPlatformSupport) this.plugin).kickPlayer(playerUUID, message);
}
/**
* Kicks a player from the network
*
* @param playerUUID player name
* @param message kick message that player will see on kick
* @since 0.12.0
*/
public void kickPlayer(UUID playerUUID, Component message) {
((ApiPlatformSupport) this.plugin).kickPlayer(playerUUID, message);
}
/**
* Api instance
*
* @return the API instance.
* @since 0.6.5
*/
public static RedisBungeeAPI getRedisBungeeApi() {
return redisBungeeApi;
}
/**
* Api instance
*
* @return the API instance.
* @since 0.6.5
*/
public static RedisBungeeAPI getRedisBungeeApi() {
return redisBungeeApi;
}
}

View File

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

View File

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

View File

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

View File

@ -1,42 +1,39 @@
/*
* 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
*/
* 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.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
/**
* 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
*/
public class PubSubMessageEvent implements IPubSubMessageEvent {
private final String channel;
private final String message;
private final String channel;
private final String message;
public PubSubMessageEvent(String channel, String message) {
this.channel = channel;
this.message = message;
}
public PubSubMessageEvent(String channel, String message) {
this.channel = channel;
this.message = message;
}
@Override
public String getChannel() {
return channel;
}
@Override
public String getChannel() {
return channel;
}
@Override
public String getMessage() {
return message;
}
@Override
public String getMessage() {
return message;
}
}

View File

@ -9,10 +9,12 @@ rootProject.name = "ValioBungee"
fun configureProject(name: String) {
val projectName = ":valiobungee-$name"
include(projectName)
project(projectName).projectDir = file(name)
configureProject(projectName,name)
}
fun configureProject(name: String, path: String) {
include(name)
project(name).projectDir = file(path)
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
@ -24,4 +26,15 @@ dependencyResolutionManagement {
}
}
// 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")