mirror of
https://github.com/proxiodev/RedisBungee.git
synced 2026-04-25 16:00:27 +00:00
0.12.0 (#86)
## NOTES data system shouldn't effect anybody, unless you do any direct query to Redis query data, you should adapt the changes, by viewing classes `ProxyDataManager` and `PlayerDataManager` # Changes * RedisBungee is compiled with `java 17` now, Due java 11 support is ending at end of September * config version is now `2` which will reset your config if older version * Adventure API is included inside RedisBungee API * new Language infrastructure for RedisBungee built-in messages #85 *commands not included yet* * New data system which replaces Redis PubSub with Redis Streams *see below* * Ability to connect player to last server they where on using an config option #84 * new environment variable `REDISBUNGEE_PROXY_ID` which can be set before launch * new environment variable `REDISBUNGEE_NETWORK_ID` which can be set before launch * RedisBungee requires redis version 6.2 or above #88 * Better command system https://github.com/ProxioDev/RedisBungee/issues/93 ## New data system Due limitation of Redis PubSub in Cluster environment, Internals of RedisBungee were changed to support Redis Streams - Network Ids - networks ids used to group network proxies - example having 'test' network and 'main' network - Networks in the same redis server / cluster share the same UUID cache - Heartbeat system: - RedisBungee old heartbeat system used hastset on redisbungee to store the current unix time of the proxy to check what every proxy died or not, now instead we publish the heartbeat using unix time, and online count to proxy which proxy store it in their memory, which allow the `get number of online players` to be faster than pooling whole list in old data system. - PubSub - since redisbungee was initially designed with pubsub in mind, registration no longer required now for event to fire, see the api changes below. ## Commands System * rewritten using [acf lib](https://github.com/aikar/commands) to be platform independent * new command `/rb` or `/redisbungee` with sub commands `help`, `info`, 'clean', 'show'. * 'rb' * '/rb' and '/rb info'  * '/rb show'  * configuration to disable or override each command from legacy to new introduced one `/rb` ```yaml # For redis bungee legacy commands # either can be run using '/rbl glist' for example # or if 'install' is set to true '/glist' can be used. # 'install' also overrides the proxy installed commands # # In legacy commands each command got it own permissions since they had it own permission pre new command system, # so it's also applied to subcommands in '/rbl'. commands: # Permission redisbungee.legacy.use redisbungee-legacy: enabled: false subcommands: # Permission redisbungee.command.glist glist: enabled: false install: false # Permission redisbungee.command.find find: enabled: false install: false # Permission redisbungee.command.lastseen lastseen: enabled: false install: false # Permission redisbungee.command.ip ip: enabled: false install: false # Permission redisbungee.command.pproxy pproxy: enabled: false install: false # Permission redisbungee.command.sendtoall sendtoall: enabled: false install: false # Permission redisbungee.command.serverid serverid: enabled: false install: false # Permission redisbungee.command.serverids serverids: enabled: false install: false # Permission redisbungee.command.plist plist: enabled: false install: false # Permission redisbungee.command.use redisbungee: enabled: true ``` ## API changes - Kick api Deprecated: - `kickPlayer(String playerName, String message) ` - `kickPlayer(UUID playerUUID, String message) ` - newer where added using adventure api: - `kickPlayer(String playerName, Component message) ` - `kickPlayer(UUID playerUUID, Component message) ` - PubSub registration api Deprecated: ```java /** * 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 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 public final void unregisterPubSubChannels(String... channels) { } ``` # Contributors * `summoncraft.us` for running this branch in production * @SrBedrock for providing [Brazilian Portuguese](https://en.wikipedia.org/wiki/Brazilian_Portuguese) translation #87 # issues closes #84 closes #88 closes #92 closes #81 closes #93 --------- Signed-off-by: mohammed jasem alaajel <xrambad@gmail.com> Co-authored-by: ThiagoROX <51332006+SrBedrock@users.noreply.github.com>
This commit is contained in:
@@ -10,8 +10,6 @@
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
|
||||
@@ -19,6 +17,7 @@ import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import redis.clients.jedis.Jedis;
|
||||
@@ -40,21 +39,13 @@ import java.util.*;
|
||||
public abstract class AbstractRedisBungeeAPI {
|
||||
protected final RedisBungeePlugin<?> plugin;
|
||||
private static AbstractRedisBungeeAPI abstractRedisBungeeAPI;
|
||||
protected final List<String> reservedChannels;
|
||||
|
||||
AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
|
||||
// this does make sure that no one can place first initiated API class.
|
||||
public AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
|
||||
// this does make sure that no one can replace first initiated API class.
|
||||
if (abstractRedisBungeeAPI == null) {
|
||||
abstractRedisBungeeAPI = this;
|
||||
}
|
||||
this.reservedChannels = ImmutableList.of(
|
||||
"redisbungee-allservers",
|
||||
"redisbungee-" + plugin.getConfiguration().getProxyId(),
|
||||
"redisbungee-data"
|
||||
);
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +54,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @return a count of all players found
|
||||
*/
|
||||
public final int getPlayerCount() {
|
||||
return plugin.getCount();
|
||||
return plugin.proxyDataManager().totalNetworkPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +65,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @return the last time a player was on, if online returns a 0
|
||||
*/
|
||||
public final long getLastOnline(@NonNull UUID player) {
|
||||
return plugin.getDataManager().getLastOnline(player);
|
||||
return plugin.playerDataManager().getLastOnline(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +77,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
*/
|
||||
@Nullable
|
||||
public final String getServerNameFor(@NonNull UUID player) {
|
||||
return plugin.getDataManager().getServer(player);
|
||||
return plugin.playerDataManager().getServerFor(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +88,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @return a Set with all players found
|
||||
*/
|
||||
public final Set<UUID> getPlayersOnline() {
|
||||
return plugin.getPlayers();
|
||||
return plugin.proxyDataManager().networkPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,11 +109,11 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
/**
|
||||
* Get a full list of players on all servers.
|
||||
*
|
||||
* @return a immutable Multimap with all players found on this server
|
||||
* @return a immutable Multimap with all players found on this network
|
||||
* @since 0.2.5
|
||||
*/
|
||||
public final Multimap<String, UUID> getServerToPlayers() {
|
||||
return plugin.serverToPlayersCache();
|
||||
return plugin.playerDataManager().serversToPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,11 +129,11 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
/**
|
||||
* Get a list of players on the specified proxy.
|
||||
*
|
||||
* @param server a server name
|
||||
* @param proxyID proxy id
|
||||
* @return a Set with all UUIDs found on this proxy
|
||||
*/
|
||||
public final Set<UUID> getPlayersOnProxy(@NonNull String server) {
|
||||
return plugin.getPlayersOnProxy(server);
|
||||
public final Set<UUID> getPlayersOnProxy(@NonNull String proxyID) {
|
||||
return plugin.proxyDataManager().getPlayersOn(proxyID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +154,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @since 0.2.4
|
||||
*/
|
||||
public final InetAddress getPlayerIp(@NonNull UUID player) {
|
||||
return plugin.getDataManager().getIp(player);
|
||||
return plugin.playerDataManager().getIpFor(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +165,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @since 0.3.3
|
||||
*/
|
||||
public final String getProxy(@NonNull UUID player) {
|
||||
return plugin.getDataManager().getProxy(player);
|
||||
return plugin.playerDataManager().getProxyFor(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +176,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @since 0.2.5
|
||||
*/
|
||||
public final void sendProxyCommand(@NonNull String command) {
|
||||
plugin.sendProxyCommand("allservers", command);
|
||||
sendProxyCommand("allservers", command);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,19 +189,20 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @since 0.2.5
|
||||
*/
|
||||
public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) {
|
||||
plugin.sendProxyCommand(proxyId, command);
|
||||
plugin.proxyDataManager().sendCommandTo(proxyId, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for
|
||||
* PubSubMessageEvent to fire.
|
||||
* Sends a message to a PubSub channel which makes PubSubMessageEvent fire.
|
||||
* <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.sendChannelMessage(channel, message);
|
||||
plugin.proxyDataManager().sendChannelMessage(channel, message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +213,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @since 0.8.0
|
||||
*/
|
||||
public final String getProxyId() {
|
||||
return plugin.getConfiguration().getProxyId();
|
||||
return plugin.proxyDataManager().proxyId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +237,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @since 0.8.0
|
||||
*/
|
||||
public final List<String> getAllProxies() {
|
||||
return plugin.getProxiesIds();
|
||||
return plugin.proxyDataManager().proxiesIds();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,9 +258,10 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
*
|
||||
* @param channels the channels to register
|
||||
* @since 0.3
|
||||
* @deprecated No longer required
|
||||
*/
|
||||
@Deprecated
|
||||
public final void registerPubSubChannels(String... channels) {
|
||||
plugin.getPubSubListener().addChannel(channels);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,13 +269,10 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
*
|
||||
* @param channels the channels to unregister
|
||||
* @since 0.3
|
||||
* @deprecated No longer required
|
||||
*/
|
||||
@Deprecated
|
||||
public final void unregisterPubSubChannels(String... channels) {
|
||||
for (String channel : channels) {
|
||||
Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel");
|
||||
}
|
||||
|
||||
plugin.getPubSubListener().removeChannel(channels);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,14 +345,16 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
|
||||
/**
|
||||
* Kicks a player from the network
|
||||
* calls {@link #getUuidFromName(String)} to get uuid
|
||||
*
|
||||
* @param playerName player name
|
||||
* @param message kick message that player will see on kick
|
||||
* @param message kick message that player will see on kick
|
||||
* @since 0.8.0
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public void kickPlayer(String playerName, String message) {
|
||||
plugin.kickPlayer(playerName, message);
|
||||
kickPlayer(getUuidFromName(playerName), message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,11 +363,38 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
* @param playerUUID player name
|
||||
* @param message kick message that player will see on kick
|
||||
* @since 0.8.0
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void kickPlayer(UUID playerUUID, String message) {
|
||||
plugin.kickPlayer(playerUUID, message);
|
||||
kickPlayer(playerUUID, Component.text(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks a player from the network
|
||||
* calls {@link #getUuidFromName(String)} to get uuid
|
||||
*
|
||||
* @param playerName player name
|
||||
* @param message kick message that player will see on kick
|
||||
* @since 0.12.0
|
||||
*/
|
||||
|
||||
public void kickPlayer(String playerName, Component message) {
|
||||
kickPlayer(getUuidFromName(playerName), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks a player from the network
|
||||
*
|
||||
* @param playerUUID player name
|
||||
* @param message kick message that player will see on kick
|
||||
* @since 0.12.0
|
||||
*/
|
||||
public void kickPlayer(UUID playerUUID, Component message) {
|
||||
this.plugin.playerDataManager().kickPlayer(playerUUID, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This gives you instance of Jedis
|
||||
*
|
||||
@@ -457,6 +476,7 @@ public abstract class AbstractRedisBungeeAPI {
|
||||
|
||||
/**
|
||||
* shows what mode is RedisBungee is on
|
||||
* Basically what every redis mode is used like cluster or single instance.
|
||||
*
|
||||
* @return {@link RedisBungeeMode}
|
||||
* @since 0.8.0
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This class manages all the data that RedisBungee fetches from Redis, along with updates to that data.
|
||||
*
|
||||
* @since 0.3.3
|
||||
*/
|
||||
public abstract class AbstractDataManager<P, PL, PD, PS> {
|
||||
protected final RedisBungeePlugin<P> plugin;
|
||||
private final Cache<UUID, String> serverCache = createCache();
|
||||
private final Cache<UUID, String> proxyCache = createCache();
|
||||
private final Cache<UUID, InetAddress> ipCache = createCache();
|
||||
private final Cache<UUID, Long> lastOnlineCache = createCache();
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public AbstractDataManager(RedisBungeePlugin<P> plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private static <K, V> Cache<K, V> createCache() {
|
||||
// TODO: Allow customization via cache specification, ala ServerListPlus
|
||||
return CacheBuilder.newBuilder()
|
||||
.maximumSize(1000)
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
}
|
||||
|
||||
public String getServer(final UUID uuid) {
|
||||
P player = plugin.getPlayer(uuid);
|
||||
|
||||
if (player != null)
|
||||
return plugin.isPlayerOnAServer(player) ? plugin.getPlayerServerName(player) : null;
|
||||
|
||||
try {
|
||||
return serverCache.get(uuid, new RedisTask<String>(plugin) {
|
||||
@Override
|
||||
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "server"), "user not found");
|
||||
|
||||
}
|
||||
});
|
||||
} catch (ExecutionException | UncheckedExecutionException e) {
|
||||
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
||||
return null; // HACK
|
||||
plugin.logFatal("Unable to get server");
|
||||
throw new RuntimeException("Unable to get server for " + uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String getProxy(final UUID uuid) {
|
||||
P player = plugin.getPlayer(uuid);
|
||||
|
||||
if (player != null)
|
||||
return plugin.getConfiguration().getProxyId();
|
||||
|
||||
try {
|
||||
return proxyCache.get(uuid, new RedisTask<String>(plugin) {
|
||||
@Override
|
||||
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "proxy"), "user not found");
|
||||
}
|
||||
});
|
||||
} catch (ExecutionException | UncheckedExecutionException e) {
|
||||
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
||||
return null; // HACK
|
||||
plugin.logFatal("Unable to get proxy");
|
||||
throw new RuntimeException("Unable to get proxy for " + uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
public InetAddress getIp(final UUID uuid) {
|
||||
P player = plugin.getPlayer(uuid);
|
||||
|
||||
if (player != null)
|
||||
return plugin.getPlayerIp(player);
|
||||
|
||||
try {
|
||||
return ipCache.get(uuid, new RedisTask<InetAddress>(plugin) {
|
||||
@Override
|
||||
public InetAddress unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
String result = unifiedJedis.hget("player:" + uuid, "ip");
|
||||
if (result == null)
|
||||
throw new NullPointerException("user not found");
|
||||
return InetAddresses.forString(result);
|
||||
}
|
||||
});
|
||||
} catch (ExecutionException | UncheckedExecutionException e) {
|
||||
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
||||
return null; // HACK
|
||||
plugin.logFatal("Unable to get IP");
|
||||
throw new RuntimeException("Unable to get IP for " + uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
public long getLastOnline(final UUID uuid) {
|
||||
P player = plugin.getPlayer(uuid);
|
||||
|
||||
if (player != null)
|
||||
return 0;
|
||||
|
||||
try {
|
||||
return lastOnlineCache.get(uuid, new RedisTask<Long>(plugin) {
|
||||
|
||||
@Override
|
||||
public Long unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
String result = unifiedJedis.hget("player:" + uuid, "online");
|
||||
return result == null ? -1 : Long.parseLong(result);
|
||||
}
|
||||
});
|
||||
} catch (ExecutionException e) {
|
||||
plugin.logFatal("Unable to get last time online");
|
||||
throw new RuntimeException("Unable to get last time online for " + uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void invalidate(UUID uuid) {
|
||||
ipCache.invalidate(uuid);
|
||||
lastOnlineCache.invalidate(uuid);
|
||||
serverCache.invalidate(uuid);
|
||||
proxyCache.invalidate(uuid);
|
||||
}
|
||||
|
||||
// Invalidate all entries related to this player, since they now lie. (call invalidate(uuid))
|
||||
public abstract void onPostLogin(PL event);
|
||||
|
||||
// Invalidate all entries related to this player, since they now lie. (call invalidate(uuid))
|
||||
public abstract void onPlayerDisconnect(PD event);
|
||||
|
||||
public abstract void onPubSubMessage(PS event);
|
||||
|
||||
public abstract boolean handleKick(UUID target, String message);
|
||||
|
||||
protected void handlePubSubMessage(String channel, String message) {
|
||||
if (!channel.equals("redisbungee-data"))
|
||||
return;
|
||||
|
||||
// Partially deserialize the message so we can look at the action
|
||||
JsonObject jsonObject = JsonParser.parseString(message).getAsJsonObject();
|
||||
|
||||
final String source = jsonObject.get("source").getAsString();
|
||||
|
||||
if (source.equals(plugin.getConfiguration().getProxyId()))
|
||||
return;
|
||||
|
||||
DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString());
|
||||
|
||||
switch (action) {
|
||||
case JOIN:
|
||||
final DataManagerMessage<LoginPayload> message1 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<LoginPayload>>() {
|
||||
}.getType());
|
||||
proxyCache.put(message1.getTarget(), message1.getSource());
|
||||
lastOnlineCache.put(message1.getTarget(), (long) 0);
|
||||
ipCache.put(message1.getTarget(), message1.getPayload().getAddress());
|
||||
plugin.executeAsync(() -> {
|
||||
Object event = plugin.createPlayerJoinedNetworkEvent(message1.getTarget());
|
||||
plugin.fireEvent(event);
|
||||
});
|
||||
break;
|
||||
case LEAVE:
|
||||
final DataManagerMessage<LogoutPayload> message2 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<LogoutPayload>>() {
|
||||
}.getType());
|
||||
invalidate(message2.getTarget());
|
||||
lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp());
|
||||
plugin.executeAsync(() -> {
|
||||
Object event = plugin.createPlayerLeftNetworkEvent(message2.getTarget());
|
||||
plugin.fireEvent(event);
|
||||
});
|
||||
break;
|
||||
case SERVER_CHANGE:
|
||||
final DataManagerMessage<ServerChangePayload> message3 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<ServerChangePayload>>() {
|
||||
}.getType());
|
||||
serverCache.put(message3.getTarget(), message3.getPayload().getServer());
|
||||
plugin.executeAsync(() -> {
|
||||
Object event = plugin.createPlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer());
|
||||
plugin.fireEvent(event);
|
||||
});
|
||||
break;
|
||||
case KICK:
|
||||
final DataManagerMessage<KickPayload> kickPayload = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<KickPayload>>() {
|
||||
}.getType());
|
||||
plugin.executeAsync(() -> handleKick(kickPayload.target, kickPayload.payload.message));
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class DataManagerMessage<T extends Payload> {
|
||||
private final UUID target;
|
||||
private final String source;
|
||||
private final Action action; // for future use!
|
||||
private final T payload;
|
||||
|
||||
public DataManagerMessage(UUID target, String source, Action action, T payload) {
|
||||
this.target = target;
|
||||
this.source = source;
|
||||
this.action = action;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public UUID getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public T getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
JOIN,
|
||||
LEAVE,
|
||||
KICK,
|
||||
SERVER_CHANGE
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Payload {
|
||||
}
|
||||
|
||||
public static class KickPayload extends Payload {
|
||||
|
||||
private final String message;
|
||||
|
||||
public KickPayload(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginPayload extends Payload {
|
||||
private final InetAddress address;
|
||||
|
||||
public LoginPayload(InetAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public InetAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerChangePayload extends Payload {
|
||||
private final String server;
|
||||
private final String oldServer;
|
||||
|
||||
public ServerChangePayload(String server, String oldServer) {
|
||||
this.server = server;
|
||||
this.oldServer = oldServer;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public String getOldServer() {
|
||||
return oldServer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class LogoutPayload extends Payload {
|
||||
private final long timestamp;
|
||||
|
||||
public LogoutPayload(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api;
|
||||
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractRedisBungeeListener<LE, PLE, PD, SC, PP, PM, PS> {
|
||||
|
||||
protected final RedisBungeePlugin<?> plugin;
|
||||
protected final List<InetAddress> exemptAddresses;
|
||||
protected final Gson gson = new Gson();
|
||||
|
||||
public AbstractRedisBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
|
||||
this.plugin = plugin;
|
||||
this.exemptAddresses = exemptAddresses;
|
||||
}
|
||||
|
||||
public void onLogin(LE event) {}
|
||||
|
||||
public abstract void onPostLogin(PLE event);
|
||||
|
||||
public abstract void onPlayerDisconnect(PD event);
|
||||
|
||||
public abstract void onServerChange(SC event);
|
||||
|
||||
public abstract void onPing(PP event);
|
||||
|
||||
public abstract void onPluginMessage(PM event);
|
||||
|
||||
public abstract void onPubSubMessage(PS event);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api;
|
||||
|
||||
|
||||
import redis.clients.jedis.JedisPubSub;
|
||||
|
||||
|
||||
public class JedisPubSubHandler extends JedisPubSub {
|
||||
|
||||
private final RedisBungeePlugin<?> plugin;
|
||||
|
||||
public JedisPubSubHandler(RedisBungeePlugin<?> plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(final String s, final String s2) {
|
||||
if (s2.trim().length() == 0) return;
|
||||
plugin.executeAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Object event = plugin.createPubSubEvent(s, s2);
|
||||
plugin.fireEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import org.json.JSONObject;
|
||||
import redis.clients.jedis.ClusterPipeline;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.Response;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class PlayerDataManager<P, LE, DE, PS extends IPubSubMessageEvent, SC extends IPlayerChangedServerNetworkEvent, NJE extends IPlayerLeftNetworkEvent, CE> {
|
||||
|
||||
protected final RedisBungeePlugin<P> plugin;
|
||||
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 Object SERVERS_TO_PLAYERS_KEY = new Object();
|
||||
private final LoadingCache<Object, Multimap<String, UUID>> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder);
|
||||
private final UnifiedJedis unifiedJedis;
|
||||
private final String proxyId;
|
||||
private final String networkId;
|
||||
|
||||
public PlayerDataManager(RedisBungeePlugin<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 abstract void onPlayerChangedServerNetworkEvent(SC event);
|
||||
|
||||
public abstract void onNetworkPlayerQuit(NJE event);
|
||||
|
||||
// local events
|
||||
public abstract void onPubSubMessageEvent(PS event);
|
||||
|
||||
public abstract void onServerConnectedEvent(CE event);
|
||||
|
||||
public abstract void onLoginEvent(LE event);
|
||||
|
||||
public abstract void onDisconnectEvent(DE event);
|
||||
|
||||
|
||||
protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) {
|
||||
this.serverCache.invalidate(event.getUuid());
|
||||
this.lastServerCache.invalidate(event.getUuid());
|
||||
}
|
||||
|
||||
protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) {
|
||||
this.proxyCache.invalidate(event.getUuid());
|
||||
this.serverCache.invalidate(event.getUuid());
|
||||
this.ipCache.invalidate(event.getUuid());
|
||||
this.lastOnlineCache.invalidate(event.getUuid());
|
||||
}
|
||||
|
||||
protected void handlePubSubMessageEvent(IPubSubMessageEvent event) {
|
||||
// kick api
|
||||
if (event.getChannel().equals("redisbungee-kick")) {
|
||||
JSONObject data = new JSONObject(event.getMessage());
|
||||
String proxy = data.getString("proxy");
|
||||
if (proxy.equals(this.proxyId)) {
|
||||
return;
|
||||
}
|
||||
UUID uuid = UUID.fromString(data.getString("uuid"));
|
||||
String message = data.getString("message");
|
||||
plugin.handlePlatformKick(uuid, COMPONENT_SERIALIZER.deserialize(message));
|
||||
return;
|
||||
}
|
||||
if (event.getChannel().equals("redisbungee-serverchange")) {
|
||||
JSONObject data = new JSONObject(event.getMessage());
|
||||
String proxy = data.getString("proxy");
|
||||
if (proxy.equals(this.proxyId)) {
|
||||
return;
|
||||
}
|
||||
UUID uuid = UUID.fromString(data.getString("uuid"));
|
||||
String from = null;
|
||||
if (data.has("from")) from = data.getString("from");
|
||||
String to = data.getString("to");
|
||||
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
|
||||
return;
|
||||
}
|
||||
if (event.getChannel().equals("redisbungee-player-join")) {
|
||||
JSONObject data = new JSONObject(event.getMessage());
|
||||
String proxy = data.getString("proxy");
|
||||
if (proxy.equals(this.proxyId)) {
|
||||
return;
|
||||
}
|
||||
UUID uuid = UUID.fromString(data.getString("uuid"));
|
||||
plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
|
||||
return;
|
||||
}
|
||||
if (event.getChannel().equals("redisbungee-player-leave")) {
|
||||
JSONObject data = new JSONObject(event.getMessage());
|
||||
String proxy = data.getString("proxy");
|
||||
if (proxy.equals(this.proxyId)) {
|
||||
return;
|
||||
}
|
||||
UUID uuid = UUID.fromString(data.getString("uuid"));
|
||||
plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void playerChangedServer(UUID uuid, String from, String to) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("proxy", this.proxyId);
|
||||
data.put("uuid", uuid);
|
||||
data.put("from", from);
|
||||
data.put("to", to);
|
||||
plugin.proxyDataManager().sendChannelMessage("redisbungee-serverchange", data.toString());
|
||||
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
|
||||
handleServerChangeRedis(uuid, to);
|
||||
}
|
||||
|
||||
private final JSONComponentSerializer COMPONENT_SERIALIZER =JSONComponentSerializer.json();
|
||||
|
||||
public void kickPlayer(UUID uuid, Component message) {
|
||||
if (!plugin.handlePlatformKick(uuid, message)) { // handle locally before SENDING a message
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("proxy", this.proxyId);
|
||||
data.put("uuid", uuid);
|
||||
data.put("message", COMPONENT_SERIALIZER.serialize(message));
|
||||
plugin.proxyDataManager().sendChannelMessage("redisbungee-kick", data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleServerChangeRedis(UUID uuid, String server) {
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("server", server);
|
||||
data.put("last-server", server);
|
||||
unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", data);
|
||||
}
|
||||
|
||||
protected void addPlayer(final UUID uuid, final InetAddress inetAddress) {
|
||||
Map<String, String> redisData = new HashMap<>();
|
||||
redisData.put("last-online", String.valueOf(0));
|
||||
redisData.put("proxy", this.proxyId);
|
||||
redisData.put("ip", inetAddress.getHostAddress());
|
||||
unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", redisData);
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("proxy", this.proxyId);
|
||||
data.put("uuid", uuid);
|
||||
plugin.proxyDataManager().sendChannelMessage("redisbungee-player-join", data.toString());
|
||||
plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
|
||||
this.plugin.proxyDataManager().addPlayer(uuid);
|
||||
}
|
||||
|
||||
protected void removePlayer(UUID uuid) {
|
||||
unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "last-online", String.valueOf(System.currentTimeMillis()));
|
||||
unifiedJedis.hdel("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "server", "proxy", "ip");
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("proxy", this.proxyId);
|
||||
data.put("uuid", uuid);
|
||||
plugin.proxyDataManager().sendChannelMessage("redisbungee-player-leave", data.toString());
|
||||
plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
|
||||
this.plugin.proxyDataManager().removePlayer(uuid);
|
||||
}
|
||||
|
||||
|
||||
protected String getProxyFromRedis(UUID uuid) {
|
||||
return unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "proxy");
|
||||
}
|
||||
|
||||
protected String getServerFromRedis(UUID uuid) {
|
||||
return unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "server");
|
||||
}
|
||||
|
||||
protected String getLastServerFromRedis(UUID uuid) {
|
||||
return unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "last-server");
|
||||
}
|
||||
|
||||
protected InetAddress getIpAddressFromRedis(UUID uuid) {
|
||||
String ip = unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "ip");
|
||||
if (ip == null) return null;
|
||||
return InetAddresses.forString(ip);
|
||||
}
|
||||
|
||||
protected long getLastOnlineFromRedis(UUID uuid) {
|
||||
String unixString = unifiedJedis.hget("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", "last-online");
|
||||
if (unixString == null) return -1;
|
||||
return Long.parseLong(unixString);
|
||||
}
|
||||
|
||||
public String getLastServerFor(UUID uuid) {
|
||||
return this.lastServerCache.get(uuid);
|
||||
}
|
||||
public String getServerFor(UUID uuid) {
|
||||
return this.serverCache.get(uuid);
|
||||
}
|
||||
|
||||
public String getProxyFor(UUID uuid) {
|
||||
return this.proxyCache.get(uuid);
|
||||
}
|
||||
|
||||
public InetAddress getIpFor(UUID uuid) {
|
||||
return this.ipCache.get(uuid);
|
||||
}
|
||||
|
||||
public long getLastOnline(UUID uuid) {
|
||||
return this.lastOnlineCache.get(uuid);
|
||||
}
|
||||
|
||||
public Multimap<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) {
|
||||
responses.put(uuid, pipeline.hget("redis-bungee::" + networkId + "::player::" + uuid + "::data", "server"));
|
||||
}
|
||||
pipeline.sync();
|
||||
responses.forEach((uuid, response) -> builder.put(response.get(), uuid));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Multimap<String, UUID> clusterPipeline(ClusterPipeline pipeline) {
|
||||
HashMap<UUID, Response<String>> responses = new HashMap<>();
|
||||
for (UUID uuid : uuids) {
|
||||
responses.put(uuid, pipeline.hget("redis-bungee::" + networkId + "::player::" + uuid + "::data", "server"));
|
||||
}
|
||||
pipeline.sync();
|
||||
responses.forEach((uuid, response) -> builder.put(response.get(), uuid));
|
||||
return builder.build();
|
||||
}
|
||||
}.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.gson.AbstractPayloadSerializer;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.DeathPayloadSerializer;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.HeartbeatPayloadSerializer;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.PubSubPayloadSerializer;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
|
||||
import redis.clients.jedis.*;
|
||||
import redis.clients.jedis.params.XAddParams;
|
||||
import redis.clients.jedis.params.XReadParams;
|
||||
import redis.clients.jedis.resps.StreamEntry;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public abstract class ProxyDataManager implements Runnable {
|
||||
|
||||
private static final int MAX_ENTRIES = 10000;
|
||||
|
||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
|
||||
private final UnifiedJedis unifiedJedis;
|
||||
|
||||
// data:
|
||||
// Proxy id, heartbeat (unix epoch from instant), players as int
|
||||
private final ConcurrentHashMap<String, HeartbeatPayload.HeartbeatData> heartbeats = new ConcurrentHashMap<>();
|
||||
|
||||
private final String networkId;
|
||||
|
||||
private final String proxyId;
|
||||
|
||||
private final String STREAM_ID;
|
||||
|
||||
// This different from proxy id, just to detect if there is duplicate proxy using same proxy id
|
||||
private final UUID dataManagerUUID = UUID.randomUUID();
|
||||
|
||||
protected final RedisBungeePlugin<?> plugin;
|
||||
|
||||
private final Gson gson = new GsonBuilder().registerTypeAdapter(AbstractPayload.class, new AbstractPayloadSerializer()).registerTypeAdapter(HeartbeatPayload.class, new HeartbeatPayloadSerializer()).registerTypeAdapter(DeathPayload.class, new DeathPayloadSerializer()).registerTypeAdapter(PubSubPayload.class, new PubSubPayloadSerializer()).registerTypeAdapter(RunCommandPayload.class, new RunCommandPayloadSerializer()).create();
|
||||
|
||||
public ProxyDataManager(RedisBungeePlugin<?> plugin) {
|
||||
this.plugin = plugin;
|
||||
this.proxyId = this.plugin.configuration().getProxyId();
|
||||
this.unifiedJedis = plugin.getSummoner().obtainResource();
|
||||
this.destroyProxyMembers();
|
||||
this.networkId = plugin.configuration().networkId();
|
||||
this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream";
|
||||
}
|
||||
|
||||
public abstract Set<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 List<String> proxiesIds() {
|
||||
return Collections.list(this.heartbeats.keys());
|
||||
}
|
||||
|
||||
public synchronized void sendCommandTo(String proxyToRun, String command) {
|
||||
if (isClosed()) return;
|
||||
publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command));
|
||||
}
|
||||
|
||||
public synchronized void sendChannelMessage(String channel, String message) {
|
||||
if (isClosed()) return;
|
||||
this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message));
|
||||
publishPayload(new PubSubPayload(this.proxyId, channel, message));
|
||||
}
|
||||
|
||||
// call every 1 second
|
||||
public synchronized void publishHeartbeat() {
|
||||
if (isClosed()) return;
|
||||
HeartbeatPayload.HeartbeatData heartbeatData = new HeartbeatPayload.HeartbeatData(Instant.now().getEpochSecond(), this.getLocalOnlineUUIDs().size());
|
||||
this.heartbeats.put(this.proxyId(), heartbeatData);
|
||||
publishPayload(new HeartbeatPayload(this.proxyId, heartbeatData));
|
||||
}
|
||||
|
||||
public Set<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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
plugin.logInfo("Player set has been corrected!");
|
||||
}
|
||||
|
||||
|
||||
// handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~30 seconds
|
||||
final Set<String> deadProxies = new HashSet<>();
|
||||
for (Map.Entry<String, HeartbeatPayload.HeartbeatData> stringHeartbeatDataEntry : this.heartbeats.entrySet()) {
|
||||
String id = stringHeartbeatDataEntry.getKey();
|
||||
long heartbeat = stringHeartbeatDataEntry.getValue().heartbeat();
|
||||
if (Instant.now().getEpochSecond() - heartbeat > RedisUtil.PROXY_TIMEOUT) {
|
||||
deadProxies.add(id);
|
||||
cleanProxy(id);
|
||||
}
|
||||
}
|
||||
try {
|
||||
new RedisPipelineTask<Void>(plugin) {
|
||||
@Override
|
||||
public Void doPooledPipeline(Pipeline pipeline) {
|
||||
for (String deadProxy : deadProxies) {
|
||||
pipeline.del("redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players");
|
||||
}
|
||||
pipeline.sync();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PubSubListener implements Runnable {
|
||||
private JedisPubSubHandler jpsh;
|
||||
private final Set<String> addedChannels = new HashSet<String>();
|
||||
|
||||
private final RedisBungeePlugin<?> plugin;
|
||||
|
||||
public PubSubListener(RedisBungeePlugin<?> plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
RedisTask<Void> subTask = new RedisTask<Void>(plugin) {
|
||||
@Override
|
||||
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
jpsh = new JedisPubSubHandler(plugin);
|
||||
addedChannels.add("redisbungee-" + plugin.getConfiguration().getProxyId());
|
||||
addedChannels.add("redisbungee-allservers");
|
||||
addedChannels.add("redisbungee-data");
|
||||
unifiedJedis.subscribe(jpsh, addedChannels.toArray(new String[0]));
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
subTask.execute();
|
||||
} catch (Exception e) {
|
||||
plugin.logWarn("PubSub error, attempting to recover in 5 secs.");
|
||||
plugin.executeAsyncAfter(this, TimeUnit.SECONDS, 5);
|
||||
}
|
||||
}
|
||||
|
||||
public void addChannel(String... channel) {
|
||||
addedChannels.addAll(Arrays.asList(channel));
|
||||
jpsh.subscribe(channel);
|
||||
}
|
||||
|
||||
public void removeChannel(String... channel) {
|
||||
Arrays.asList(channel).forEach(addedChannels::remove);
|
||||
jpsh.unsubscribe(channel);
|
||||
}
|
||||
|
||||
public void poison() {
|
||||
addedChannels.clear();
|
||||
jpsh.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,28 +10,18 @@
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
|
||||
/**
|
||||
* This Class has all internal methods needed by every redis bungee plugin, and it can be used to implement another platforms than bungeecord or another forks of RedisBungee
|
||||
@@ -51,225 +41,56 @@ public interface RedisBungeePlugin<P> extends EventsPlatform {
|
||||
|
||||
}
|
||||
|
||||
Summoner<?> getSummoner();
|
||||
|
||||
RedisBungeeConfiguration getConfiguration();
|
||||
|
||||
int getCount();
|
||||
|
||||
default int getCurrentCount() {
|
||||
return new RedisTask<Long>(this) {
|
||||
@Override
|
||||
public Long unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
long total = 0;
|
||||
long redisTime = getRedisTime(unifiedJedis);
|
||||
Map<String, String> heartBeats = unifiedJedis.hgetAll("heartbeats");
|
||||
for (Map.Entry<String, String> stringStringEntry : heartBeats.entrySet()) {
|
||||
String k = stringStringEntry.getKey();
|
||||
String v = stringStringEntry.getValue();
|
||||
|
||||
long heartbeatTime = Long.parseLong(v);
|
||||
if (heartbeatTime + RedisUtil.PROXY_TIMEOUT >= redisTime) {
|
||||
total = total + unifiedJedis.scard("proxy:" + k + ":usersOnline");
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}.execute().intValue();
|
||||
}
|
||||
|
||||
Set<String> getLocalPlayersAsUuidStrings();
|
||||
|
||||
AbstractDataManager<P, ?, ?, ?> getDataManager();
|
||||
|
||||
default Set<UUID> getPlayers() {
|
||||
return new RedisTask<Set<UUID>>(this) {
|
||||
@Override
|
||||
public Set<UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
|
||||
try {
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (String i : getProxiesIds()) {
|
||||
keys.add("proxy:" + i + ":usersOnline");
|
||||
}
|
||||
if (!keys.isEmpty()) {
|
||||
Set<String> users = unifiedJedis.sunion(keys.toArray(new String[0]));
|
||||
if (users != null && !users.isEmpty()) {
|
||||
for (String user : users) {
|
||||
try {
|
||||
setBuilder = setBuilder.add(UUID.fromString(user));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
logFatal("Unable to get connection from pool - did your Redis server go away?");
|
||||
throw new RuntimeException("Unable to get all players online", e);
|
||||
}
|
||||
return setBuilder.build();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
AbstractRedisBungeeAPI getAbstractRedisBungeeApi();
|
||||
|
||||
UUIDTranslator getUuidTranslator();
|
||||
|
||||
Multimap<String, UUID> serverToPlayersCache();
|
||||
|
||||
default Multimap<String, UUID> serversToPlayers() {
|
||||
return new RedisTask<Multimap<String, UUID>>(this) {
|
||||
@Override
|
||||
public Multimap<String, UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
|
||||
for (String serverId : getProxiesIds()) {
|
||||
Set<String> players = unifiedJedis.smembers("proxy:" + serverId + ":usersOnline");
|
||||
for (String player : players) {
|
||||
String playerServer = unifiedJedis.hget("player:" + player, "server");
|
||||
if (playerServer == null) {
|
||||
continue;
|
||||
}
|
||||
builder.put(playerServer, UUID.fromString(player));
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
default Set<UUID> getPlayersOnProxy(String proxyId) {
|
||||
checkArgument(getProxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID");
|
||||
return new RedisTask<Set<UUID>>(this) {
|
||||
@Override
|
||||
public Set<UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
Set<String> users = unifiedJedis.smembers("proxy:" + proxyId + ":usersOnline");
|
||||
ImmutableSet.Builder<UUID> builder = ImmutableSet.builder();
|
||||
for (String user : users) {
|
||||
builder.add(UUID.fromString(user));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
default void sendProxyCommand(String proxyId, String command) {
|
||||
checkArgument(getProxiesIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid");
|
||||
sendChannelMessage("redisbungee-" + proxyId, command);
|
||||
}
|
||||
|
||||
List<String> getProxiesIds();
|
||||
|
||||
default List<String> getCurrentProxiesIds(boolean lagged) {
|
||||
return new RedisTask<List<String>>(this) {
|
||||
@Override
|
||||
public List<String> unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
try {
|
||||
long time = getRedisTime(unifiedJedis);
|
||||
ImmutableList.Builder<String> servers = ImmutableList.builder();
|
||||
Map<String, String> heartbeats = unifiedJedis.hgetAll("heartbeats");
|
||||
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
|
||||
try {
|
||||
long stamp = Long.parseLong(entry.getValue());
|
||||
if (lagged ? time >= stamp + RedisUtil.PROXY_TIMEOUT : time <= stamp + RedisUtil.PROXY_TIMEOUT) {
|
||||
servers.add(entry.getKey());
|
||||
} else if (time > stamp + RedisUtil.PROXY_TIMEOUT) {
|
||||
logWarn(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat.");
|
||||
unifiedJedis.hdel("heartbeats", entry.getKey());
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return servers.build();
|
||||
} catch (JedisConnectionException e) {
|
||||
logFatal("Unable to fetch server IDs");
|
||||
e.printStackTrace();
|
||||
return Collections.singletonList(getConfiguration().getProxyId());
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
PubSubListener getPubSubListener();
|
||||
|
||||
default void sendChannelMessage(String channel, String message) {
|
||||
new RedisTask<Void>(this) {
|
||||
@Override
|
||||
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
try {
|
||||
unifiedJedis.publish(channel, message);
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
logFatal("Unable to get connection from pool - did your Redis server go away?");
|
||||
throw new RuntimeException("Unable to publish channel message", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
void executeAsync(Runnable runnable);
|
||||
|
||||
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
|
||||
|
||||
boolean isOnlineMode();
|
||||
|
||||
void logInfo(String msg);
|
||||
|
||||
void logInfo(String format, Object... object);
|
||||
|
||||
void logWarn(String msg);
|
||||
|
||||
void logWarn(String format, Object... object);
|
||||
|
||||
void logFatal(String msg);
|
||||
|
||||
void logFatal(String format, Throwable throwable);
|
||||
|
||||
RedisBungeeConfiguration configuration();
|
||||
|
||||
LangConfiguration langConfiguration();
|
||||
|
||||
Summoner<?> getSummoner();
|
||||
|
||||
RedisBungeeMode getRedisBungeeMode();
|
||||
|
||||
AbstractRedisBungeeAPI getAbstractRedisBungeeApi();
|
||||
|
||||
ProxyDataManager proxyDataManager();
|
||||
|
||||
PlayerDataManager<P, ?, ?, ?, ?, ?, ?> playerDataManager();
|
||||
|
||||
UUIDTranslator getUuidTranslator();
|
||||
|
||||
boolean isOnlineMode();
|
||||
|
||||
P getPlayer(UUID uuid);
|
||||
|
||||
P getPlayer(String name);
|
||||
|
||||
UUID getPlayerUUID(String player);
|
||||
|
||||
|
||||
String getPlayerName(UUID player);
|
||||
|
||||
boolean handlePlatformKick(UUID uuid, Component message);
|
||||
|
||||
String getPlayerServerName(P player);
|
||||
|
||||
boolean isPlayerOnAServer(P player);
|
||||
|
||||
InetAddress getPlayerIp(P player);
|
||||
|
||||
default void sendProxyCommand(String cmd) {
|
||||
sendProxyCommand(getConfiguration().getProxyId(), cmd);
|
||||
}
|
||||
void executeAsync(Runnable runnable);
|
||||
|
||||
default Long getRedisTime(UnifiedJedis unifiedJedis) {
|
||||
List<Object> data = (List<Object>) unifiedJedis.sendCommand(Protocol.Command.TIME);
|
||||
List<String> times = new ArrayList<>();
|
||||
data.forEach((o) -> times.add(new String((byte[])o)));
|
||||
return getRedisTime(times);
|
||||
}
|
||||
default long getRedisTime(List<String> timeRes) {
|
||||
return Long.parseLong(timeRes.get(0));
|
||||
}
|
||||
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
|
||||
|
||||
default void kickPlayer(UUID playerUniqueId, String message) {
|
||||
// first handle on origin proxy if player not found publish the payload
|
||||
if (!getDataManager().handleKick(playerUniqueId, message)) {
|
||||
new RedisTask<Void>(this) {
|
||||
@Override
|
||||
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
PayloadUtils.kickPlayerPayload(playerUniqueId, message, unifiedJedis);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
default void kickPlayer(String playerName, String message) {
|
||||
// fetch the uuid from name
|
||||
UUID playerUUID = getUuidTranslator().getTranslatedUuid(playerName, true);
|
||||
kickPlayer(playerUUID, message);
|
||||
}
|
||||
|
||||
RedisBungeeMode getRedisBungeeMode();
|
||||
|
||||
void updateProxiesIds();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.config;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This language support implementation is temporarily
|
||||
* until I come up with better system but for now we will use Maps instead :/
|
||||
* Todo: possible usage of adventure api
|
||||
*/
|
||||
public class LangConfiguration {
|
||||
|
||||
private interface RegistrableMessages {
|
||||
|
||||
void register(String id, Locale locale, String miniMessage);
|
||||
|
||||
void test(Locale locale);
|
||||
|
||||
default void throwError(Locale locale, String where) {
|
||||
throw new IllegalStateException("Language system in `" + where + "` found missing entries for " + locale.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Messages implements RegistrableMessages{
|
||||
|
||||
private final Map<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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,42 +11,39 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.api.config;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.net.InetAddresses;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class RedisBungeeConfiguration {
|
||||
|
||||
public enum MessageType {
|
||||
LOGGED_IN_OTHER_LOCATION,
|
||||
ALREADY_LOGGED_IN
|
||||
}
|
||||
|
||||
private final ImmutableMap<MessageType, String> messages;
|
||||
public static final int CONFIG_VERSION = 1;
|
||||
private final String proxyId;
|
||||
private final List<InetAddress> exemptAddresses;
|
||||
private final boolean registerLegacyCommands;
|
||||
private final boolean overrideBungeeCommands;
|
||||
private final boolean kickWhenOnline;
|
||||
|
||||
private final boolean restoreOldKickBehavior;
|
||||
private final boolean handleReconnectToLastServer;
|
||||
private final boolean handleMotd;
|
||||
|
||||
public RedisBungeeConfiguration(String proxyId, List<String> exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, ImmutableMap<MessageType, String> messages, boolean restoreOldKickBehavior) {
|
||||
private final CommandsConfiguration commandsConfiguration;
|
||||
private final String networkId;
|
||||
|
||||
|
||||
public RedisBungeeConfiguration(String networkId, String proxyId, List<String> exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, CommandsConfiguration commandsConfiguration) {
|
||||
this.proxyId = proxyId;
|
||||
this.messages = messages;
|
||||
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
|
||||
for (String s : exemptAddresses) {
|
||||
addressBuilder.add(InetAddresses.forString(s));
|
||||
}
|
||||
this.exemptAddresses = addressBuilder.build();
|
||||
this.registerLegacyCommands = registerLegacyCommands;
|
||||
this.overrideBungeeCommands = overrideBungeeCommands;
|
||||
this.restoreOldKickBehavior = restoreOldKickBehavior;
|
||||
this.kickWhenOnline = kickWhenOnline;
|
||||
this.handleReconnectToLastServer = handleReconnectToLastServer;
|
||||
this.handleMotd = handleMotd;
|
||||
this.commandsConfiguration = commandsConfiguration;
|
||||
this.networkId = networkId;
|
||||
}
|
||||
|
||||
public String getProxyId() {
|
||||
return proxyId;
|
||||
}
|
||||
@@ -55,19 +52,37 @@ public class RedisBungeeConfiguration {
|
||||
return exemptAddresses;
|
||||
}
|
||||
|
||||
public boolean doRegisterLegacyCommands() {
|
||||
return registerLegacyCommands;
|
||||
public boolean kickWhenOnline() {
|
||||
return kickWhenOnline;
|
||||
}
|
||||
|
||||
public boolean doOverrideBungeeCommands() {
|
||||
return overrideBungeeCommands;
|
||||
public boolean handleMotd() {
|
||||
return this.handleMotd;
|
||||
}
|
||||
|
||||
public ImmutableMap<MessageType, String> getMessages() {
|
||||
return messages;
|
||||
public boolean handleReconnectToLastServer() {
|
||||
return this.handleReconnectToLastServer;
|
||||
}
|
||||
|
||||
public boolean restoreOldKickBehavior() {
|
||||
return restoreOldKickBehavior;
|
||||
public record CommandsConfiguration(boolean redisbungeeEnabled, boolean redisbungeeLegacyEnabled,
|
||||
@Nullable LegacySubCommandsConfiguration legacySubCommandsConfiguration) {
|
||||
|
||||
}
|
||||
|
||||
public record LegacySubCommandsConfiguration(boolean findEnabled, boolean glistEnabled, boolean ipEnabled,
|
||||
boolean lastseenEnabled, boolean plistEnabled, boolean pproxyEnabled,
|
||||
boolean sendtoallEnabled, boolean serveridEnabled,
|
||||
boolean serveridsEnabled, boolean installFind, boolean installGlist, boolean installIp,
|
||||
boolean installLastseen, boolean installPlist, boolean installPproxy,
|
||||
boolean installSendtoall, boolean installServerid,
|
||||
boolean installServerids) {
|
||||
}
|
||||
|
||||
public CommandsConfiguration commandsConfiguration() {
|
||||
return commandsConfiguration;
|
||||
}
|
||||
|
||||
public String networkId() {
|
||||
return networkId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.config;
|
||||
package com.imaginarycode.minecraft.redisbungee.api.config.loaders;
|
||||
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
|
||||
@@ -26,35 +26,29 @@ import redis.clients.jedis.*;
|
||||
import redis.clients.jedis.providers.ClusterConnectionProvider;
|
||||
import redis.clients.jedis.providers.PooledConnectionProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
|
||||
public interface ConfigLoader {
|
||||
public interface ConfigLoader extends GenericConfigLoader {
|
||||
|
||||
default void loadConfig(RedisBungeePlugin<?> plugin, File dataFolder) throws IOException {
|
||||
loadConfig(plugin, dataFolder.toPath());
|
||||
}
|
||||
int CONFIG_VERSION = 2;
|
||||
|
||||
default void loadConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
|
||||
Path configFile = createConfigFile(dataFolder);
|
||||
Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml");
|
||||
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build();
|
||||
ConfigurationNode node = yamlConfigurationFileLoader.load();
|
||||
if (node.getNode("config-version").getInt(0) != RedisBungeeConfiguration.CONFIG_VERSION) {
|
||||
handleOldConfig(dataFolder);
|
||||
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
|
||||
handleOldConfig(dataFolder, "config.yml", "config.yml");
|
||||
node = yamlConfigurationFileLoader.load();
|
||||
}
|
||||
final boolean useSSL = node.getNode("useSSL").getBoolean(false);
|
||||
final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false);
|
||||
final boolean registerLegacyCommands = node.getNode("register-legacy-commands").getBoolean(false);
|
||||
final boolean restoreOldKickBehavior = node.getNode("disable-kick-when-online").getBoolean(false);
|
||||
final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true);
|
||||
String redisPassword = node.getNode("redis-password").getString("");
|
||||
String redisUsername = node.getNode("redis-username").getString("");
|
||||
String proxyId = node.getNode("proxy-id").getString("test-1");
|
||||
String networkId = node.getNode("network-id").getString("main");
|
||||
String proxyId = node.getNode("proxy-id").getString("proxy-1");
|
||||
|
||||
final int maxConnections = node.getNode("max-redis-connections").getInt(10);
|
||||
List<String> exemptAddresses;
|
||||
try {
|
||||
@@ -71,10 +65,19 @@ public interface ConfigLoader {
|
||||
if ((redisUsername.isEmpty() || redisUsername.equals("none"))) {
|
||||
redisUsername = null;
|
||||
}
|
||||
|
||||
if (useSSL) {
|
||||
plugin.logInfo("Using ssl");
|
||||
// env var
|
||||
String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID");
|
||||
if (proxyIdFromEnv != null) {
|
||||
plugin.logInfo("Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID", proxyId, proxyIdFromEnv);
|
||||
proxyId = proxyIdFromEnv;
|
||||
}
|
||||
|
||||
String networkIdFromEnv = System.getenv("REDISBUNGEE_NETWORK_ID");
|
||||
if (networkIdFromEnv != null) {
|
||||
plugin.logInfo("Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID", networkId, networkIdFromEnv);
|
||||
networkId = networkIdFromEnv;
|
||||
}
|
||||
|
||||
// Configuration sanity checks.
|
||||
if (proxyId == null || proxyId.isEmpty()) {
|
||||
String genId = UUID.randomUUID().toString();
|
||||
@@ -86,9 +89,62 @@ public interface ConfigLoader {
|
||||
} else {
|
||||
plugin.logInfo("Loaded proxy id " + proxyId);
|
||||
}
|
||||
RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, getMessagesFromPath(createMessagesFile(dataFolder)), restoreOldKickBehavior);
|
||||
|
||||
if (networkId.isEmpty()) {
|
||||
networkId = "main";
|
||||
plugin.logWarn("network id was empty and replaced with 'main'");
|
||||
}
|
||||
|
||||
plugin.logInfo("Loaded network id " + networkId);
|
||||
|
||||
|
||||
|
||||
boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean();
|
||||
boolean handleMotd = node.getNode("handle-motd").getBoolean(true);
|
||||
plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer);
|
||||
plugin.logInfo("handle motd: {}", handleMotd);
|
||||
|
||||
|
||||
// commands
|
||||
boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(true);
|
||||
boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(false);
|
||||
|
||||
boolean glistEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled").getBoolean(false);
|
||||
boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(false);
|
||||
boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(false);
|
||||
boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(false);
|
||||
boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(false);
|
||||
boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(false);
|
||||
boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(false);
|
||||
boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(false);
|
||||
boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(false);
|
||||
|
||||
boolean installGlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install").getBoolean(false);
|
||||
boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(false);
|
||||
boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(false);
|
||||
boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(false);
|
||||
boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(false);
|
||||
boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(false);
|
||||
boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(false);
|
||||
boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(false);
|
||||
boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(false);
|
||||
|
||||
|
||||
RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(networkId, proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, new RedisBungeeConfiguration.CommandsConfiguration(
|
||||
redisBungeeEnabled, redisBungeeLegacyEnabled,
|
||||
new RedisBungeeConfiguration.LegacySubCommandsConfiguration(
|
||||
findEnabled, glistEnabled, ipEnabled,
|
||||
lastseenEnabled, pListEnabled, pproxyEnabled,
|
||||
sendToAllEnabled, serverIdEnabled, serverIdsEnabled,
|
||||
installFind, installGlist, installIp,
|
||||
installLastseen, installPlist, installPproxy,
|
||||
installSendToAll, installServerid, installServerIds)
|
||||
));
|
||||
Summoner<?> summoner;
|
||||
RedisBungeeMode redisBungeeMode;
|
||||
if (useSSL) {
|
||||
plugin.logInfo("Using ssl");
|
||||
}
|
||||
if (node.getNode("cluster-mode-enabled").getBoolean(false)) {
|
||||
plugin.logInfo("RedisBungee MODE: CLUSTER");
|
||||
Set<HostAndPort> hostAndPortSet = new HashSet<>();
|
||||
@@ -115,7 +171,7 @@ public interface ConfigLoader {
|
||||
throw new RuntimeException("No redis server specified");
|
||||
}
|
||||
JedisPool jedisPool = null;
|
||||
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(true)) {
|
||||
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) {
|
||||
JedisPoolConfig config = new JedisPoolConfig();
|
||||
config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3));
|
||||
config.setBlockWhenExhausted(true);
|
||||
@@ -134,53 +190,5 @@ public interface ConfigLoader {
|
||||
|
||||
void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode);
|
||||
|
||||
default ImmutableMap<RedisBungeeConfiguration.MessageType, String> getMessagesFromPath(Path path) throws IOException {
|
||||
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(path).build();
|
||||
ConfigurationNode node = yamlConfigurationFileLoader.load();
|
||||
HashMap<RedisBungeeConfiguration.MessageType, String> messages = new HashMap<>();
|
||||
messages.put(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION, node.getNode("logged-in-other-location").getString("§cLogged in from another location."));
|
||||
messages.put(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN, node.getNode("already-logged-in").getString("§cYou are already logged in!"));
|
||||
return ImmutableMap.copyOf(messages);
|
||||
}
|
||||
|
||||
default Path createMessagesFile(Path dataFolder) throws IOException {
|
||||
if (Files.notExists(dataFolder)) {
|
||||
Files.createDirectory(dataFolder);
|
||||
}
|
||||
Path file = dataFolder.resolve("messages.yml");
|
||||
if (Files.notExists(file)) {
|
||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream("messages.yml")) {
|
||||
Files.createFile(file);
|
||||
assert in != null;
|
||||
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
default Path createConfigFile(Path dataFolder) throws IOException {
|
||||
if (Files.notExists(dataFolder)) {
|
||||
Files.createDirectory(dataFolder);
|
||||
}
|
||||
Path file = dataFolder.resolve("config.yml");
|
||||
if (Files.notExists(file)) {
|
||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
|
||||
Files.createFile(file);
|
||||
assert in != null;
|
||||
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
default void handleOldConfig(Path dataFolder) throws IOException {
|
||||
Path oldConfigFolder = dataFolder.resolve("old_config");
|
||||
if (Files.notExists(oldConfigFolder)) {
|
||||
Files.createDirectory(oldConfigFolder);
|
||||
}
|
||||
Path oldConfigPath = dataFolder.resolve("config.yml");
|
||||
Files.move(oldConfigPath, oldConfigFolder.resolve(UUID.randomUUID() + "_config.yml"));
|
||||
createConfigFile(dataFolder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.config.loaders;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.Instant;
|
||||
|
||||
|
||||
public interface GenericConfigLoader {
|
||||
|
||||
// CHANGES on every reboot
|
||||
String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond();
|
||||
|
||||
default Path createConfigFile(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
|
||||
if (Files.notExists(dataFolder)) {
|
||||
Files.createDirectory(dataFolder);
|
||||
}
|
||||
Path file = dataFolder.resolve(configFile);
|
||||
if (Files.notExists(file) && defaultResourceID != null) {
|
||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream(defaultResourceID)) {
|
||||
Files.createFile(file);
|
||||
assert in != null;
|
||||
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
default void handleOldConfig(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
|
||||
Path oldConfigFolder = dataFolder.resolve("old_config");
|
||||
if (Files.notExists(oldConfigFolder)) {
|
||||
Files.createDirectory(oldConfigFolder);
|
||||
}
|
||||
Path randomStoreConfigDirectory = oldConfigFolder.resolve(RANDOM_OLD);
|
||||
if (Files.notExists(randomStoreConfigDirectory)) {
|
||||
Files.createDirectory(randomStoreConfigDirectory);
|
||||
}
|
||||
Path oldConfigPath = dataFolder.resolve(configFile);
|
||||
|
||||
Files.move(oldConfigPath, randomStoreConfigDirectory.resolve(configFile));
|
||||
createConfigFile(dataFolder, configFile, defaultResourceID);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.config.loaders;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
|
||||
public interface LangConfigLoader extends GenericConfigLoader {
|
||||
|
||||
int CONFIG_VERSION = 1;
|
||||
|
||||
default void loadLangConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
|
||||
Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml");
|
||||
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build();
|
||||
ConfigurationNode node = yamlConfigurationFileLoader.load();
|
||||
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
|
||||
handleOldConfig(dataFolder, "lang.yml", "lang.yml");
|
||||
node = yamlConfigurationFileLoader.load();
|
||||
}
|
||||
// MINI message serializer
|
||||
MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
Component prefix = miniMessage.deserialize(node.getNode("prefix").getString("<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));
|
||||
}
|
||||
|
||||
|
||||
void onLangConfigLoad(LangConfiguration langConfiguration);
|
||||
|
||||
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import java.util.UUID;
|
||||
*
|
||||
* @author Ham1255
|
||||
* @since 0.7.0
|
||||
*
|
||||
*/
|
||||
public interface EventsPlatform {
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads;
|
||||
|
||||
public abstract class AbstractPayload {
|
||||
|
||||
private final String senderProxy;
|
||||
|
||||
public AbstractPayload(String proxyId) {
|
||||
this.senderProxy = proxyId;
|
||||
}
|
||||
|
||||
public AbstractPayload(String senderProxy, String className) {
|
||||
this.senderProxy = senderProxy;
|
||||
}
|
||||
|
||||
public String senderProxy() {
|
||||
return senderProxy;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.gson;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class AbstractPayloadSerializer implements JsonSerializer<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 JsonElement serialize(AbstractPayload src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
|
||||
|
||||
public class DeathPayload extends AbstractPayload {
|
||||
public DeathPayload(String proxyId) {
|
||||
super(proxyId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
|
||||
|
||||
public class HeartbeatPayload extends AbstractPayload {
|
||||
|
||||
public record HeartbeatData(long heartbeat, int players) {
|
||||
|
||||
}
|
||||
|
||||
private final HeartbeatData data;
|
||||
|
||||
public HeartbeatPayload(String proxyId, HeartbeatData data) {
|
||||
super(proxyId);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public HeartbeatData data() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
|
||||
|
||||
public class PubSubPayload extends AbstractPayload {
|
||||
|
||||
private final String channel;
|
||||
private final String message;
|
||||
|
||||
|
||||
public PubSubPayload(String proxyId, String channel, String message) {
|
||||
super(proxyId);
|
||||
this.channel = channel;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
|
||||
|
||||
public class RunCommandPayload extends AbstractPayload {
|
||||
|
||||
|
||||
private final String proxyToRun;
|
||||
|
||||
private final String command;
|
||||
|
||||
|
||||
public RunCommandPayload(String proxyId, String proxyToRun, String command) {
|
||||
super(proxyId);
|
||||
this.proxyToRun = proxyToRun;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public String proxyToRun() {
|
||||
return proxyToRun;
|
||||
}
|
||||
|
||||
public String command() {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class DeathPayloadSerializer implements JsonSerializer<DeathPayload>, JsonDeserializer<DeathPayload> {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
|
||||
@Override
|
||||
public DeathPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
String senderProxy = jsonObject.get("proxy").getAsString();
|
||||
return new DeathPayload(senderProxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class HeartbeatPayloadSerializer implements JsonSerializer<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 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class PubSubPayloadSerializer implements JsonSerializer<PubSubPayload>, JsonDeserializer<PubSubPayload> {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
|
||||
@Override
|
||||
public PubSubPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
String senderProxy = jsonObject.get("proxy").getAsString();
|
||||
String channel = jsonObject.get("channel").getAsString();
|
||||
String message = jsonObject.get("message").getAsString();
|
||||
return new PubSubPayload(senderProxy, channel, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(PubSubPayload src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
|
||||
jsonObject.add("channel", new JsonPrimitive(src.channel()));
|
||||
jsonObject.add("message", context.serialize(src.message()));
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class RunCommandPayloadSerializer implements JsonSerializer<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 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;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
public class JedisClusterSummoner implements Summoner<JedisCluster> {
|
||||
public final ClusterConnectionProvider clusterConnectionProvider;
|
||||
private final ClusterConnectionProvider clusterConnectionProvider;
|
||||
|
||||
public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) {
|
||||
this.clusterConnectionProvider = clusterConnectionProvider;
|
||||
@@ -35,6 +35,8 @@ public class JedisClusterSummoner implements Summoner<JedisCluster> {
|
||||
|
||||
@Override
|
||||
public JedisCluster obtainResource() {
|
||||
return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(30000));
|
||||
return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.api.summoners;
|
||||
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import redis.clients.jedis.JedisPooled;
|
||||
import redis.clients.jedis.providers.ClusterConnectionProvider;
|
||||
import redis.clients.jedis.providers.PooledConnectionProvider;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.summoners;
|
||||
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
|
||||
@@ -18,9 +20,8 @@ import java.io.Closeable;
|
||||
*
|
||||
* @author Ham1255
|
||||
* @since 0.7.0
|
||||
*
|
||||
*/
|
||||
public interface Summoner<P> extends Closeable {
|
||||
public interface Summoner<P extends UnifiedJedis> extends Closeable {
|
||||
|
||||
P obtainResource();
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.tasks;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class HeartbeatTask extends RedisTask<Void>{
|
||||
|
||||
public final static TimeUnit REPEAT_INTERVAL_TIME_UNIT = TimeUnit.SECONDS;
|
||||
public final static int INTERVAL = 1;
|
||||
private final AtomicInteger globalPlayerCount;
|
||||
|
||||
public HeartbeatTask(RedisBungeePlugin<?> plugin, AtomicInteger globalPlayerCount) {
|
||||
super(plugin);
|
||||
this.globalPlayerCount = globalPlayerCount;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
try {
|
||||
long redisTime = plugin.getRedisTime(unifiedJedis);
|
||||
unifiedJedis.hset("heartbeats", plugin.getConfiguration().getProxyId(), String.valueOf(redisTime));
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
plugin.logFatal("Unable to update heartbeat - did your Redis server go away?");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
plugin.updateProxiesIds();
|
||||
globalPlayerCount.set(plugin.getCurrentCount());
|
||||
} catch (Throwable e) {
|
||||
plugin.logFatal("Unable to update data - did your Redis server go away?");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.tasks;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class InitialUtils {
|
||||
|
||||
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
|
||||
new RedisTask<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 3.0 RedisBungee requires a newer version of Redis.");
|
||||
throw new RuntimeException("Unsupported Redis version detected");
|
||||
}
|
||||
long uuidCacheSize = unifiedJedis.hlen("uuid-cache");
|
||||
if (uuidCacheSize > 750000) {
|
||||
plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
|
||||
public static void checkIfRecovering(RedisBungeePlugin<?> plugin, Path dataFolder) {
|
||||
new RedisTask<Void>(plugin) {
|
||||
@Override
|
||||
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
Path crashFile = dataFolder.resolve("restarted_from_crash.txt");
|
||||
if (Files.exists(crashFile)) {
|
||||
try {
|
||||
Files.delete(crashFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
plugin.logInfo("crash file was deleted continuing RedisBungee startup ");
|
||||
} else if (unifiedJedis.hexists("heartbeats", plugin.getConfiguration().getProxyId())) {
|
||||
try {
|
||||
long value = Long.parseLong(unifiedJedis.hget("heartbeats", plugin.getConfiguration().getProxyId()));
|
||||
long redisTime = plugin.getRedisTime(unifiedJedis);
|
||||
|
||||
if (redisTime < value + RedisUtil.PROXY_TIMEOUT) {
|
||||
logImposter(plugin);
|
||||
throw new RuntimeException("Possible impostor instance!");
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private static void logImposter(RedisBungeePlugin<?> plugin) {
|
||||
plugin.logFatal("You have launched a possible impostor Velocity / Bungeecord instance. Another instance is already running.");
|
||||
plugin.logFatal("For data consistency reasons, RedisBungee will now disable itself.");
|
||||
plugin.logFatal("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.tasks;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class IntegrityCheckTask extends RedisTask<Void> {
|
||||
|
||||
public static int INTERVAL = 30;
|
||||
public static TimeUnit TIMEUNIT = TimeUnit.SECONDS;
|
||||
|
||||
|
||||
public IntegrityCheckTask(RedisBungeePlugin<?> plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
try {
|
||||
Set<String> players = plugin.getLocalPlayersAsUuidStrings();
|
||||
Set<String> playersInRedis = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline");
|
||||
List<String> lagged = plugin.getCurrentProxiesIds(true);
|
||||
|
||||
// Clean up lagged players.
|
||||
for (String s : lagged) {
|
||||
Set<String> laggedPlayers = unifiedJedis.smembers("proxy:" + s + ":usersOnline");
|
||||
unifiedJedis.del("proxy:" + s + ":usersOnline");
|
||||
if (!laggedPlayers.isEmpty()) {
|
||||
plugin.logInfo("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)...");
|
||||
for (String laggedPlayer : laggedPlayers) {
|
||||
PlayerUtils.cleanUpPlayer(laggedPlayer, unifiedJedis, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> absentLocally = new HashSet<>(playersInRedis);
|
||||
absentLocally.removeAll(players);
|
||||
Set<String> absentInRedis = new HashSet<>(players);
|
||||
absentInRedis.removeAll(playersInRedis);
|
||||
|
||||
for (String member : absentLocally) {
|
||||
boolean found = false;
|
||||
for (String proxyId : plugin.getProxiesIds()) {
|
||||
if (proxyId.equals(plugin.getConfiguration().getProxyId())) continue;
|
||||
if (unifiedJedis.sismember("proxy:" + proxyId + ":usersOnline", member)) {
|
||||
// Just clean up the set.
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
PlayerUtils.cleanUpPlayer(member, unifiedJedis, false);
|
||||
plugin.logWarn("Player found in set that was not found locally and globally: " + member);
|
||||
} else {
|
||||
unifiedJedis.srem("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline", member);
|
||||
plugin.logWarn("Player found in set that was not found locally, but is on another proxy: " + member);
|
||||
}
|
||||
}
|
||||
// due unifiedJedis does not support pipelined.
|
||||
//Pipeline pipeline = jedis.pipelined();
|
||||
|
||||
for (String player : absentInRedis) {
|
||||
// Player not online according to Redis but not BungeeCord.
|
||||
handlePlatformPlayer(player, unifiedJedis);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
plugin.logFatal("Unable to fix up stored player data");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public abstract void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.tasks;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import redis.clients.jedis.*;
|
||||
|
||||
public abstract class RedisPipelineTask<T> extends RedisTask<T> {
|
||||
|
||||
|
||||
public RedisPipelineTask(AbstractRedisBungeeAPI api) {
|
||||
super(api);
|
||||
}
|
||||
|
||||
public RedisPipelineTask(RedisBungeePlugin<?> plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
if (unifiedJedis instanceof JedisPooled pooled) {
|
||||
try (Pipeline pipeline = pooled.pipelined()) {
|
||||
return doPooledPipeline(pipeline);
|
||||
}
|
||||
} else if (unifiedJedis instanceof JedisCluster jedisCluster) {
|
||||
try (ClusterPipeline pipeline = jedisCluster.pipelined()) {
|
||||
return clusterPipeline(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract T doPooledPipeline(Pipeline pipeline);
|
||||
|
||||
public abstract T clusterPipeline(ClusterPipeline pipeline);
|
||||
|
||||
|
||||
}
|
||||
@@ -11,11 +11,11 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.api.tasks;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -27,23 +27,22 @@ import java.util.concurrent.Callable;
|
||||
public abstract class RedisTask<V> implements Runnable, Callable<V> {
|
||||
|
||||
protected final Summoner<?> summoner;
|
||||
protected final AbstractRedisBungeeAPI api;
|
||||
protected RedisBungeePlugin<?> plugin;
|
||||
|
||||
protected final RedisBungeeMode mode;
|
||||
|
||||
@Override
|
||||
public V call() throws Exception {
|
||||
return execute();
|
||||
return this.execute();
|
||||
}
|
||||
|
||||
public RedisTask(AbstractRedisBungeeAPI api) {
|
||||
this.api = api;
|
||||
this.summoner = api.getSummoner();
|
||||
this.mode = api.getMode();
|
||||
}
|
||||
|
||||
public RedisTask(RedisBungeePlugin<?> plugin) {
|
||||
this.plugin = plugin;
|
||||
this.api = plugin.getAbstractRedisBungeeApi();
|
||||
this.summoner = api.getSummoner();
|
||||
this.summoner = plugin.getSummoner();
|
||||
this.mode = plugin.getRedisBungeeMode();
|
||||
}
|
||||
|
||||
public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis);
|
||||
@@ -53,22 +52,16 @@ public abstract class RedisTask<V> implements Runnable, Callable<V> {
|
||||
this.execute();
|
||||
}
|
||||
|
||||
public V execute(){
|
||||
public V execute() {
|
||||
// JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its single instance anyway.
|
||||
if (api.getMode() == RedisBungeeMode.SINGLE) {
|
||||
if (mode == RedisBungeeMode.SINGLE) {
|
||||
JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner;
|
||||
return this.unifiedJedisTask(jedisSummoner.obtainResource());
|
||||
} else if (api.getMode() == RedisBungeeMode.CLUSTER) {
|
||||
} else if (mode == RedisBungeeMode.CLUSTER) {
|
||||
JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner;
|
||||
return this.unifiedJedisTask(jedisClusterSummoner.obtainResource());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public RedisBungeePlugin<?> getPlugin() {
|
||||
if (plugin == null) {
|
||||
throw new NullPointerException("Plugin is null in the task");
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.tasks;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class ShutdownUtils {
|
||||
|
||||
public static void shutdownCleanup(RedisBungeePlugin<?> plugin) {
|
||||
new RedisTask<Void>(plugin) {
|
||||
@Override
|
||||
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
|
||||
unifiedJedis.hdel("heartbeats", plugin.getConfiguration().getProxyId());
|
||||
if (unifiedJedis.scard("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline") > 0) {
|
||||
Set<String> players = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline");
|
||||
for (String member : players)
|
||||
PlayerUtils.cleanUpPlayer(member, unifiedJedis, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.tasks;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.CachedUUIDEntry;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public class UUIDCleanupTask extends RedisTask<Void>{
|
||||
|
||||
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) -> {
|
||||
CachedUUIDEntry cachedUUIDEntry = gson.fromJson(data, CachedUUIDEntry.class);
|
||||
if (cachedUUIDEntry.expired()) {
|
||||
fieldsToRemove.add(field);
|
||||
}
|
||||
});
|
||||
if (!fieldsToRemove.isEmpty()) {
|
||||
unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0]));
|
||||
}
|
||||
plugin.logInfo("deleted {} entries", fieldsToRemove.size());
|
||||
} catch (JedisException e) {
|
||||
plugin.logFatal("There was an error fetching information", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2013-present RedisBungee contributors
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.imaginarycode.minecraft.redisbungee.api.util;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
|
||||
public class InitialUtils {
|
||||
|
||||
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
|
||||
new RedisTask<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 https://github.com/ProxioDev/Brains");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,6 +5,10 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
@VisibleForTesting
|
||||
public class RedisUtil {
|
||||
public final static int PROXY_TIMEOUT = 30;
|
||||
|
||||
public static final int MAJOR_VERSION = 6;
|
||||
public static final int MINOR_VERSION = 2;
|
||||
|
||||
public static boolean isRedisVersionRight(String redisVersion) {
|
||||
String[] args = redisVersion.split("\\.");
|
||||
if (args.length < 2) {
|
||||
@@ -12,7 +16,10 @@ public class RedisUtil {
|
||||
}
|
||||
int major = Integer.parseInt(args[0]);
|
||||
int minor = Integer.parseInt(args[1]);
|
||||
return major >= 3 && minor >= 0;
|
||||
|
||||
if (major > MAJOR_VERSION) return true;
|
||||
return major == MAJOR_VERSION && minor >= MINOR_VERSION;
|
||||
|
||||
}
|
||||
|
||||
// Ham1255: i am keeping this if some plugin uses this *IF*
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.api.util.io;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
|
||||
public class IOUtil {
|
||||
public static String readInputStreamAsString(InputStream is) {
|
||||
String string;
|
||||
try {
|
||||
string = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.api.util.payload;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PayloadUtils {
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
public static void playerJoinPayload(UUID uuid, UnifiedJedis unifiedJedis, InetAddress inetAddress) {
|
||||
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
|
||||
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.JOIN,
|
||||
new AbstractDataManager.LoginPayload(inetAddress))));
|
||||
}
|
||||
|
||||
|
||||
public static void playerQuitPayload(String uuid, UnifiedJedis unifiedJedis, long timestamp) {
|
||||
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
|
||||
UUID.fromString(uuid), AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.LEAVE,
|
||||
new AbstractDataManager.LogoutPayload(timestamp))));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void playerServerChangePayload(UUID uuid, UnifiedJedis unifiedJedis, String newServer, String oldServer) {
|
||||
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
|
||||
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.SERVER_CHANGE,
|
||||
new AbstractDataManager.ServerChangePayload(newServer, oldServer))));
|
||||
}
|
||||
|
||||
|
||||
public static void kickPlayerPayload(UUID uuid, String message, UnifiedJedis unifiedJedis) {
|
||||
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
|
||||
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.KICK,
|
||||
new AbstractDataManager.KickPayload(message))));
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.api.util.player;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerJoinPayload;
|
||||
import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerQuitPayload;
|
||||
|
||||
public class PlayerUtils {
|
||||
|
||||
public static void cleanUpPlayer(String uuid, UnifiedJedis rsc, boolean firePayload) {
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid, rsc);
|
||||
rsc.srem("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid);
|
||||
if (!isKickedFromOtherLocation) {
|
||||
rsc.hdel("player:" + uuid, "server", "ip", "proxy");
|
||||
rsc.hset("player:" + uuid, "online", String.valueOf(timestamp));
|
||||
}
|
||||
if (firePayload && !isKickedFromOtherLocation) {
|
||||
playerQuitPayload(uuid, rsc, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) {
|
||||
// set anything for sake of exists check. then expire it after 2 seconds. should be great?
|
||||
unifiedJedis.set("kicked-other-location::" + uuid, "0");
|
||||
unifiedJedis.expire("kicked-other-location::" + uuid, 2);
|
||||
}
|
||||
|
||||
public static boolean isKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) {
|
||||
return unifiedJedis.exists("kicked-other-location::" + uuid);
|
||||
}
|
||||
|
||||
|
||||
public static void createPlayer(UUID uuid, UnifiedJedis unifiedJedis, String currentServer, InetAddress hostname, boolean fireEvent) {
|
||||
final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid.toString(), unifiedJedis);
|
||||
Map<String, String> playerData = new HashMap<>(4);
|
||||
playerData.put("online", "0");
|
||||
playerData.put("ip", hostname.getHostName());
|
||||
playerData.put("proxy", AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId());
|
||||
if (currentServer != null) {
|
||||
playerData.put("server", currentServer);
|
||||
}
|
||||
unifiedJedis.sadd("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid.toString());
|
||||
unifiedJedis.hset("player:" + uuid, playerData);
|
||||
if (fireEvent && !isKickedFromOtherLocation) {
|
||||
playerJoinPayload(uuid, unifiedJedis, hostname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import com.google.common.io.ByteArrayDataOutput;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class Serializations {
|
||||
public class MultiMapSerialization {
|
||||
|
||||
public static void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
|
||||
output.writeInt(collection.elementSet().size());
|
||||
@@ -36,4 +36,5 @@ public class Serializations {
|
||||
output.writeUTF(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,38 +22,38 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class NameFetcher {
|
||||
private static OkHttpClient httpClient;
|
||||
private static final Gson gson = new Gson();
|
||||
private static OkHttpClient httpClient;
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
public static void setHttpClient(OkHttpClient httpClient) {
|
||||
NameFetcher.httpClient = httpClient;
|
||||
}
|
||||
public static void setHttpClient(OkHttpClient httpClient) {
|
||||
NameFetcher.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public static List<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();
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,15 @@ import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
# RedisBungee configuration file.
|
||||
# Get Redis from http://redis.io/
|
||||
# Notice:
|
||||
# Redis 7.2.4 is last free and open source Redis version after license change
|
||||
# https://download.redis.io/releases/redis-7.2.4.tar.gz which you have to compile yourself,
|
||||
# unless your package manager still provide it.
|
||||
# Here is The alternatives
|
||||
# - 'ValKey' By linux foundation https://valkey.io/download/
|
||||
# - 'KeyDB' by Snapchat inc https://docs.keydb.dev/docs/download/
|
||||
|
||||
# The Redis server you use.
|
||||
|
||||
# The 'Redis', 'ValKey', 'KeyDB' server you will use.
|
||||
# these settings are ignored when cluster mode is enabled.
|
||||
redis-server: 127.0.0.1
|
||||
redis-port: 6379
|
||||
@@ -12,7 +19,7 @@ cluster-mode-enabled: false
|
||||
|
||||
# FORMAT:
|
||||
# redis-cluster-servers:
|
||||
# - host: 127.0.0.1
|
||||
# - host: 127.0.0.1`
|
||||
# port: 2020
|
||||
# - host: 127.0.0.1
|
||||
# port: 2021
|
||||
@@ -25,11 +32,10 @@ redis-cluster-servers:
|
||||
- host: 127.0.0.1
|
||||
port: 6379
|
||||
|
||||
# THIS FEATURE IS REDIS V6+
|
||||
# OPTIONAL: if your redis uses acl usernames set the username here. leave empty for no username.
|
||||
redis-username: ""
|
||||
|
||||
# OPTIONAL but recommended: If your Redis server uses AUTH, set the password required.
|
||||
# OPTIONAL but recommended: If your Redis server uses AUTH, set the required password.
|
||||
redis-password: ""
|
||||
|
||||
# Maximum connections that will be maintained to the Redis server.
|
||||
@@ -37,44 +43,100 @@ redis-password: ""
|
||||
# inefficient plugins or a lot of players.
|
||||
max-redis-connections: 10
|
||||
|
||||
# since redis can support ssl by version 6 you can use ssl / tls in redis bungee too!
|
||||
# since redis can support ssl by version 6 you can use SSL/TLS in redis bungee too!
|
||||
# but there is more configuration needed to work see https://github.com/ProxioDev/RedisBungee/issues/18
|
||||
# Keep note that SSL/TLS connections will decrease redis performance so use it when needed.
|
||||
useSSL: false
|
||||
|
||||
# An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank.
|
||||
proxy-id: "test-1"
|
||||
# An identifier for this network, which helps to separate redisbungee instances on same redis instance.
|
||||
# You can use environment variable 'REDISBUNGEE_NETWORK_ID' to override
|
||||
network-id: "main"
|
||||
|
||||
# since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool.
|
||||
# An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank.
|
||||
# You can set Environment variable 'REDISBUNGEE_PROXY_ID' to override
|
||||
proxy-id: "proxy-1"
|
||||
|
||||
# since RedisBungee Internally now uses UnifiedJedis instead of Jedis, JedisPool.
|
||||
# which will break compatibility with old plugins that uses RedisBungee JedisPool
|
||||
# so to mitigate this issue, we will instruct RedisBungee to init an JedisPool for compatibility reasons.
|
||||
# enabled by default
|
||||
# ignored when cluster mode is enabled
|
||||
enable-jedis-pool-compatibility: true
|
||||
# so to mitigate this issue, RedisBungee will create an JedisPool for compatibility reasons.
|
||||
# disabled by default
|
||||
# Automatically disabled when cluster mode is enabled
|
||||
enable-jedis-pool-compatibility: false
|
||||
|
||||
# max connections for the compatibility pool
|
||||
compatibility-max-connections: 3
|
||||
|
||||
# Register redis bungee legacy commands
|
||||
# if this disabled override-bungee-commands will be ignored
|
||||
register-legacy-commands: false
|
||||
# restore old login behavior before 0.9.0 update
|
||||
# enabled by default
|
||||
# when true: when player login and there is old player with same uuid it will get disconnected as result and new player will log in
|
||||
# when false: when a player login but login will fail because old player is still connected.
|
||||
kick-when-online: true
|
||||
|
||||
# Whether or not RedisBungee should install its version of regular BungeeCord commands.
|
||||
# Often, the RedisBungee commands are desired, but in some cases someone may wish to
|
||||
# override the commands using another plugin.
|
||||
#
|
||||
# If you are just denying access to the commands, RedisBungee uses the default BungeeCord
|
||||
# permissions - just deny them and access will be denied.
|
||||
#
|
||||
# Please note that with build 787+, most commands overridden by RedisBungee were moved to
|
||||
# modules, and these must be disabled or overridden yourself.
|
||||
override-bungee-commands: false
|
||||
# enabled by default
|
||||
# this option tells RedisBungee handle motd and set online count, when motd is requested
|
||||
# you can disable this when you want to handle motd yourself, use RedisBungee api to get total players when needed :)
|
||||
handle-motd: true
|
||||
|
||||
# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic
|
||||
# restart scripts.
|
||||
# Automatically disabled if handle-motd is disabled.
|
||||
exempt-ip-addresses: []
|
||||
|
||||
# restore old login when online behavior before 0.9.0 update
|
||||
disable-kick-when-online: false
|
||||
# disabled by default
|
||||
# RedisBungee will attempt to connect player to last server that was stored.
|
||||
reconnect-to-last-server: false
|
||||
|
||||
# For redis bungee legacy commands
|
||||
# either can be run using '/rbl glist' for example
|
||||
# or if 'install' is set to true '/glist' can be used.
|
||||
# 'install' also overrides the proxy installed commands
|
||||
#
|
||||
# In legacy commands each command got it own permissions since they had it own permission pre new command system,
|
||||
# so it's also applied to subcommands in '/rbl'.
|
||||
commands:
|
||||
# Permission redisbungee.legacy.use
|
||||
redisbungee-legacy:
|
||||
enabled: false
|
||||
subcommands:
|
||||
# Permission redisbungee.command.glist
|
||||
glist:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.find
|
||||
find:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.lastseen
|
||||
lastseen:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.ip
|
||||
ip:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.pproxy
|
||||
pproxy:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.sendtoall
|
||||
sendtoall:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.serverid
|
||||
serverid:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.serverids
|
||||
serverids:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.plist
|
||||
plist:
|
||||
enabled: false
|
||||
install: false
|
||||
# Permission redisbungee.command.use
|
||||
redisbungee:
|
||||
enabled: true
|
||||
|
||||
# Config version DO NOT CHANGE!!!!
|
||||
config-version: 1
|
||||
config-version: 2
|
||||
|
||||
55
RedisBungee-API/src/main/resources/lang.yml
Normal file
55
RedisBungee-API/src/main/resources/lang.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
# this config file is for messages / Languages
|
||||
# use MiniMessage format https://docs.advntr.dev/minimessage/format.html
|
||||
# for colors etc... Legacy chat color is not supported.
|
||||
|
||||
# Language codes used in minecraft from the minecraft wiki
|
||||
# example: en-us for american english and ar-sa for arabic
|
||||
|
||||
# all codes can be obtained from link below
|
||||
# from the colum Locale Code -> In-game
|
||||
# NOTE: minecraft wiki shows languages like this `en_us` in config it should be `en-us`
|
||||
# https://minecraft.wiki/w/Language
|
||||
|
||||
# example:
|
||||
# lets assume we want to add arabic language.
|
||||
# messages:
|
||||
# logged-in-other-location:
|
||||
# en-us: "<color:red>You logged in from another location!"
|
||||
# ar-sa: "<color:red>لقد اتصلت من مكان اخر"
|
||||
|
||||
|
||||
# RedisBungee Prefix if ever used.
|
||||
prefix: "<color:red>[<color:yellow>Redis<color:red>Bungee]"
|
||||
|
||||
# en-us is american English, Which is the default language used when a language for a message isn't defined.
|
||||
# Warning: IF THE set default locale wasn't defined in the config for all messages, plugin will not load.
|
||||
# set the Default locale
|
||||
default-locale: en-us
|
||||
|
||||
# send language based on client sent settings
|
||||
# if you don't have languages configured For client Language
|
||||
# it will default to language that has been set above
|
||||
# NOTE: due minecraft protocol not sending player settings during login,
|
||||
# some of the messages like logged-in-other-location will
|
||||
# skip translation and use default locale that has been set in default-locale.
|
||||
use-client-locale: true
|
||||
|
||||
# messages that are used during login, and connecting to Last server
|
||||
messages:
|
||||
logged-in-other-location:
|
||||
en-us: "<color:red>You logged in from another location!"
|
||||
pt-br: "<color:red>Você está logado em outra localização!"
|
||||
already-logged-in:
|
||||
en-us: "<color:red>You are already logged in!"
|
||||
pt-br: "<color:red>Você já está logado!"
|
||||
server-not-found:
|
||||
# placeholder <server> displays server name in the message.
|
||||
en-us: "<color:red>unable to connect you to the last server, because server <server> was not found."
|
||||
pt-br: "<color:red>falha ao conectar você ao último servidor, porque o servidor <server> não foi encontrado."
|
||||
server-connecting:
|
||||
# placeholder <server> displays server name in the message.
|
||||
en-us: "<color:green>Connecting you to <server>..."
|
||||
pt-br: "<color:green>Conectando você a <server>..."
|
||||
|
||||
# DO NOT CHANGE!!!!!
|
||||
config-version: 1
|
||||
@@ -1,2 +0,0 @@
|
||||
logged-in-other-location: "§cYou logged in from another location!"
|
||||
already-logged-in: "§cYou are already logged in!"
|
||||
Reference in New Issue
Block a user