mirror of
https://github.com/proxiodev/RedisBungee.git
synced 2024-11-23 04:28:01 +00:00
seperate the internals from bungeecord api for easily supporting platform | progress 60%
This commit is contained in:
parent
165ba84791
commit
61dec7f03c
22
RedisBungee-API/pom.xml
Normal file
22
RedisBungee-API/pom.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>RedisBungee</artifactId>
|
||||||
|
<groupId>com.imaginarycode.minecraft</groupId>
|
||||||
|
<version>0.7.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>RedisBungee-API</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
@ -4,31 +4,34 @@ import com.google.common.base.Preconditions;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import lombok.NonNull;
|
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||||
import net.md_5.bungee.api.config.ServerInfo;
|
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.JedisPool;
|
import redis.clients.jedis.JedisPool;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungee#getApi()}.
|
* This class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungeePlugin#getApi()}.
|
||||||
*
|
*
|
||||||
* @author tuxed
|
* @author tuxed
|
||||||
* @since 0.2.3
|
* @since 0.2.3
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class RedisBungeeAPI {
|
public class RedisBungeeAPI {
|
||||||
private final RedisBungee plugin;
|
private final RedisBungeePlugin<?> plugin;
|
||||||
private final List<String> reservedChannels;
|
private final List<String> reservedChannels;
|
||||||
private static RedisBungeeAPI redisBungeeApi;
|
private static RedisBungeeAPI redisBungeeApi;
|
||||||
|
|
||||||
RedisBungeeAPI(RedisBungee plugin) {
|
RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
redisBungeeApi = this;
|
redisBungeeApi = this;
|
||||||
this.reservedChannels = ImmutableList.of(
|
this.reservedChannels = ImmutableList.of(
|
||||||
"redisbungee-allservers",
|
"redisbungee-allservers",
|
||||||
"redisbungee-" + RedisBungee.getConfiguration().getServerId(),
|
"redisbungee-" + plugin.getConfiguration().getServerId(),
|
||||||
"redisbungee-data"
|
"redisbungee-data"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -58,11 +61,10 @@ public class RedisBungeeAPI {
|
|||||||
* as well, and will return local information on them.
|
* as well, and will return local information on them.
|
||||||
*
|
*
|
||||||
* @param player a player name
|
* @param player a player name
|
||||||
* @return a {@link net.md_5.bungee.api.config.ServerInfo} for the server the player is on.
|
* @return a String name for the server the player is on.
|
||||||
*/
|
*/
|
||||||
public final ServerInfo getServerFor(@NonNull UUID player) {
|
public final String getServerFor(@NonNull UUID player) {
|
||||||
String server = plugin.getDataManager().getServer(player);
|
return plugin.getDataManager().getServer(player);
|
||||||
return plugin.getProxy().getServerInfo(server);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,7 +180,7 @@ public class RedisBungeeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for {@link com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent} to fire.
|
* Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for {@link PubSubMessageEvent} to fire.
|
||||||
*
|
*
|
||||||
* @param channel The PubSub channel
|
* @param channel The PubSub channel
|
||||||
* @param message the message body to send
|
* @param message the message body to send
|
||||||
@ -196,7 +198,7 @@ public class RedisBungeeAPI {
|
|||||||
* @since 0.2.5
|
* @since 0.2.5
|
||||||
*/
|
*/
|
||||||
public final String getServerId() {
|
public final String getServerId() {
|
||||||
return RedisBungee.getConfiguration().getServerId();
|
return plugin.getConfiguration().getServerId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -211,13 +213,13 @@ public class RedisBungeeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register (a) PubSub channel(s), so that you may handle {@link com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent} for it.
|
* Register (a) PubSub channel(s), so that you may handle {@link PubSubMessageEvent} for it.
|
||||||
*
|
*
|
||||||
* @param channels the channels to register
|
* @param channels the channels to register
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
public final void registerPubSubChannels(String... channels) {
|
public final void registerPubSubChannels(String... channels) {
|
||||||
RedisBungee.getPubSubListener().addChannel(channels);
|
plugin.getPubSubListener().addChannel(channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -231,7 +233,7 @@ public class RedisBungeeAPI {
|
|||||||
Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel");
|
Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel");
|
||||||
}
|
}
|
||||||
|
|
||||||
RedisBungee.getPubSubListener().removeChannel(channels);
|
plugin.getPubSubListener().removeChannel(channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -302,18 +304,16 @@ public class RedisBungeeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This gets Redis Bungee Jedis pool
|
* This gives you instance of Jedis!
|
||||||
*
|
*
|
||||||
* @return {@link JedisPool}
|
* @return {@link JedisPool}
|
||||||
* @since 0.6.5
|
* @since 0.7.0
|
||||||
*/
|
*/
|
||||||
public JedisPool getJedisPool() {
|
public Jedis getJedisPool() {
|
||||||
return this.plugin.getPool();
|
return this.plugin.requestJedis();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This alternative to {@link RedisBungee#getApi()}
|
|
||||||
* which now deprecated. but to maintain old plugins compatibility it won't be removed.
|
|
||||||
*
|
*
|
||||||
* @return the API instance.
|
* @return the API instance.
|
||||||
* @since 0.6.5
|
* @since 0.6.5
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee.events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is posted when a PubSub message is received.
|
||||||
|
* <p>
|
||||||
|
* <strong>Warning</strong>: This event is fired in a separate thread!
|
||||||
|
*
|
||||||
|
* @since 0.2.6
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class PubSubMessageEvent {
|
||||||
|
private final String channel;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public PubSubMessageEvent(String channel, String message) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee.internal;
|
||||||
|
|
||||||
|
|
||||||
|
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 static final String ALREADY_LOGGED_IN = "§cYou are already logged on to this server. \n\nIt may help to try logging in again in a few minutes.\nIf this does not resolve your issue, please contact staff.";
|
||||||
|
|
||||||
|
protected static final String ONLINE_MODE_RECONNECT = "§cWhoops! You need to reconnect\n\nWe found someone online using your username. They were kicked and you may reconnect.\nIf this does not work, please contact staff.";
|
||||||
|
|
||||||
|
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 abstract 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);
|
||||||
|
|
||||||
|
private void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
|
||||||
|
output.writeInt(collection.elementSet().size());
|
||||||
|
for (Multiset.Entry<String> entry : collection.entrySet()) {
|
||||||
|
output.writeUTF(entry.getElement());
|
||||||
|
output.writeInt(entry.getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private void serializeMultimap(Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) {
|
||||||
|
output.writeInt(collection.keySet().size());
|
||||||
|
for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) {
|
||||||
|
output.writeUTF(entry.getKey());
|
||||||
|
if (includeNames) {
|
||||||
|
serializeCollection(entry.getValue(), output);
|
||||||
|
} else {
|
||||||
|
output.writeInt(entry.getValue().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) {
|
||||||
|
output.writeInt(collection.size());
|
||||||
|
for (Object o : collection) {
|
||||||
|
output.writeUTF(o.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onPubSubMessage(PS event);
|
||||||
|
}
|
@ -1,23 +1,14 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee;
|
package com.imaginarycode.minecraft.redisbungee.internal;
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.net.InetAddresses;
|
import com.google.common.net.InetAddresses;
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
|
||||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
|
||||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
|
||||||
import net.md_5.bungee.api.plugin.Listener;
|
|
||||||
import net.md_5.bungee.event.EventHandler;
|
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@ -26,21 +17,21 @@ import java.util.UUID;
|
|||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class manages all the data that RedisBungee fetches from Redis, along with updates to that data.
|
* This class manages all the data that RedisBungee fetches from Redis, along with updates to that data.
|
||||||
*
|
*
|
||||||
* @since 0.3.3
|
* @since 0.3.3
|
||||||
*/
|
*/
|
||||||
public class DataManager implements Listener {
|
public abstract class DataManager<P, PS, PL, PD> {
|
||||||
private final RedisBungee plugin;
|
private final RedisBungeePlugin<P> plugin;
|
||||||
private final Cache<UUID, String> serverCache = createCache();
|
private final Cache<UUID, String> serverCache = createCache();
|
||||||
private final Cache<UUID, String> proxyCache = createCache();
|
private final Cache<UUID, String> proxyCache = createCache();
|
||||||
private final Cache<UUID, InetAddress> ipCache = createCache();
|
private final Cache<UUID, InetAddress> ipCache = createCache();
|
||||||
private final Cache<UUID, Long> lastOnlineCache = createCache();
|
private final Cache<UUID, Long> lastOnlineCache = createCache();
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
public DataManager(RedisBungee plugin) {
|
public DataManager(RedisBungeePlugin<P> plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,16 +46,16 @@ public class DataManager implements Listener {
|
|||||||
private final JsonParser parser = new JsonParser();
|
private final JsonParser parser = new JsonParser();
|
||||||
|
|
||||||
public String getServer(final UUID uuid) {
|
public String getServer(final UUID uuid) {
|
||||||
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
|
P player = plugin.getPlayer(uuid);
|
||||||
|
|
||||||
if (player != null)
|
if (player != null)
|
||||||
return player.getServer() != null ? player.getServer().getInfo().getName() : null;
|
return plugin.isPlayerOnAServer(player) ? plugin.getPlayerServerName(player) : null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return serverCache.get(uuid, new Callable<String>() {
|
return serverCache.get(uuid, new Callable<String>() {
|
||||||
@Override
|
@Override
|
||||||
public String call() throws Exception {
|
public String call() throws Exception {
|
||||||
try (Jedis tmpRsc = plugin.getPool().getResource()) {
|
try (Jedis tmpRsc = plugin.requestJedis()) {
|
||||||
return Objects.requireNonNull(tmpRsc.hget("player:" + uuid, "server"), "user not found");
|
return Objects.requireNonNull(tmpRsc.hget("player:" + uuid, "server"), "user not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,22 +63,23 @@ public class DataManager implements Listener {
|
|||||||
} catch (ExecutionException | UncheckedExecutionException e) {
|
} catch (ExecutionException | UncheckedExecutionException e) {
|
||||||
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
||||||
return null; // HACK
|
return null; // HACK
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to get server", e);
|
plugin.logFatal("Unable to get server");
|
||||||
throw new RuntimeException("Unable to get server for " + uuid, e);
|
throw new RuntimeException("Unable to get server for " + uuid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getProxy(final UUID uuid) {
|
public String getProxy(final UUID uuid) {
|
||||||
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
|
P player = plugin.getPlayer(uuid);
|
||||||
|
|
||||||
if (player != null)
|
if (player != null)
|
||||||
return RedisBungee.getConfiguration().getServerId();
|
return plugin.getConfiguration().getServerId();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return proxyCache.get(uuid, new Callable<String>() {
|
return proxyCache.get(uuid, new Callable<String>() {
|
||||||
@Override
|
@Override
|
||||||
public String call() throws Exception {
|
public String call() throws Exception {
|
||||||
try (Jedis tmpRsc = plugin.getPool().getResource()) {
|
try (Jedis tmpRsc = plugin.requestJedis()) {
|
||||||
return Objects.requireNonNull(tmpRsc.hget("player:" + uuid, "proxy"), "user not found");
|
return Objects.requireNonNull(tmpRsc.hget("player:" + uuid, "proxy"), "user not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,22 +87,22 @@ public class DataManager implements Listener {
|
|||||||
} catch (ExecutionException | UncheckedExecutionException e) {
|
} catch (ExecutionException | UncheckedExecutionException e) {
|
||||||
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
||||||
return null; // HACK
|
return null; // HACK
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to get proxy", e);
|
plugin.logFatal("Unable to get proxy");
|
||||||
throw new RuntimeException("Unable to get proxy for " + uuid, e);
|
throw new RuntimeException("Unable to get proxy for " + uuid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetAddress getIp(final UUID uuid) {
|
public InetAddress getIp(final UUID uuid) {
|
||||||
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
|
P player = plugin.getPlayer(uuid);
|
||||||
|
|
||||||
if (player != null)
|
if (player != null)
|
||||||
return player.getAddress().getAddress();
|
return plugin.getPlayerIp(player);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return ipCache.get(uuid, new Callable<InetAddress>() {
|
return ipCache.get(uuid, new Callable<InetAddress>() {
|
||||||
@Override
|
@Override
|
||||||
public InetAddress call() throws Exception {
|
public InetAddress call() throws Exception {
|
||||||
try (Jedis tmpRsc = plugin.getPool().getResource()) {
|
try (Jedis tmpRsc = plugin.requestJedis()) {
|
||||||
String result = tmpRsc.hget("player:" + uuid, "ip");
|
String result = tmpRsc.hget("player:" + uuid, "ip");
|
||||||
if (result == null)
|
if (result == null)
|
||||||
throw new NullPointerException("user not found");
|
throw new NullPointerException("user not found");
|
||||||
@ -121,13 +113,13 @@ public class DataManager implements Listener {
|
|||||||
} catch (ExecutionException | UncheckedExecutionException e) {
|
} catch (ExecutionException | UncheckedExecutionException e) {
|
||||||
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
|
||||||
return null; // HACK
|
return null; // HACK
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to get IP", e);
|
plugin.logFatal( "Unable to get IP");
|
||||||
throw new RuntimeException("Unable to get IP for " + uuid, e);
|
throw new RuntimeException("Unable to get IP for " + uuid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLastOnline(final UUID uuid) {
|
public long getLastOnline(final UUID uuid) {
|
||||||
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
|
P player = plugin.getPlayer(uuid);
|
||||||
|
|
||||||
if (player != null)
|
if (player != null)
|
||||||
return 0;
|
return 0;
|
||||||
@ -136,14 +128,14 @@ public class DataManager implements Listener {
|
|||||||
return lastOnlineCache.get(uuid, new Callable<Long>() {
|
return lastOnlineCache.get(uuid, new Callable<Long>() {
|
||||||
@Override
|
@Override
|
||||||
public Long call() throws Exception {
|
public Long call() throws Exception {
|
||||||
try (Jedis tmpRsc = plugin.getPool().getResource()) {
|
try (Jedis tmpRsc = plugin.requestJedis()) {
|
||||||
String result = tmpRsc.hget("player:" + uuid, "online");
|
String result = tmpRsc.hget("player:" + uuid, "online");
|
||||||
return result == null ? -1 : Long.valueOf(result);
|
return result == null ? -1 : Long.valueOf(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to get last time online", e);
|
plugin.logFatal("Unable to get last time online");
|
||||||
throw new RuntimeException("Unable to get last time online for " + uuid, e);
|
throw new RuntimeException("Unable to get last time online for " + uuid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,104 +147,153 @@ public class DataManager implements Listener {
|
|||||||
proxyCache.invalidate(uuid);
|
proxyCache.invalidate(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPostLogin(PostLoginEvent event) {
|
public void onPostLogin(PL event) {
|
||||||
// Invalidate all entries related to this player, since they now lie.
|
|
||||||
invalidate(event.getPlayer().getUniqueId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
public void onPlayerDisconnect(PD event) {
|
||||||
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
|
|
||||||
// Invalidate all entries related to this player, since they now lie.
|
|
||||||
invalidate(event.getPlayer().getUniqueId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
public abstract void onPubSubMessage(PS event);
|
||||||
public void onPubSubMessage(PubSubMessageEvent event) {
|
|
||||||
if (!event.getChannel().equals("redisbungee-data"))
|
protected void handlePubSubMessage(String channel, String message) {
|
||||||
|
if (!channel.equals("redisbungee-data"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Partially deserialize the message so we can look at the action
|
// Partially deserialize the message so we can look at the action
|
||||||
JsonObject jsonObject = parser.parse(event.getMessage()).getAsJsonObject();
|
JsonObject jsonObject = parser.parse(message).getAsJsonObject();
|
||||||
|
|
||||||
String source = jsonObject.get("source").getAsString();
|
String source = jsonObject.get("source").getAsString();
|
||||||
|
|
||||||
if (source.equals(RedisBungee.getConfiguration().getServerId()))
|
if (source.equals(plugin.getConfiguration().getServerId()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString());
|
DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString());
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case JOIN:
|
case JOIN:
|
||||||
final DataManagerMessage<LoginPayload> message1 = RedisBungee.getGson().fromJson(jsonObject, new TypeToken<DataManagerMessage<LoginPayload>>() {
|
final DataManagerMessage message1 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
proxyCache.put(message1.getTarget(), message1.getSource());
|
proxyCache.put(message1.getTarget(), message1.getSource());
|
||||||
lastOnlineCache.put(message1.getTarget(), (long) 0);
|
lastOnlineCache.put(message1.getTarget(), (long) 0);
|
||||||
ipCache.put(message1.getTarget(), message1.getPayload().getAddress());
|
ipCache.put(message1.getTarget(), ((LoginPayload)message1.getPayload()).getAddress());
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
plugin.executeAsync(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
plugin.getProxy().getPluginManager().callEvent(new PlayerJoinedNetworkEvent(message1.getTarget()));
|
//plugin.getProxy().getPluginManager().callEvent(new PlayerJoinedNetworkEvent(message1.getTarget()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case LEAVE:
|
case LEAVE:
|
||||||
final DataManagerMessage<LogoutPayload> message2 = RedisBungee.getGson().fromJson(jsonObject, new TypeToken<DataManagerMessage<LogoutPayload>>() {
|
final DataManagerMessage message2 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
invalidate(message2.getTarget());
|
invalidate(message2.getTarget());
|
||||||
lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp());
|
lastOnlineCache.put(message2.getTarget(), ((LogoutPayload)message2.getPayload()).getTimestamp());
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
plugin.executeAsync(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
plugin.getProxy().getPluginManager().callEvent(new PlayerLeftNetworkEvent(message2.getTarget()));
|
// plugin.getProxy().getPluginManager().callEvent(new PlayerLeftNetworkEvent(message2.getTarget()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case SERVER_CHANGE:
|
case SERVER_CHANGE:
|
||||||
final DataManagerMessage<ServerChangePayload> message3 = RedisBungee.getGson().fromJson(jsonObject, new TypeToken<DataManagerMessage<ServerChangePayload>>() {
|
final DataManagerMessage message3 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
serverCache.put(message3.getTarget(), message3.getPayload().getServer());
|
serverCache.put(message3.getTarget(), ((ServerChangePayload)message3.getPayload()).getServer());
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
plugin.executeAsync(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
plugin.getProxy().getPluginManager().callEvent(new PlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer()));
|
//plugin.getProxy().getPluginManager().callEvent(new PlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
static class DataManagerMessage<T> {
|
|
||||||
private final UUID target;
|
|
||||||
private final String source = RedisBungee.getApi().getServerId();
|
|
||||||
private final Action action; // for future use!
|
|
||||||
private final T payload;
|
|
||||||
|
|
||||||
enum Action {
|
|
||||||
|
public static class DataManagerMessage {
|
||||||
|
private final UUID target;
|
||||||
|
private final String source;
|
||||||
|
private final Action action; // for future use!
|
||||||
|
private final Payload payload;
|
||||||
|
|
||||||
|
public DataManagerMessage(UUID target, String source, Action action, Payload 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 Payload getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Action {
|
||||||
JOIN,
|
JOIN,
|
||||||
LEAVE,
|
LEAVE,
|
||||||
SERVER_CHANGE
|
SERVER_CHANGE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
public static abstract class Payload {
|
||||||
@RequiredArgsConstructor
|
|
||||||
static class LoginPayload {
|
|
||||||
private final InetAddress address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
|
||||||
@RequiredArgsConstructor
|
public static class LoginPayload extends Payload{
|
||||||
static class ServerChangePayload {
|
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 server;
|
||||||
private final String oldServer;
|
private final String oldServer;
|
||||||
|
|
||||||
|
ServerChangePayload(String server, String oldServer) {
|
||||||
|
this.server = server;
|
||||||
|
this.oldServer = oldServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
public String getServer() {
|
||||||
@RequiredArgsConstructor
|
return server;
|
||||||
static class LogoutPayload {
|
}
|
||||||
|
|
||||||
|
public String getOldServer() {
|
||||||
|
return oldServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class LogoutPayload extends Payload {
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
|
|
||||||
|
public LogoutPayload(long timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee.internal;
|
||||||
|
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||||
|
import redis.clients.jedis.JedisPubSub;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
public class JedisPubSubHandler extends JedisPubSub {
|
||||||
|
|
||||||
|
private final RedisBungeePlugin<?> plugin;
|
||||||
|
|
||||||
|
public JedisPubSubHandler(RedisBungeePlugin<?> plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> bungeeEvent;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(final String s, final String s2) {
|
||||||
|
if (s2.trim().length() == 0) return;
|
||||||
|
plugin.executeAsync(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (isBungeeEvent()) {
|
||||||
|
try {
|
||||||
|
Object object = bungeeEvent.getConstructor(String.class, String.class).newInstance(s, s2);
|
||||||
|
plugin.callEvent(object);
|
||||||
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException("unable to fire pubsub event.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PubSubMessageEvent event = new PubSubMessageEvent(s, s2);
|
||||||
|
plugin.callEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBungeeEvent() {
|
||||||
|
return bungeeEvent != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBungeeEvent(Class<?> clazz) {
|
||||||
|
bungeeEvent = clazz;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee.internal;
|
||||||
|
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class PubSubListener implements Runnable {
|
||||||
|
private JedisPubSubHandler jpsh;
|
||||||
|
private Set<String> addedChannels = new HashSet<String>();
|
||||||
|
|
||||||
|
private final RedisBungeePlugin<?> plugin;
|
||||||
|
|
||||||
|
public PubSubListener(RedisBungeePlugin<?> plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
boolean broken = false;
|
||||||
|
try (Jedis rsc = plugin.requestJedis()) {
|
||||||
|
try {
|
||||||
|
jpsh = new JedisPubSubHandler(plugin);
|
||||||
|
addedChannels.add("redisbungee-" + plugin.getConfiguration().getServerId());
|
||||||
|
addedChannels.add("redisbungee-allservers");
|
||||||
|
addedChannels.add("redisbungee-data");
|
||||||
|
rsc.subscribe(jpsh, addedChannels.toArray(new String[0]));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// FIXME: Extremely ugly hack
|
||||||
|
// Attempt to unsubscribe this instance and try again.
|
||||||
|
plugin.logWarn("PubSub error, attempting to recover.");
|
||||||
|
try {
|
||||||
|
jpsh.unsubscribe();
|
||||||
|
} catch (Exception e1) {
|
||||||
|
/* This may fail with
|
||||||
|
- java.net.SocketException: Broken pipe
|
||||||
|
- redis.clients.jedis.exceptions.JedisConnectionException: JedisPubSub was not subscribed to a Jedis instance
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
broken = true;
|
||||||
|
}
|
||||||
|
} catch (JedisConnectionException e) {
|
||||||
|
plugin.logWarn("PubSub error, attempting to recover in 5 secs.");
|
||||||
|
plugin.executeAsyncAfter(this, TimeUnit.SECONDS, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (broken) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee.internal;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.net.InetAddresses;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RedisBungeeConfiguration {
|
||||||
|
private final String serverId;
|
||||||
|
private final List<InetAddress> exemptAddresses;
|
||||||
|
private static RedisBungeeConfiguration config;
|
||||||
|
|
||||||
|
public RedisBungeeConfiguration(String serverId, List<String> exemptAddresses) {
|
||||||
|
this.serverId = serverId;
|
||||||
|
|
||||||
|
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
|
||||||
|
for (String s : exemptAddresses) {
|
||||||
|
addressBuilder.add(InetAddresses.forString(s));
|
||||||
|
}
|
||||||
|
this.exemptAddresses = addressBuilder.build();
|
||||||
|
config = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerId() {
|
||||||
|
return serverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InetAddress> getExemptAddresses() {
|
||||||
|
return exemptAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RedisBungeeConfiguration getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee.internal;
|
||||||
|
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDTranslator;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public interface RedisBungeePlugin<P> {
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
|
||||||
|
void disable();
|
||||||
|
|
||||||
|
RedisBungeeConfiguration getConfiguration();
|
||||||
|
|
||||||
|
int getCount();
|
||||||
|
|
||||||
|
DataManager<P, ?, ?, ?> getDataManager();
|
||||||
|
|
||||||
|
Set<UUID> getPlayers();
|
||||||
|
|
||||||
|
Jedis requestJedis();
|
||||||
|
|
||||||
|
RedisBungeeAPI getApi();
|
||||||
|
|
||||||
|
UUIDTranslator getUuidTranslator();
|
||||||
|
|
||||||
|
Multimap<String, UUID> serversToPlayers();
|
||||||
|
|
||||||
|
Set<UUID> getPlayersOnProxy(String proxyId);
|
||||||
|
|
||||||
|
void sendProxyCommand(String serverId, String command);
|
||||||
|
|
||||||
|
List<String> getServerIds();
|
||||||
|
|
||||||
|
PubSubListener getPubSubListener();
|
||||||
|
|
||||||
|
void sendChannelMessage(String channel, String message);
|
||||||
|
|
||||||
|
void executeAsync(Runnable runnable);
|
||||||
|
|
||||||
|
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int seconds);
|
||||||
|
|
||||||
|
void callEvent(Object object);
|
||||||
|
|
||||||
|
boolean isOnlineMode();
|
||||||
|
|
||||||
|
void logInfo(String msg);
|
||||||
|
|
||||||
|
void logWarn(String msg);
|
||||||
|
|
||||||
|
void logFatal(String msg);
|
||||||
|
|
||||||
|
boolean isPlayerServerNull(P player);
|
||||||
|
|
||||||
|
P getPlayer(UUID uuid);
|
||||||
|
|
||||||
|
P getPlayer(String name);
|
||||||
|
|
||||||
|
UUID getPlayerUUID(String player);
|
||||||
|
|
||||||
|
String getPlayerName(UUID player);
|
||||||
|
|
||||||
|
String getPlayerServerName(P player);
|
||||||
|
|
||||||
|
boolean isPlayerOnAServer(P player);
|
||||||
|
|
||||||
|
InetAddress getPlayerIp(P player);
|
||||||
|
|
||||||
|
void executeProxyCommand(String cmd);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee.internal;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.Pipeline;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public class RedisUtil {
|
||||||
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
|
public static void cleanUpPlayer(String player, Jedis rsc) {
|
||||||
|
rsc.srem("proxy:" + RedisBungeeAPI.getRedisBungeeApi().getServerId() + ":usersOnline", player);
|
||||||
|
rsc.hdel("player:" + player, "server", "ip", "proxy");
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
rsc.hset("player:" + player, "online", String.valueOf(timestamp));
|
||||||
|
rsc.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage(
|
||||||
|
UUID.fromString(player), RedisBungeeAPI.getRedisBungeeApi().getServerId(), DataManager.DataManagerMessage.Action.LEAVE,
|
||||||
|
new DataManager.LogoutPayload(timestamp))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void cleanUpPlayer(String player, Pipeline rsc) {
|
||||||
|
rsc.srem("proxy:" + RedisBungeeAPI.getRedisBungeeApi().getServerId() + ":usersOnline", player);
|
||||||
|
rsc.hdel("player:" + player, "server", "ip", "proxy");
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
rsc.hset("player:" + player, "online", String.valueOf(timestamp));
|
||||||
|
rsc.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage(
|
||||||
|
UUID.fromString(player), RedisBungeeAPI.getRedisBungeeApi().getServerId(), DataManager.DataManagerMessage.Action.LEAVE,
|
||||||
|
new DataManager.LogoutPayload(timestamp))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRedisVersionRight(String redisVersion) {
|
||||||
|
// Need to use >=6.2 to use Lua optimizations.
|
||||||
|
String[] args = redisVersion.split("\\.");
|
||||||
|
if (args.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int major = Integer.parseInt(args[0]);
|
||||||
|
int minor = Integer.parseInt(args[1]);
|
||||||
|
return major >= 6 && minor >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ham1255: i am keeping this if some plugin uses this *IF*
|
||||||
|
@Deprecated
|
||||||
|
public static boolean canUseLua(String redisVersion) {
|
||||||
|
return isRedisVersionRight(redisVersion);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,12 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.util;
|
package com.imaginarycode.minecraft.redisbungee.internal.util;
|
||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class IOUtil {
|
public class IOUtil {
|
||||||
public static String readInputStreamAsString(InputStream is) {
|
public static String readInputStreamAsString(InputStream is) {
|
||||||
String string;
|
String string;
|
@ -1,32 +1,46 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.util;
|
package com.imaginarycode.minecraft.redisbungee.internal.util;
|
||||||
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.exceptions.JedisDataException;
|
import redis.clients.jedis.exceptions.JedisDataException;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class LuaManager {
|
public class LuaManager {
|
||||||
private final RedisBungee plugin;
|
private final RedisBungeePlugin<?> plugin;
|
||||||
|
|
||||||
|
public LuaManager(RedisBungeePlugin<?> plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
public Script createScript(String script) {
|
public Script createScript(String script) {
|
||||||
try (Jedis jedis = plugin.getPool().getResource()) {
|
try (Jedis jedis = plugin.requestJedis()) {
|
||||||
String hash = jedis.scriptLoad(script);
|
String hash = jedis.scriptLoad(script);
|
||||||
return new Script(script, hash);
|
return new Script(script, hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class Script {
|
public class Script {
|
||||||
private final String script;
|
private final String script;
|
||||||
private final String hashed;
|
private final String hashed;
|
||||||
|
|
||||||
|
public Script(String script, String hashed) {
|
||||||
|
this.script = script;
|
||||||
|
this.hashed = hashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScript() {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHashed() {
|
||||||
|
return hashed;
|
||||||
|
}
|
||||||
|
|
||||||
public Object eval(List<String> keys, List<String> args) {
|
public Object eval(List<String> keys, List<String> args) {
|
||||||
Object data;
|
Object data;
|
||||||
|
|
||||||
try (Jedis jedis = plugin.getPool().getResource()) {
|
try (Jedis jedis = plugin.requestJedis()) {
|
||||||
try {
|
try {
|
||||||
data = jedis.evalsha(hashed, keys, args);
|
data = jedis.evalsha(hashed, keys, args);
|
||||||
} catch (JedisDataException e) {
|
} catch (JedisDataException e) {
|
@ -1,16 +1,19 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.util;
|
package com.imaginarycode.minecraft.redisbungee.internal.util;
|
||||||
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
|
|
||||||
public abstract class RedisCallable<T> implements Callable<T>, Runnable {
|
public abstract class RedisCallable<T> implements Callable<T>, Runnable {
|
||||||
private final RedisBungee plugin;
|
private final RedisBungeePlugin<?> plugin;
|
||||||
|
|
||||||
|
public RedisCallable(RedisBungeePlugin<?> plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T call() {
|
public T call() {
|
||||||
@ -22,10 +25,10 @@ public abstract class RedisCallable<T> implements Callable<T>, Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private T run(boolean retry) {
|
private T run(boolean retry) {
|
||||||
try (Jedis jedis = plugin.getPool().getResource()) {
|
try (Jedis jedis = plugin.requestJedis()) {
|
||||||
return call(jedis);
|
return call(jedis);
|
||||||
} catch (JedisConnectionException e) {
|
} catch (JedisConnectionException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to get connection", e);
|
plugin.logFatal("Unable to get connection");
|
||||||
|
|
||||||
if (!retry) {
|
if (!retry) {
|
||||||
// Wait one second before retrying the task
|
// Wait one second before retrying the task
|
@ -1,24 +1,23 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.util.uuid;
|
package com.imaginarycode.minecraft.redisbungee.internal.util.uuid;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
import com.squareup.okhttp.Request;
|
import com.squareup.okhttp.Request;
|
||||||
import com.squareup.okhttp.ResponseBody;
|
import com.squareup.okhttp.ResponseBody;
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class NameFetcher {
|
public class NameFetcher {
|
||||||
@Setter
|
|
||||||
private static OkHttpClient httpClient;
|
private static OkHttpClient httpClient;
|
||||||
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
|
public static void setHttpClient(OkHttpClient httpClient) {
|
||||||
|
NameFetcher.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
|
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
|
||||||
String url = "https://api.mojang.com/user/profiles/" + uuid.toString().replace("-", "") + "/names";
|
String url = "https://api.mojang.com/user/profiles/" + uuid.toString().replace("-", "") + "/names";
|
||||||
@ -29,7 +28,7 @@ public class NameFetcher {
|
|||||||
|
|
||||||
Type listType = new TypeToken<List<Name>>() {
|
Type listType = new TypeToken<List<Name>>() {
|
||||||
}.getType();
|
}.getType();
|
||||||
List<Name> names = RedisBungee.getGson().fromJson(response, listType);
|
List<Name> names = gson.fromJson(response, listType);
|
||||||
|
|
||||||
List<String> humanNames = new ArrayList<>();
|
List<String> humanNames = new ArrayList<>();
|
||||||
for (Name name : names) {
|
for (Name name : names) {
|
@ -1,9 +1,8 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.util.uuid;
|
package com.imaginarycode.minecraft.redisbungee.internal.util.uuid;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
import com.google.gson.Gson;
|
||||||
import com.squareup.okhttp.*;
|
import com.squareup.okhttp.*;
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -18,8 +17,13 @@ public class UUIDFetcher implements Callable<Map<String, UUID>> {
|
|||||||
private static final MediaType JSON = MediaType.parse("application/json");
|
private static final MediaType JSON = MediaType.parse("application/json");
|
||||||
private final List<String> names;
|
private final List<String> names;
|
||||||
private final boolean rateLimiting;
|
private final boolean rateLimiting;
|
||||||
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
|
|
||||||
|
public static void setHttpClient(OkHttpClient httpClient) {
|
||||||
|
UUIDFetcher.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
@Setter
|
|
||||||
private static OkHttpClient httpClient;
|
private static OkHttpClient httpClient;
|
||||||
|
|
||||||
private UUIDFetcher(List<String> names, boolean rateLimiting) {
|
private UUIDFetcher(List<String> names, boolean rateLimiting) {
|
||||||
@ -39,12 +43,12 @@ public class UUIDFetcher implements Callable<Map<String, UUID>> {
|
|||||||
Map<String, UUID> uuidMap = new HashMap<>();
|
Map<String, UUID> uuidMap = new HashMap<>();
|
||||||
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
|
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
|
||||||
for (int i = 0; i < requests; i++) {
|
for (int i = 0; i < requests; i++) {
|
||||||
String body = RedisBungee.getGson().toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
|
String body = gson.toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
|
||||||
Request request = new Request.Builder().url(PROFILE_URL).post(RequestBody.create(JSON, body)).build();
|
Request request = new Request.Builder().url(PROFILE_URL).post(RequestBody.create(JSON, body)).build();
|
||||||
ResponseBody responseBody = httpClient.newCall(request).execute().body();
|
ResponseBody responseBody = httpClient.newCall(request).execute().body();
|
||||||
String response = responseBody.string();
|
String response = responseBody.string();
|
||||||
responseBody.close();
|
responseBody.close();
|
||||||
Profile[] array = RedisBungee.getGson().fromJson(response, Profile[].class);
|
Profile[] array = gson.fromJson(response, Profile[].class);
|
||||||
for (Profile profile : array) {
|
for (Profile profile : array) {
|
||||||
UUID uuid = UUIDFetcher.getUUID(profile.id);
|
UUID uuid = UUIDFetcher.getUUID(profile.id);
|
||||||
uuidMap.put(profile.name, uuid);
|
uuidMap.put(profile.name, uuid);
|
@ -1,13 +1,12 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.util.uuid;
|
package com.imaginarycode.minecraft.redisbungee.internal.util.uuid;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
import com.google.gson.Gson;
|
||||||
import lombok.Getter;
|
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import net.md_5.bungee.api.ProxyServer;
|
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.Pipeline;
|
import redis.clients.jedis.Pipeline;
|
||||||
import redis.clients.jedis.exceptions.JedisException;
|
import redis.clients.jedis.exceptions.JedisException;
|
||||||
@ -17,13 +16,17 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public final class UUIDTranslator {
|
public final class UUIDTranslator {
|
||||||
private static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
|
private static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
|
||||||
private static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
|
private static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
|
||||||
private final RedisBungee plugin;
|
private final RedisBungeePlugin<?> plugin;
|
||||||
private final Map<String, CachedUUIDEntry> nameToUuidMap = new ConcurrentHashMap<>(128, 0.5f, 4);
|
private final Map<String, CachedUUIDEntry> nameToUuidMap = new ConcurrentHashMap<>(128, 0.5f, 4);
|
||||||
private final Map<UUID, CachedUUIDEntry> uuidToNameMap = new ConcurrentHashMap<>(128, 0.5f, 4);
|
private final Map<UUID, CachedUUIDEntry> uuidToNameMap = new ConcurrentHashMap<>(128, 0.5f, 4);
|
||||||
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
|
public UUIDTranslator(RedisBungeePlugin<?> plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
private void addToMaps(String name, UUID uuid) {
|
private void addToMaps(String name, UUID uuid) {
|
||||||
// This is why I like LocalDate...
|
// This is why I like LocalDate...
|
||||||
@ -41,8 +44,8 @@ public final class UUIDTranslator {
|
|||||||
public final UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) {
|
public final UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) {
|
||||||
// If the player is online, give them their UUID.
|
// If the player is online, give them their UUID.
|
||||||
// Remember, local data > remote data.
|
// Remember, local data > remote data.
|
||||||
if (ProxyServer.getInstance().getPlayer(player) != null)
|
if (plugin.getPlayer(player) != null)
|
||||||
return ProxyServer.getInstance().getPlayer(player).getUniqueId();
|
return plugin.getPlayerUUID(player);
|
||||||
|
|
||||||
// Check if it exists in the map
|
// Check if it exists in the map
|
||||||
CachedUUIDEntry cachedUUIDEntry = nameToUuidMap.get(player.toLowerCase());
|
CachedUUIDEntry cachedUUIDEntry = nameToUuidMap.get(player.toLowerCase());
|
||||||
@ -65,16 +68,16 @@ public final class UUIDTranslator {
|
|||||||
|
|
||||||
// If we are in offline mode, UUID generation is simple.
|
// If we are in offline mode, UUID generation is simple.
|
||||||
// We don't even have to cache the UUID, since this is easy to recalculate.
|
// We don't even have to cache the UUID, since this is easy to recalculate.
|
||||||
if (!plugin.getProxy().getConfig().isOnlineMode()) {
|
if (!plugin.isOnlineMode()) {
|
||||||
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
|
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's try Redis.
|
// Let's try Redis.
|
||||||
try (Jedis jedis = plugin.getPool().getResource()) {
|
try (Jedis jedis = plugin.requestJedis()) {
|
||||||
String stored = jedis.hget("uuid-cache", player.toLowerCase());
|
String stored = jedis.hget("uuid-cache", player.toLowerCase());
|
||||||
if (stored != null) {
|
if (stored != null) {
|
||||||
// Found an entry value. Deserialize it.
|
// Found an entry value. Deserialize it.
|
||||||
CachedUUIDEntry entry = RedisBungee.getGson().fromJson(stored, CachedUUIDEntry.class);
|
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
|
||||||
|
|
||||||
// Check for expiry:
|
// Check for expiry:
|
||||||
if (entry.expired()) {
|
if (entry.expired()) {
|
||||||
@ -89,14 +92,14 @@ public final class UUIDTranslator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// That didn't work. Let's ask Mojang.
|
// That didn't work. Let's ask Mojang.
|
||||||
if (!expensiveLookups || !ProxyServer.getInstance().getConfig().isOnlineMode())
|
if (!expensiveLookups || !plugin.isOnlineMode())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Map<String, UUID> uuidMap1;
|
Map<String, UUID> uuidMap1;
|
||||||
try {
|
try {
|
||||||
uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call();
|
uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to fetch UUID from Mojang for " + player, e);
|
plugin.logFatal("Unable to fetch UUID from Mojang for " + player);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
|
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
|
||||||
@ -106,7 +109,7 @@ public final class UUIDTranslator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (JedisException e) {
|
} catch (JedisException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to fetch UUID for " + player, e);
|
plugin.logFatal("Unable to fetch UUID for " + player);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // Nope, game over!
|
return null; // Nope, game over!
|
||||||
@ -115,8 +118,8 @@ public final class UUIDTranslator {
|
|||||||
public final String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) {
|
public final String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) {
|
||||||
// If the player is online, give them their UUID.
|
// If the player is online, give them their UUID.
|
||||||
// Remember, local data > remote data.
|
// Remember, local data > remote data.
|
||||||
if (ProxyServer.getInstance().getPlayer(player) != null)
|
if (plugin.getPlayer(player) != null)
|
||||||
return ProxyServer.getInstance().getPlayer(player).getName();
|
return plugin.getPlayerName(player);
|
||||||
|
|
||||||
// Check if it exists in the map
|
// Check if it exists in the map
|
||||||
CachedUUIDEntry cachedUUIDEntry = uuidToNameMap.get(player);
|
CachedUUIDEntry cachedUUIDEntry = uuidToNameMap.get(player);
|
||||||
@ -128,11 +131,11 @@ public final class UUIDTranslator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Okay, it wasn't locally cached. Let's try Redis.
|
// Okay, it wasn't locally cached. Let's try Redis.
|
||||||
try (Jedis jedis = plugin.getPool().getResource()) {
|
try (Jedis jedis = plugin.requestJedis()) {
|
||||||
String stored = jedis.hget("uuid-cache", player.toString());
|
String stored = jedis.hget("uuid-cache", player.toString());
|
||||||
if (stored != null) {
|
if (stored != null) {
|
||||||
// Found an entry value. Deserialize it.
|
// Found an entry value. Deserialize it.
|
||||||
CachedUUIDEntry entry = RedisBungee.getGson().fromJson(stored, CachedUUIDEntry.class);
|
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
|
||||||
|
|
||||||
// Check for expiry:
|
// Check for expiry:
|
||||||
if (entry.expired()) {
|
if (entry.expired()) {
|
||||||
@ -147,7 +150,7 @@ public final class UUIDTranslator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expensiveLookups || !ProxyServer.getInstance().getConfig().isOnlineMode())
|
if (!expensiveLookups || !plugin.isOnlineMode())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// That didn't work. Let's ask Mojang. This call may fail, because Mojang is insane.
|
// That didn't work. Let's ask Mojang. This call may fail, because Mojang is insane.
|
||||||
@ -156,7 +159,7 @@ public final class UUIDTranslator {
|
|||||||
List<String> nameHist = NameFetcher.nameHistoryFromUuid(player);
|
List<String> nameHist = NameFetcher.nameHistoryFromUuid(player);
|
||||||
name = Iterables.getLast(nameHist, null);
|
name = Iterables.getLast(nameHist, null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to fetch name from Mojang for " + player, e);
|
plugin.logFatal("Unable to fetch name from Mojang for " + player);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,30 +170,46 @@ public final class UUIDTranslator {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (JedisException e) {
|
} catch (JedisException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Unable to fetch name for " + player, e);
|
plugin.logFatal("Unable to fetch name for " + player);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void persistInfo(String name, UUID uuid, Jedis jedis) {
|
public final void persistInfo(String name, UUID uuid, Jedis jedis) {
|
||||||
addToMaps(name, uuid);
|
addToMaps(name, uuid);
|
||||||
String json = RedisBungee.getGson().toJson(uuidToNameMap.get(uuid));
|
String json = gson.toJson(uuidToNameMap.get(uuid));
|
||||||
jedis.hmset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
|
jedis.hmset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void persistInfo(String name, UUID uuid, Pipeline jedis) {
|
public final void persistInfo(String name, UUID uuid, Pipeline jedis) {
|
||||||
addToMaps(name, uuid);
|
addToMaps(name, uuid);
|
||||||
String json = RedisBungee.getGson().toJson(uuidToNameMap.get(uuid));
|
String json = gson.toJson(uuidToNameMap.get(uuid));
|
||||||
jedis.hmset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
|
jedis.hmset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
private static class CachedUUIDEntry {
|
||||||
@Getter
|
|
||||||
private class CachedUUIDEntry {
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final UUID uuid;
|
private final UUID uuid;
|
||||||
private final Calendar expiry;
|
private final Calendar expiry;
|
||||||
|
|
||||||
|
public CachedUUIDEntry(String name, UUID uuid, Calendar expiry) {
|
||||||
|
this.name = name;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.expiry = expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Calendar getExpiry() {
|
||||||
|
return expiry;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean expired() {
|
public boolean expired() {
|
||||||
return Calendar.getInstance().after(expiry);
|
return Calendar.getInstance().after(expiry);
|
||||||
}
|
}
|
110
RedisBungee-Bungee/pom.xml
Normal file
110
RedisBungee-Bungee/pom.xml
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>RedisBungee</artifactId>
|
||||||
|
<groupId>com.imaginarycode.minecraft</groupId>
|
||||||
|
<version>0.7.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>RedisBungee-Bungee</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>bungeecord-repo</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.2.4</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>redis.clients.jedis</pattern>
|
||||||
|
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedis
|
||||||
|
</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>redis.clients.util</pattern>
|
||||||
|
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedisutil
|
||||||
|
</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.apache.commons.pool</pattern>
|
||||||
|
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.commonspool
|
||||||
|
</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.squareup.okhttp</pattern>
|
||||||
|
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okhttp
|
||||||
|
</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>okio</pattern>
|
||||||
|
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okio
|
||||||
|
</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>8</source>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imaginarycode.minecraft</groupId>
|
||||||
|
<artifactId>RedisBungee-API</artifactId>
|
||||||
|
<version>0.7.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-api</artifactId>
|
||||||
|
<version>1.17-R0.1-SNAPSHOT</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||||
|
import net.md_5.bungee.api.connection.PendingConnection;
|
||||||
|
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||||
|
import redis.clients.jedis.Pipeline;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class RBUtils {
|
||||||
|
|
||||||
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
|
protected static void createPlayer(ProxiedPlayer player, Pipeline pipeline, boolean fireEvent) {
|
||||||
|
createPlayer(player.getPendingConnection(), pipeline, fireEvent);
|
||||||
|
if (player.getServer() != null)
|
||||||
|
pipeline.hset("player:" + player.getUniqueId().toString(), "server", player.getServer().getInfo().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void createPlayer(PendingConnection connection, Pipeline pipeline, boolean fireEvent) {
|
||||||
|
Map<String, String> playerData = new HashMap<>(4);
|
||||||
|
playerData.put("online", "0");
|
||||||
|
playerData.put("ip", connection.getAddress().getAddress().getHostAddress());
|
||||||
|
playerData.put("proxy", RedisBungeeAPI.getRedisBungeeApi().getServerId());
|
||||||
|
|
||||||
|
pipeline.sadd("proxy:" + RedisBungeeAPI.getRedisBungeeApi().getServerId() + ":usersOnline", connection.getUniqueId().toString());
|
||||||
|
pipeline.hmset("player:" + connection.getUniqueId().toString(), playerData);
|
||||||
|
|
||||||
|
if (fireEvent) {
|
||||||
|
pipeline.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage(
|
||||||
|
connection.getUniqueId(), RedisBungeeAPI.getRedisBungeeApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
|
||||||
|
new DataManager.LoginPayload(connection.getAddress().getAddress()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
package com.imaginarycode.minecraft.redisbungee;
|
||||||
|
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.internal.AbstractRedisBungeeListener;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.events.bungee.PubSubMessageEvent;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.internal.RedisUtil;
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.internal.util.RedisCallable;
|
||||||
|
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||||
|
import net.md_5.bungee.api.event.*;
|
||||||
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.Pipeline;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RedisBungeeListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, PlayerDisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent> implements Listener {
|
||||||
|
|
||||||
|
|
||||||
|
public RedisBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
|
||||||
|
super(plugin, exemptAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EventHandler
|
||||||
|
public void onLogin(LoginEvent event) {
|
||||||
|
event.registerIntent((Plugin) plugin);
|
||||||
|
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||||
|
@Override
|
||||||
|
protected Void call(Jedis jedis) {
|
||||||
|
try {
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We make sure they aren't trying to use an existing player's name.
|
||||||
|
// This is problematic for online-mode servers as they always disconnect old clients.
|
||||||
|
if (plugin.isOnlineMode()) {
|
||||||
|
ProxiedPlayer player = (ProxiedPlayer) plugin.getPlayer(event.getConnection().getName());
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
// TODO: Make it accept a BaseComponent[] like everything else.
|
||||||
|
event.setCancelReason(ONLINE_MODE_RECONNECT);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String s : plugin.getServerIds()) {
|
||||||
|
if (jedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
// TODO: Make it accept a BaseComponent[] like everything else.
|
||||||
|
event.setCancelReason(ALREADY_LOGGED_IN);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
event.completeIntent((Plugin) plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EventHandler
|
||||||
|
public void onPostLogin(PostLoginEvent event) {
|
||||||
|
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||||
|
@Override
|
||||||
|
protected Void call(Jedis jedis) {
|
||||||
|
// this code was moved out from login event due being async..
|
||||||
|
// and it can be cancelled but it will show as false in redis-bungee
|
||||||
|
// which will register the player into the redis database.
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), pipeline);
|
||||||
|
RBUtils.createPlayer(event.getPlayer(), pipeline, false);
|
||||||
|
pipeline.sync();
|
||||||
|
// the end of moved code.
|
||||||
|
|
||||||
|
jedis.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage(
|
||||||
|
event.getPlayer().getUniqueId(), plugin.getApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
|
||||||
|
new DataManager.LoginPayload(event.getPlayer().getAddress().getAddress()))));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
|
||||||
|
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||||
|
@Override
|
||||||
|
protected Void call(Jedis jedis) {
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
RedisUtil.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), pipeline);
|
||||||
|
pipeline.sync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EventHandler
|
||||||
|
public void onServerChange(ServerConnectedEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EventHandler
|
||||||
|
public void onPing(ProxyPingEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EventHandler
|
||||||
|
public void onPluginMessage(PluginMessageEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@EventHandler
|
||||||
|
public void onPubSubMessage(PubSubMessageEvent event) {
|
||||||
|
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getApi().getServerId())) {
|
||||||
|
String message = event.getMessage();
|
||||||
|
if (message.startsWith("/"))
|
||||||
|
message = message.substring(1);
|
||||||
|
plugin.logInfo("Invoking command via PubSub: /" + message);
|
||||||
|
plugin.executeProxyCommand(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.events;
|
package com.imaginarycode.minecraft.redisbungee.events.bungee;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.ToString;
|
|
||||||
import net.md_5.bungee.api.plugin.Event;
|
import net.md_5.bungee.api.plugin.Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,12 +9,16 @@ import net.md_5.bungee.api.plugin.Event;
|
|||||||
*
|
*
|
||||||
* @since 0.2.6
|
* @since 0.2.6
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
|
||||||
@ToString
|
|
||||||
public class PubSubMessageEvent extends Event {
|
public class PubSubMessageEvent extends Event {
|
||||||
private final String channel;
|
private final String channel;
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
||||||
|
public PubSubMessageEvent(String channel, String message) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
public String getChannel() {
|
public String getChannel() {
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
94
pom.xml
94
pom.xml
@ -6,87 +6,34 @@
|
|||||||
|
|
||||||
<groupId>com.imaginarycode.minecraft</groupId>
|
<groupId>com.imaginarycode.minecraft</groupId>
|
||||||
<artifactId>RedisBungee</artifactId>
|
<artifactId>RedisBungee</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
<version>0.7.0-SNAPSHOT</version>
|
<version>0.7.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>bungeecord-repo</id>
|
|
||||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.1</version>
|
|
||||||
<configuration>
|
|
||||||
<source>1.8</source>
|
|
||||||
<target>1.8</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
<version>3.2.4</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<relocations>
|
|
||||||
<relocation>
|
|
||||||
<pattern>redis.clients.jedis</pattern>
|
|
||||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedis
|
|
||||||
</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>redis.clients.util</pattern>
|
|
||||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedisutil
|
|
||||||
</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>org.apache.commons.pool</pattern>
|
|
||||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.commonspool
|
|
||||||
</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>com.squareup.okhttp</pattern>
|
|
||||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okhttp
|
|
||||||
</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>okio</pattern>
|
|
||||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okio
|
|
||||||
</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
</relocations>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
|
||||||
<version>3.3.0</version>
|
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>8</source>
|
<source>8</source>
|
||||||
|
<target>8</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
<modules>
|
||||||
|
<module>RedisBungee-API</module>
|
||||||
|
<module>RedisBungee-Bungee</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>31.1-jre</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>redis.clients</groupId>
|
<groupId>redis.clients</groupId>
|
||||||
<artifactId>jedis</artifactId>
|
<artifactId>jedis</artifactId>
|
||||||
@ -99,19 +46,6 @@
|
|||||||
<version>2.11.1</version>
|
<version>2.11.1</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>net.md-5</groupId>
|
|
||||||
<artifactId>bungeecord-api</artifactId>
|
|
||||||
<version>1.17-R0.1-SNAPSHOT</version>
|
|
||||||
<type>jar</type>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<version>1.18.22</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp</groupId>
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
|
@ -1,595 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.collect.*;
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.util.*;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.util.uuid.NameFetcher;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDFetcher;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDTranslator;
|
|
||||||
import com.squareup.okhttp.Dispatcher;
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import net.md_5.bungee.api.ProxyServer;
|
|
||||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
|
||||||
import net.md_5.bungee.api.plugin.Plugin;
|
|
||||||
import net.md_5.bungee.config.Configuration;
|
|
||||||
import net.md_5.bungee.config.ConfigurationProvider;
|
|
||||||
import net.md_5.bungee.config.YamlConfiguration;
|
|
||||||
import redis.clients.jedis.*;
|
|
||||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The RedisBungee plugin.
|
|
||||||
* <p>
|
|
||||||
* The only function of interest is {@link #getApi()}, which deprecated now,
|
|
||||||
* Please check {@link RedisBungeeAPI#getRedisBungeeApi()},
|
|
||||||
*
|
|
||||||
* which exposes some functions in this class.
|
|
||||||
* but if you want old version support,
|
|
||||||
* then you can use old method {@link #getApi()}
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class RedisBungee extends Plugin {
|
|
||||||
@Getter
|
|
||||||
private static Gson gson = new Gson();
|
|
||||||
private static RedisBungeeAPI api;
|
|
||||||
@Getter(AccessLevel.PACKAGE)
|
|
||||||
private static PubSubListener psl = null;
|
|
||||||
@Getter
|
|
||||||
private JedisPool pool;
|
|
||||||
@Getter
|
|
||||||
private UUIDTranslator uuidTranslator;
|
|
||||||
@Getter(AccessLevel.PACKAGE)
|
|
||||||
private static RedisBungeeConfiguration configuration;
|
|
||||||
@Getter
|
|
||||||
private DataManager dataManager;
|
|
||||||
@Getter
|
|
||||||
private static OkHttpClient httpClient;
|
|
||||||
private volatile List<String> serverIds;
|
|
||||||
private final AtomicInteger nagAboutServers = new AtomicInteger();
|
|
||||||
private final AtomicInteger globalPlayerCount = new AtomicInteger();
|
|
||||||
private Future<?> integrityCheck;
|
|
||||||
private Future<?> heartbeatTask;
|
|
||||||
private LuaManager.Script serverToPlayersScript;
|
|
||||||
private LuaManager.Script getPlayerCountScript;
|
|
||||||
|
|
||||||
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
|
|
||||||
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(5, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the {@link RedisBungeeAPI} object created on plugin start.
|
|
||||||
*
|
|
||||||
* @deprecated Please use {@link RedisBungeeAPI#getRedisBungeeApi()}
|
|
||||||
*
|
|
||||||
* @return the {@link RedisBungeeAPI} object instance.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static RedisBungeeAPI getApi() {
|
|
||||||
return api;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PubSubListener getPubSubListener() {
|
|
||||||
return psl;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<String> getServerIds() {
|
|
||||||
return serverIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getCurrentServerIds(boolean nag, boolean lagged) {
|
|
||||||
try (Jedis jedis = pool.getResource()) {
|
|
||||||
long time = getRedisTime(jedis.time());
|
|
||||||
int nagTime = 0;
|
|
||||||
if (nag) {
|
|
||||||
nagTime = nagAboutServers.decrementAndGet();
|
|
||||||
if (nagTime <= 0) {
|
|
||||||
nagAboutServers.set(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImmutableList.Builder<String> servers = ImmutableList.builder();
|
|
||||||
Map<String, String> heartbeats = jedis.hgetAll("heartbeats");
|
|
||||||
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
|
|
||||||
try {
|
|
||||||
long stamp = Long.parseLong(entry.getValue());
|
|
||||||
if (lagged ? time >= stamp + 30 : time <= stamp + 30)
|
|
||||||
servers.add(entry.getKey());
|
|
||||||
else if (nag && nagTime <= 0) {
|
|
||||||
getLogger().warning(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat.");
|
|
||||||
jedis.hdel("heartbeats", entry.getKey());
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers.build();
|
|
||||||
} catch (JedisConnectionException e) {
|
|
||||||
getLogger().log(Level.SEVERE, "Unable to fetch server IDs", e);
|
|
||||||
return Collections.singletonList(configuration.getServerId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<UUID> getPlayersOnProxy(String server) {
|
|
||||||
checkArgument(getServerIds().contains(server), server + " is not a valid proxy ID");
|
|
||||||
try (Jedis jedis = pool.getResource()) {
|
|
||||||
Set<String> users = jedis.smembers("proxy:" + server + ":usersOnline");
|
|
||||||
ImmutableSet.Builder<UUID> builder = ImmutableSet.builder();
|
|
||||||
for (String user : users) {
|
|
||||||
builder.add(UUID.fromString(user));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Multimap<String, UUID> serversToPlayers() {
|
|
||||||
try {
|
|
||||||
return serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, new Callable<Multimap<String, UUID>>() {
|
|
||||||
@Override
|
|
||||||
public Multimap<String, UUID> call() throws Exception {
|
|
||||||
Collection<String> data = (Collection<String>) serverToPlayersScript.eval(ImmutableList.<String>of(), getServerIds());
|
|
||||||
|
|
||||||
ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
|
|
||||||
String key = null;
|
|
||||||
for (String s : data) {
|
|
||||||
if (key == null) {
|
|
||||||
key = s;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.put(key, UUID.fromString(s));
|
|
||||||
key = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final int getCount() {
|
|
||||||
return globalPlayerCount.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
final int getCurrentCount() {
|
|
||||||
Long count = (Long) getPlayerCountScript.eval(ImmutableList.<String>of(), ImmutableList.<String>of());
|
|
||||||
return count.intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getLocalPlayersAsUuidStrings() {
|
|
||||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
|
||||||
for (ProxiedPlayer player : getProxy().getPlayers()) {
|
|
||||||
builder.add(player.getUniqueId().toString());
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Set<UUID> getPlayers() {
|
|
||||||
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
|
|
||||||
if (pool != null) {
|
|
||||||
try (Jedis rsc = pool.getResource()) {
|
|
||||||
List<String> keys = new ArrayList<>();
|
|
||||||
for (String i : getServerIds()) {
|
|
||||||
keys.add("proxy:" + i + ":usersOnline");
|
|
||||||
}
|
|
||||||
if (!keys.isEmpty()) {
|
|
||||||
Set<String> users = rsc.sunion(keys.toArray(new String[keys.size()]));
|
|
||||||
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!
|
|
||||||
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
|
|
||||||
throw new RuntimeException("Unable to get all players online", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return setBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) {
|
|
||||||
checkArgument(getServerIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid");
|
|
||||||
sendChannelMessage("redisbungee-" + proxyId, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
final void sendChannelMessage(String channel, String message) {
|
|
||||||
try (Jedis jedis = pool.getResource()) {
|
|
||||||
jedis.publish(channel, message);
|
|
||||||
} catch (JedisConnectionException e) {
|
|
||||||
// Redis server has disappeared!
|
|
||||||
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
|
|
||||||
throw new RuntimeException("Unable to publish channel message", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getRedisTime(List<String> timeRes) {
|
|
||||||
return Long.parseLong(timeRes.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
ThreadFactory factory = ((ThreadPoolExecutor) getExecutorService()).getThreadFactory();
|
|
||||||
ScheduledExecutorService service = Executors.newScheduledThreadPool(24, factory);
|
|
||||||
try {
|
|
||||||
Field field = Plugin.class.getDeclaredField("service");
|
|
||||||
field.setAccessible(true);
|
|
||||||
ExecutorService builtinService = (ExecutorService) field.get(this);
|
|
||||||
field.set(this, service);
|
|
||||||
builtinService.shutdownNow();
|
|
||||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
|
||||||
getLogger().log(Level.WARNING, "Can't replace BungeeCord thread pool with our own");
|
|
||||||
getLogger().log(Level.INFO, "skipping replacement.....");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
loadConfig();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to load/save config", e);
|
|
||||||
} catch (JedisConnectionException e) {
|
|
||||||
throw new RuntimeException("Unable to connect to your Redis server!", e);
|
|
||||||
}
|
|
||||||
if (pool != null) {
|
|
||||||
try (Jedis tmpRsc = pool.getResource()) {
|
|
||||||
// This is more portable than INFO <section>
|
|
||||||
String info = tmpRsc.info();
|
|
||||||
for (String s : info.split("\r\n")) {
|
|
||||||
if (s.startsWith("redis_version:")) {
|
|
||||||
String version = s.split(":")[1];
|
|
||||||
getLogger().info(version + " <- redis version");
|
|
||||||
if (!RedisUtil.isRedisVersionRight(version)) {
|
|
||||||
getLogger().warning("Your version of Redis (" + version + ") is not at least version 6.0 RedisBungee requires a newer version of Redis.");
|
|
||||||
throw new RuntimeException("Unsupported Redis version detected");
|
|
||||||
} else {
|
|
||||||
LuaManager manager = new LuaManager(this);
|
|
||||||
serverToPlayersScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/server_to_players.lua")));
|
|
||||||
getPlayerCountScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/get_player_count.lua")));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpRsc.hset("heartbeats", configuration.getServerId(), tmpRsc.time().get(0));
|
|
||||||
|
|
||||||
long uuidCacheSize = tmpRsc.hlen("uuid-cache");
|
|
||||||
if (uuidCacheSize > 750000) {
|
|
||||||
getLogger().info("Looks like you have a really big UUID cache! Run https://www.spigotmc.org/resources/redisbungeecleaner.8505/ as soon as possible.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serverIds = getCurrentServerIds(true, false);
|
|
||||||
uuidTranslator = new UUIDTranslator(this);
|
|
||||||
heartbeatTask = service.scheduleAtFixedRate(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try (Jedis rsc = pool.getResource()) {
|
|
||||||
long redisTime = getRedisTime(rsc.time());
|
|
||||||
rsc.hset("heartbeats", configuration.getServerId(), String.valueOf(redisTime));
|
|
||||||
} catch (JedisConnectionException e) {
|
|
||||||
// Redis server has disappeared!
|
|
||||||
getLogger().log(Level.SEVERE, "Unable to update heartbeat - did your Redis server go away?", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
serverIds = getCurrentServerIds(true, false);
|
|
||||||
globalPlayerCount.set(getCurrentCount());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
getLogger().log(Level.SEVERE, "Unable to update data - did your Redis server go away?", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0, 3, TimeUnit.SECONDS);
|
|
||||||
dataManager = new DataManager(this);
|
|
||||||
if (configuration.isRegisterBungeeCommands()) {
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand(this));
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand(this));
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand(this));
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand(this));
|
|
||||||
}
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll(this));
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId(this));
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerIds());
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlayerProxyCommand(this));
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlistCommand(this));
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.DebugCommand(this));
|
|
||||||
api = new RedisBungeeAPI(this);
|
|
||||||
getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this, configuration.getExemptAddresses()));
|
|
||||||
getProxy().getPluginManager().registerListener(this, dataManager);
|
|
||||||
psl = new PubSubListener();
|
|
||||||
getProxy().getScheduler().runAsync(this, psl);
|
|
||||||
integrityCheck = service.scheduleAtFixedRate(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try (Jedis tmpRsc = pool.getResource()) {
|
|
||||||
Set<String> players = getLocalPlayersAsUuidStrings();
|
|
||||||
Set<String> playersInRedis = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
|
|
||||||
List<String> lagged = getCurrentServerIds(false, true);
|
|
||||||
|
|
||||||
// Clean up lagged players.
|
|
||||||
for (String s : lagged) {
|
|
||||||
Set<String> laggedPlayers = tmpRsc.smembers("proxy:" + s + ":usersOnline");
|
|
||||||
tmpRsc.del("proxy:" + s + ":usersOnline");
|
|
||||||
if (!laggedPlayers.isEmpty()) {
|
|
||||||
getLogger().info("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)...");
|
|
||||||
for (String laggedPlayer : laggedPlayers) {
|
|
||||||
RedisUtil.cleanUpPlayer(laggedPlayer, tmpRsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 : getServerIds()) {
|
|
||||||
if (proxyId.equals(configuration.getServerId())) continue;
|
|
||||||
if (tmpRsc.sismember("proxy:" + proxyId + ":usersOnline", member)) {
|
|
||||||
// Just clean up the set.
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
|
||||||
getLogger().warning("Player found in set that was not found locally and globally: " + member);
|
|
||||||
} else {
|
|
||||||
tmpRsc.srem("proxy:" + configuration.getServerId() + ":usersOnline", member);
|
|
||||||
getLogger().warning("Player found in set that was not found locally, but is on another proxy: " + member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Pipeline pipeline = tmpRsc.pipelined();
|
|
||||||
|
|
||||||
for (String player : absentInRedis) {
|
|
||||||
// Player not online according to Redis but not BungeeCord.
|
|
||||||
getLogger().warning("Player " + player + " is on the proxy but not in Redis.");
|
|
||||||
|
|
||||||
ProxiedPlayer proxiedPlayer = ProxyServer.getInstance().getPlayer(UUID.fromString(player));
|
|
||||||
if (proxiedPlayer == null)
|
|
||||||
continue; // We'll deal with it later.
|
|
||||||
|
|
||||||
RedisUtil.createPlayer(proxiedPlayer, pipeline, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline.sync();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
getLogger().log(Level.SEVERE, "Unable to fix up stored player data", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0, 1, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
getProxy().registerChannel("legacy:redisbungee");
|
|
||||||
getProxy().registerChannel("RedisBungee");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
if (pool != null) {
|
|
||||||
// Poison the PubSub listener
|
|
||||||
psl.poison();
|
|
||||||
integrityCheck.cancel(true);
|
|
||||||
heartbeatTask.cancel(true);
|
|
||||||
getProxy().getPluginManager().unregisterListeners(this);
|
|
||||||
|
|
||||||
try (Jedis tmpRsc = pool.getResource()) {
|
|
||||||
tmpRsc.hdel("heartbeats", configuration.getServerId());
|
|
||||||
if (tmpRsc.scard("proxy:" + configuration.getServerId() + ":usersOnline") > 0) {
|
|
||||||
Set<String> players = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
|
|
||||||
for (String member : players)
|
|
||||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pool.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadConfig() throws IOException, JedisConnectionException {
|
|
||||||
if (!getDataFolder().exists()) {
|
|
||||||
getDataFolder().mkdir();
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(getDataFolder(), "config.yml");
|
|
||||||
|
|
||||||
if (!file.exists()) {
|
|
||||||
file.createNewFile();
|
|
||||||
try (InputStream in = getResourceAsStream("example_config.yml");
|
|
||||||
OutputStream out = new FileOutputStream(file)) {
|
|
||||||
ByteStreams.copy(in, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Configuration configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file);
|
|
||||||
|
|
||||||
final String redisServer = configuration.getString("redis-server", "localhost");
|
|
||||||
final int redisPort = configuration.getInt("redis-port", 6379);
|
|
||||||
final boolean useSSL = configuration.getBoolean("useSSL");
|
|
||||||
String redisPassword = configuration.getString("redis-password");
|
|
||||||
String serverId = configuration.getString("server-id");
|
|
||||||
final String randomUUID = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
if (redisPassword != null && (redisPassword.isEmpty() || redisPassword.equals("none"))) {
|
|
||||||
redisPassword = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration sanity checks.
|
|
||||||
if (serverId == null || serverId.isEmpty()) {
|
|
||||||
/*
|
|
||||||
* this check causes the config comments to disappear somehow
|
|
||||||
* I think due snake yaml limitations so as todo: write our own yaml parser?
|
|
||||||
*/
|
|
||||||
String genId = UUID.randomUUID().toString();
|
|
||||||
getLogger().info("Generated server id " + genId + " and saving it to config.");
|
|
||||||
configuration.set("server-id", genId);
|
|
||||||
ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, new File(getDataFolder(), "config.yml"));
|
|
||||||
} else {
|
|
||||||
getLogger().info("Loaded server id " + serverId + '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.getBoolean("use-random-id-string", false)) {
|
|
||||||
serverId = configuration.getString("server-id") + "-" + randomUUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redisServer != null && !redisServer.isEmpty()) {
|
|
||||||
final String finalRedisPassword = redisPassword;
|
|
||||||
FutureTask<JedisPool> task = new FutureTask<>(new Callable<JedisPool>() {
|
|
||||||
@Override
|
|
||||||
public JedisPool call() throws Exception {
|
|
||||||
// Create the pool...
|
|
||||||
JedisPoolConfig config = new JedisPoolConfig();
|
|
||||||
config.setMaxTotal(configuration.getInt("max-redis-connections", 8));
|
|
||||||
return new JedisPool(config, redisServer, redisPort, 0, finalRedisPassword, useSSL);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
getProxy().getScheduler().runAsync(this, task);
|
|
||||||
|
|
||||||
try {
|
|
||||||
pool = task.get();
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
throw new RuntimeException("Unable to create Redis pool", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the connection
|
|
||||||
try (Jedis rsc = pool.getResource()) {
|
|
||||||
rsc.ping();
|
|
||||||
// If that worked, now we can check for an existing, alive Bungee:
|
|
||||||
File crashFile = new File(getDataFolder(), "restarted_from_crash.txt");
|
|
||||||
if (crashFile.exists()) {
|
|
||||||
crashFile.delete();
|
|
||||||
} else if (rsc.hexists("heartbeats", serverId)) {
|
|
||||||
try {
|
|
||||||
long value = Long.parseLong(rsc.hget("heartbeats", serverId));
|
|
||||||
long redisTime = getRedisTime(rsc.time());
|
|
||||||
if (redisTime < value + 20) {
|
|
||||||
getLogger().severe("You have launched a possible impostor BungeeCord instance. Another instance is already running.");
|
|
||||||
getLogger().severe("For data consistency reasons, RedisBungee will now disable itself.");
|
|
||||||
getLogger().severe("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.");
|
|
||||||
throw new RuntimeException("Possible impostor instance!");
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureTask<Void> task2 = new FutureTask<>(new Callable<Void>() {
|
|
||||||
@Override
|
|
||||||
public Void call() throws Exception {
|
|
||||||
httpClient = new OkHttpClient();
|
|
||||||
Dispatcher dispatcher = new Dispatcher(getExecutorService());
|
|
||||||
httpClient.setDispatcher(dispatcher);
|
|
||||||
NameFetcher.setHttpClient(httpClient);
|
|
||||||
UUIDFetcher.setHttpClient(httpClient);
|
|
||||||
RedisBungee.configuration = new RedisBungeeConfiguration(RedisBungee.this.getPool(), configuration, randomUUID);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
getProxy().getScheduler().runAsync(this, task2);
|
|
||||||
|
|
||||||
try {
|
|
||||||
task2.get();
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
throw new RuntimeException("Unable to create HTTP client", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
getLogger().log(Level.INFO, "Successfully connected to Redis.");
|
|
||||||
} catch (JedisConnectionException e) {
|
|
||||||
pool.destroy();
|
|
||||||
pool = null;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("No redis server specified!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
class PubSubListener implements Runnable {
|
|
||||||
private JedisPubSubHandler jpsh;
|
|
||||||
|
|
||||||
private Set<String> addedChannels = new HashSet<String>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
boolean broken = false;
|
|
||||||
try (Jedis rsc = pool.getResource()) {
|
|
||||||
try {
|
|
||||||
jpsh = new JedisPubSubHandler();
|
|
||||||
addedChannels.add("redisbungee-" + configuration.getServerId());
|
|
||||||
addedChannels.add("redisbungee-allservers");
|
|
||||||
addedChannels.add("redisbungee-data");
|
|
||||||
rsc.subscribe(jpsh, addedChannels.toArray(new String[0]));
|
|
||||||
} catch (Exception e) {
|
|
||||||
// FIXME: Extremely ugly hack
|
|
||||||
// Attempt to unsubscribe this instance and try again.
|
|
||||||
getLogger().log(Level.INFO, "PubSub error, attempting to recover.", e);
|
|
||||||
try {
|
|
||||||
jpsh.unsubscribe();
|
|
||||||
} catch (Exception e1) {
|
|
||||||
/* This may fail with
|
|
||||||
- java.net.SocketException: Broken pipe
|
|
||||||
- redis.clients.jedis.exceptions.JedisConnectionException: JedisPubSub was not subscribed to a Jedis instance
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
broken = true;
|
|
||||||
}
|
|
||||||
} catch (JedisConnectionException e) {
|
|
||||||
getLogger().log(Level.INFO, "PubSub error, attempting to recover in 5 secs.");
|
|
||||||
getProxy().getScheduler().schedule(RedisBungee.this, PubSubListener.this, 5, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (broken) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addChannel(String... channel) {
|
|
||||||
addedChannels.addAll(Arrays.asList(channel));
|
|
||||||
jpsh.subscribe(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeChannel(String... channel) {
|
|
||||||
addedChannels.removeAll(Arrays.asList(channel));
|
|
||||||
jpsh.unsubscribe(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void poison() {
|
|
||||||
addedChannels.clear();
|
|
||||||
jpsh.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class JedisPubSubHandler extends JedisPubSub {
|
|
||||||
@Override
|
|
||||||
public void onMessage(final String s, final String s2) {
|
|
||||||
if (s2.trim().length() == 0) return;
|
|
||||||
getProxy().getScheduler().runAsync(RedisBungee.this, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
getProxy().getPluginManager().callEvent(new PubSubMessageEvent(s, s2));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee;
|
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import net.md_5.bungee.api.CommandSender;
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is the CommandSender that RedisBungee uses to dispatch commands to BungeeCord.
|
|
||||||
* <p>
|
|
||||||
* It inherits all permissions of the console command sender. Sending messages and modifying permissions are no-ops.
|
|
||||||
*
|
|
||||||
* @author tuxed
|
|
||||||
* @since 0.2.3
|
|
||||||
*/
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class RedisBungeeCommandSender implements CommandSender {
|
|
||||||
static final RedisBungeeCommandSender instance = new RedisBungeeCommandSender();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "RedisBungee";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(String s) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessages(String... strings) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(BaseComponent... baseComponents) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(BaseComponent baseComponent) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getGroups() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addGroups(String... strings) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeGroups(String... strings) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasPermission(String s) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPermission(String s, boolean b) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getPermissions() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,356 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.collect.HashMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import net.md_5.bungee.api.CommandSender;
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
|
||||||
import net.md_5.bungee.api.chat.TextComponent;
|
|
||||||
import net.md_5.bungee.api.config.ServerInfo;
|
|
||||||
import net.md_5.bungee.api.plugin.Command;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
|
|
||||||
* <p>
|
|
||||||
* All classes use the {@link RedisBungeeAPI}.
|
|
||||||
*
|
|
||||||
* @author tuxed
|
|
||||||
* @since 0.2.3
|
|
||||||
*/
|
|
||||||
class RedisBungeeCommands {
|
|
||||||
private static final BaseComponent[] NO_PLAYER_SPECIFIED =
|
|
||||||
new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create();
|
|
||||||
private static final BaseComponent[] PLAYER_NOT_FOUND =
|
|
||||||
new ComponentBuilder("No such player found.").color(ChatColor.RED).create();
|
|
||||||
private static final BaseComponent[] NO_COMMAND_SPECIFIED =
|
|
||||||
new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
|
|
||||||
|
|
||||||
private static String playerPlural(int num) {
|
|
||||||
return num == 1 ? num + " player is" : num + " players are";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GlistCommand extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
GlistCommand(RedisBungee plugin) {
|
|
||||||
super("glist", "bungeecord.command.list", "redisbungee", "rglist");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(final CommandSender sender, final String[] args) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
int count = RedisBungee.getApi().getPlayerCount();
|
|
||||||
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
|
|
||||||
.append(playerPlural(count) + " currently online.").create();
|
|
||||||
if (args.length > 0 && args[0].equals("showall")) {
|
|
||||||
Multimap<String, UUID> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
|
|
||||||
Multimap<String, String> human = HashMultimap.create();
|
|
||||||
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
|
|
||||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
|
||||||
}
|
|
||||||
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
|
|
||||||
TextComponent serverName = new TextComponent();
|
|
||||||
serverName.setColor(ChatColor.GREEN);
|
|
||||||
serverName.setText("[" + server + "] ");
|
|
||||||
TextComponent serverCount = new TextComponent();
|
|
||||||
serverCount.setColor(ChatColor.YELLOW);
|
|
||||||
serverCount.setText("(" + serverToPlayers.get(server).size() + "): ");
|
|
||||||
TextComponent serverPlayers = new TextComponent();
|
|
||||||
serverPlayers.setColor(ChatColor.WHITE);
|
|
||||||
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
|
|
||||||
sender.sendMessage(serverName, serverCount, serverPlayers);
|
|
||||||
}
|
|
||||||
sender.sendMessage(playersOnline);
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(playersOnline);
|
|
||||||
sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class FindCommand extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
FindCommand(RedisBungee plugin) {
|
|
||||||
super("find", "bungeecord.command.find", "rfind");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(final CommandSender sender, final String[] args) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (args.length > 0) {
|
|
||||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
|
||||||
if (uuid == null) {
|
|
||||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ServerInfo si = RedisBungee.getApi().getServerFor(uuid);
|
|
||||||
if (si != null) {
|
|
||||||
TextComponent message = new TextComponent();
|
|
||||||
message.setColor(ChatColor.BLUE);
|
|
||||||
message.setText(args[0] + " is on " + si.getName() + ".");
|
|
||||||
sender.sendMessage(message);
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LastSeenCommand extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
LastSeenCommand(RedisBungee plugin) {
|
|
||||||
super("lastseen", "redisbungee.command.lastseen", "rlastseen");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(final CommandSender sender, final String[] args) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (args.length > 0) {
|
|
||||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
|
||||||
if (uuid == null) {
|
|
||||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
long secs = RedisBungee.getApi().getLastOnline(uuid);
|
|
||||||
TextComponent message = new TextComponent();
|
|
||||||
if (secs == 0) {
|
|
||||||
message.setColor(ChatColor.GREEN);
|
|
||||||
message.setText(args[0] + " is currently online.");
|
|
||||||
} else if (secs != -1) {
|
|
||||||
message.setColor(ChatColor.BLUE);
|
|
||||||
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
|
|
||||||
} else {
|
|
||||||
message.setColor(ChatColor.RED);
|
|
||||||
message.setText(args[0] + " has never been online.");
|
|
||||||
}
|
|
||||||
sender.sendMessage(message);
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class IpCommand extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
IpCommand(RedisBungee plugin) {
|
|
||||||
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(final CommandSender sender, final String[] args) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (args.length > 0) {
|
|
||||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
|
||||||
if (uuid == null) {
|
|
||||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
InetAddress ia = RedisBungee.getApi().getPlayerIp(uuid);
|
|
||||||
if (ia != null) {
|
|
||||||
TextComponent message = new TextComponent();
|
|
||||||
message.setColor(ChatColor.GREEN);
|
|
||||||
message.setText(args[0] + " is connected from " + ia.toString() + ".");
|
|
||||||
sender.sendMessage(message);
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PlayerProxyCommand extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
PlayerProxyCommand(RedisBungee plugin) {
|
|
||||||
super("pproxy", "redisbungee.command.pproxy");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(final CommandSender sender, final String[] args) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (args.length > 0) {
|
|
||||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
|
||||||
if (uuid == null) {
|
|
||||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String proxy = RedisBungee.getApi().getProxy(uuid);
|
|
||||||
if (proxy != null) {
|
|
||||||
TextComponent message = new TextComponent();
|
|
||||||
message.setColor(ChatColor.GREEN);
|
|
||||||
message.setText(args[0] + " is connected to " + proxy + ".");
|
|
||||||
sender.sendMessage(message);
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SendToAll extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
SendToAll(RedisBungee plugin) {
|
|
||||||
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(CommandSender sender, String[] args) {
|
|
||||||
if (args.length > 0) {
|
|
||||||
String command = Joiner.on(" ").skipNulls().join(args);
|
|
||||||
RedisBungee.getApi().sendProxyCommand(command);
|
|
||||||
TextComponent message = new TextComponent();
|
|
||||||
message.setColor(ChatColor.GREEN);
|
|
||||||
message.setText("Sent the command /" + command + " to all proxies.");
|
|
||||||
sender.sendMessage(message);
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(NO_COMMAND_SPECIFIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ServerId extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
ServerId(RedisBungee plugin) {
|
|
||||||
super("serverid", "redisbungee.command.serverid", "rserverid");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(CommandSender sender, String[] args) {
|
|
||||||
TextComponent textComponent = new TextComponent();
|
|
||||||
textComponent.setText("You are on " + RedisBungee.getApi().getServerId() + ".");
|
|
||||||
textComponent.setColor(ChatColor.YELLOW);
|
|
||||||
sender.sendMessage(textComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ServerIds extends Command {
|
|
||||||
public ServerIds() {
|
|
||||||
super("serverids", "redisbungee.command.serverids");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(CommandSender sender, String[] strings) {
|
|
||||||
TextComponent textComponent = new TextComponent();
|
|
||||||
textComponent.setText("All server IDs: " + Joiner.on(", ").join(RedisBungee.getApi().getAllServers()));
|
|
||||||
textComponent.setColor(ChatColor.YELLOW);
|
|
||||||
sender.sendMessage(textComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PlistCommand extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
PlistCommand(RedisBungee plugin) {
|
|
||||||
super("plist", "redisbungee.command.plist", "rplist");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(final CommandSender sender, final String[] args) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String proxy = args.length >= 1 ? args[0] : RedisBungee.getConfiguration().getServerId();
|
|
||||||
if (!plugin.getServerIds().contains(proxy)) {
|
|
||||||
sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Set<UUID> players = RedisBungee.getApi().getPlayersOnProxy(proxy);
|
|
||||||
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
|
|
||||||
.append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create();
|
|
||||||
if (args.length >= 2 && args[1].equals("showall")) {
|
|
||||||
Multimap<String, UUID> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
|
|
||||||
Multimap<String, String> human = HashMultimap.create();
|
|
||||||
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
|
|
||||||
if (players.contains(entry.getValue())) {
|
|
||||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String server : new TreeSet<>(human.keySet())) {
|
|
||||||
TextComponent serverName = new TextComponent();
|
|
||||||
serverName.setColor(ChatColor.RED);
|
|
||||||
serverName.setText("[" + server + "] ");
|
|
||||||
TextComponent serverCount = new TextComponent();
|
|
||||||
serverCount.setColor(ChatColor.YELLOW);
|
|
||||||
serverCount.setText("(" + human.get(server).size() + "): ");
|
|
||||||
TextComponent serverPlayers = new TextComponent();
|
|
||||||
serverPlayers.setColor(ChatColor.WHITE);
|
|
||||||
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
|
|
||||||
sender.sendMessage(serverName, serverCount, serverPlayers);
|
|
||||||
}
|
|
||||||
sender.sendMessage(playersOnline);
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(playersOnline);
|
|
||||||
sender.sendMessage(new ComponentBuilder("To see all players online, use /plist " + proxy + " showall.").color(ChatColor.YELLOW).create());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DebugCommand extends Command {
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
|
|
||||||
DebugCommand(RedisBungee plugin) {
|
|
||||||
super("rdebug", "redisbungee.command.debug");
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(final CommandSender sender, final String[] args) {
|
|
||||||
TextComponent poolActiveStat = new TextComponent("Currently active pool objects: " + plugin.getPool().getNumActive());
|
|
||||||
TextComponent poolIdleStat = new TextComponent("Currently idle pool objects: " + plugin.getPool().getNumIdle());
|
|
||||||
TextComponent poolWaitingStat = new TextComponent("Waiting on free objects: " + plugin.getPool().getNumWaiters());
|
|
||||||
sender.sendMessage(poolActiveStat);
|
|
||||||
sender.sendMessage(poolIdleStat);
|
|
||||||
sender.sendMessage(poolWaitingStat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.net.InetAddresses;
|
|
||||||
import lombok.Getter;
|
|
||||||
import net.md_5.bungee.config.Configuration;
|
|
||||||
import redis.clients.jedis.JedisPool;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class RedisBungeeConfiguration {
|
|
||||||
@Getter
|
|
||||||
private final JedisPool pool;
|
|
||||||
@Getter
|
|
||||||
private final String serverId;
|
|
||||||
@Getter
|
|
||||||
private final boolean registerBungeeCommands;
|
|
||||||
@Getter
|
|
||||||
private final List<InetAddress> exemptAddresses;
|
|
||||||
|
|
||||||
|
|
||||||
public RedisBungeeConfiguration(JedisPool pool, Configuration configuration, String randomUUID) {
|
|
||||||
this.pool = pool;
|
|
||||||
if (configuration.getBoolean("use-random-id-string", false)) {
|
|
||||||
this.serverId = configuration.getString("server-id") + "-" + randomUUID;
|
|
||||||
} else {
|
|
||||||
this.serverId = configuration.getString("server-id");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registerBungeeCommands = configuration.getBoolean("register-bungee-commands", true);
|
|
||||||
|
|
||||||
List<String> stringified = configuration.getStringList("exempt-ip-addresses");
|
|
||||||
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
|
|
||||||
|
|
||||||
for (String s : stringified) {
|
|
||||||
addressBuilder.add(InetAddresses.forString(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.exemptAddresses = addressBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,291 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.collect.HashMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import com.google.common.collect.Multiset;
|
|
||||||
import com.google.common.io.ByteArrayDataInput;
|
|
||||||
import com.google.common.io.ByteArrayDataOutput;
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.util.RedisCallable;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import net.md_5.bungee.api.AbstractReconnectHandler;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
|
||||||
import net.md_5.bungee.api.config.ServerInfo;
|
|
||||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
|
||||||
import net.md_5.bungee.api.connection.Server;
|
|
||||||
import net.md_5.bungee.api.event.*;
|
|
||||||
import net.md_5.bungee.api.plugin.Listener;
|
|
||||||
import net.md_5.bungee.event.EventHandler;
|
|
||||||
import net.md_5.bungee.event.EventPriority;
|
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
import redis.clients.jedis.Pipeline;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class RedisBungeeListener implements Listener {
|
|
||||||
private static final BaseComponent[] ALREADY_LOGGED_IN =
|
|
||||||
new ComponentBuilder("You are already logged on to this server.").color(ChatColor.RED)
|
|
||||||
.append("\n\nIt may help to try logging in again in a few minutes.\nIf this does not resolve your issue, please contact staff.")
|
|
||||||
.color(ChatColor.GRAY)
|
|
||||||
.create();
|
|
||||||
private static final BaseComponent[] ONLINE_MODE_RECONNECT =
|
|
||||||
new ComponentBuilder("Whoops! You need to reconnect.").color(ChatColor.RED)
|
|
||||||
.append("\n\nWe found someone online using your username. They were kicked and you may reconnect.\nIf this does not work, please contact staff.")
|
|
||||||
.color(ChatColor.GRAY)
|
|
||||||
.create();
|
|
||||||
private final RedisBungee plugin;
|
|
||||||
private final List<InetAddress> exemptAddresses;
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
|
||||||
public void onLogin(final LoginEvent event) {
|
|
||||||
event.registerIntent(plugin);
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
|
|
||||||
@Override
|
|
||||||
protected Void call(Jedis jedis) {
|
|
||||||
try {
|
|
||||||
if (event.isCancelled()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We make sure they aren't trying to use an existing player's name.
|
|
||||||
// This is problematic for online-mode servers as they always disconnect old clients.
|
|
||||||
if (plugin.getProxy().getConfig().isOnlineMode()) {
|
|
||||||
ProxiedPlayer player = plugin.getProxy().getPlayer(event.getConnection().getName());
|
|
||||||
|
|
||||||
if (player != null) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
// TODO: Make it accept a BaseComponent[] like everything else.
|
|
||||||
event.setCancelReason(ONLINE_MODE_RECONNECT);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String s : plugin.getServerIds()) {
|
|
||||||
if (jedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
// TODO: Make it accept a BaseComponent[] like everything else.
|
|
||||||
event.setCancelReason(ALREADY_LOGGED_IN);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
event.completeIntent(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPostLogin(final PostLoginEvent event) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
|
|
||||||
@Override
|
|
||||||
protected Void call(Jedis jedis) {
|
|
||||||
// this code was moved out from login event due being async..
|
|
||||||
// and it can be cancelled but it will show as false in redis-bungee
|
|
||||||
// which will register the player into the redis database.
|
|
||||||
Pipeline pipeline = jedis.pipelined();
|
|
||||||
plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), pipeline);
|
|
||||||
RedisUtil.createPlayer(event.getPlayer(), pipeline, false);
|
|
||||||
pipeline.sync();
|
|
||||||
// the end of moved code.
|
|
||||||
|
|
||||||
jedis.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
|
|
||||||
event.getPlayer().getUniqueId(), DataManager.DataManagerMessage.Action.JOIN,
|
|
||||||
new DataManager.LoginPayload(event.getPlayer().getAddress().getAddress()))));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPlayerDisconnect(final PlayerDisconnectEvent event) {
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
|
|
||||||
@Override
|
|
||||||
protected Void call(Jedis jedis) {
|
|
||||||
Pipeline pipeline = jedis.pipelined();
|
|
||||||
RedisUtil.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), pipeline);
|
|
||||||
pipeline.sync();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onServerChange(final ServerConnectedEvent event) {
|
|
||||||
final String currentServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName();
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
|
|
||||||
@Override
|
|
||||||
protected Void call(Jedis jedis) {
|
|
||||||
jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName());
|
|
||||||
jedis.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
|
|
||||||
event.getPlayer().getUniqueId(), DataManager.DataManagerMessage.Action.SERVER_CHANGE,
|
|
||||||
new DataManager.ServerChangePayload(event.getServer().getInfo().getName(), currentServer))));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
|
||||||
public void onPing(final ProxyPingEvent event) {
|
|
||||||
if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
|
|
||||||
|
|
||||||
if (forced != null && event.getConnection().getListener().isPingPassthrough()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.getResponse().getPlayers().setOnline(plugin.getCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
|
||||||
@EventHandler
|
|
||||||
public void onPluginMessage(final PluginMessageEvent event) {
|
|
||||||
if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) {
|
|
||||||
final String currentChannel = event.getTag();
|
|
||||||
final byte[] data = Arrays.copyOf(event.getData(), event.getData().length);
|
|
||||||
plugin.getProxy().getScheduler().runAsync(plugin, () -> {
|
|
||||||
ByteArrayDataInput in = ByteStreams.newDataInput(data);
|
|
||||||
|
|
||||||
String subchannel = in.readUTF();
|
|
||||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
|
||||||
String type;
|
|
||||||
|
|
||||||
switch (subchannel) {
|
|
||||||
case "PlayerList":
|
|
||||||
out.writeUTF("PlayerList");
|
|
||||||
Set<UUID> original = Collections.emptySet();
|
|
||||||
type = in.readUTF();
|
|
||||||
if (type.equals("ALL")) {
|
|
||||||
out.writeUTF("ALL");
|
|
||||||
original = plugin.getPlayers();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
original = RedisBungee.getApi().getPlayersOnServer(type);
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Set<String> players = new HashSet<>();
|
|
||||||
for (UUID uuid : original)
|
|
||||||
players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false));
|
|
||||||
out.writeUTF(Joiner.on(',').join(players));
|
|
||||||
break;
|
|
||||||
case "PlayerCount":
|
|
||||||
out.writeUTF("PlayerCount");
|
|
||||||
type = in.readUTF();
|
|
||||||
if (type.equals("ALL")) {
|
|
||||||
out.writeUTF("ALL");
|
|
||||||
out.writeInt(plugin.getCount());
|
|
||||||
} else {
|
|
||||||
out.writeUTF(type);
|
|
||||||
try {
|
|
||||||
out.writeInt(RedisBungee.getApi().getPlayersOnServer(type).size());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
out.writeInt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "LastOnline":
|
|
||||||
String user = in.readUTF();
|
|
||||||
out.writeUTF("LastOnline");
|
|
||||||
out.writeUTF(user);
|
|
||||||
out.writeLong(RedisBungee.getApi().getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(user, true)));
|
|
||||||
break;
|
|
||||||
case "ServerPlayers":
|
|
||||||
String type1 = in.readUTF();
|
|
||||||
out.writeUTF("ServerPlayers");
|
|
||||||
Multimap<String, UUID> multimap = RedisBungee.getApi().getServerToPlayers();
|
|
||||||
|
|
||||||
boolean includesUsers;
|
|
||||||
|
|
||||||
switch (type1) {
|
|
||||||
case "COUNT":
|
|
||||||
includesUsers = false;
|
|
||||||
break;
|
|
||||||
case "PLAYERS":
|
|
||||||
includesUsers = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// TODO: Should I raise an error?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.writeUTF(type1);
|
|
||||||
|
|
||||||
if (includesUsers) {
|
|
||||||
Multimap<String, String> human = HashMultimap.create();
|
|
||||||
for (Map.Entry<String, UUID> entry : multimap.entries()) {
|
|
||||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
|
||||||
}
|
|
||||||
serializeMultimap(human, true, out);
|
|
||||||
} else {
|
|
||||||
serializeMultiset(multimap.keys(), out);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Proxy":
|
|
||||||
out.writeUTF("Proxy");
|
|
||||||
out.writeUTF(RedisBungee.getConfiguration().getServerId());
|
|
||||||
break;
|
|
||||||
case "PlayerProxy":
|
|
||||||
String username = in.readUTF();
|
|
||||||
out.writeUTF("PlayerProxy");
|
|
||||||
out.writeUTF(username);
|
|
||||||
out.writeUTF(RedisBungee.getApi().getProxy(plugin.getUuidTranslator().getTranslatedUuid(username, true)));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
((Server) event.getSender()).sendData(currentChannel, out.toByteArray());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
|
|
||||||
output.writeInt(collection.elementSet().size());
|
|
||||||
for (Multiset.Entry<String> entry : collection.entrySet()) {
|
|
||||||
output.writeUTF(entry.getElement());
|
|
||||||
output.writeInt(entry.getCount());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
private void serializeMultimap(Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) {
|
|
||||||
output.writeInt(collection.keySet().size());
|
|
||||||
for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) {
|
|
||||||
output.writeUTF(entry.getKey());
|
|
||||||
if (includeNames) {
|
|
||||||
serializeCollection(entry.getValue(), output);
|
|
||||||
} else {
|
|
||||||
output.writeInt(entry.getValue().size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) {
|
|
||||||
output.writeInt(collection.size());
|
|
||||||
for (Object o : collection) {
|
|
||||||
output.writeUTF(o.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPubSubMessage(PubSubMessageEvent event) {
|
|
||||||
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + RedisBungee.getApi().getServerId())) {
|
|
||||||
String message = event.getMessage();
|
|
||||||
if (message.startsWith("/"))
|
|
||||||
message = message.substring(1);
|
|
||||||
plugin.getLogger().info("Invoking command via PubSub: /" + message);
|
|
||||||
plugin.getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.instance, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import net.md_5.bungee.api.connection.PendingConnection;
|
|
||||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
import redis.clients.jedis.Pipeline;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class RedisUtil {
|
|
||||||
protected static void createPlayer(ProxiedPlayer player, Pipeline pipeline, boolean fireEvent) {
|
|
||||||
createPlayer(player.getPendingConnection(), pipeline, fireEvent);
|
|
||||||
if (player.getServer() != null)
|
|
||||||
pipeline.hset("player:" + player.getUniqueId().toString(), "server", player.getServer().getInfo().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void createPlayer(PendingConnection connection, Pipeline pipeline, boolean fireEvent) {
|
|
||||||
Map<String, String> playerData = new HashMap<>(4);
|
|
||||||
playerData.put("online", "0");
|
|
||||||
playerData.put("ip", connection.getAddress().getAddress().getHostAddress());
|
|
||||||
playerData.put("proxy", RedisBungee.getConfiguration().getServerId());
|
|
||||||
|
|
||||||
pipeline.sadd("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", connection.getUniqueId().toString());
|
|
||||||
pipeline.hmset("player:" + connection.getUniqueId().toString(), playerData);
|
|
||||||
|
|
||||||
if (fireEvent) {
|
|
||||||
pipeline.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
|
|
||||||
connection.getUniqueId(), DataManager.DataManagerMessage.Action.JOIN,
|
|
||||||
new DataManager.LoginPayload(connection.getAddress().getAddress()))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cleanUpPlayer(String player, Jedis rsc) {
|
|
||||||
rsc.srem("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", player);
|
|
||||||
rsc.hdel("player:" + player, "server", "ip", "proxy");
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
rsc.hset("player:" + player, "online", String.valueOf(timestamp));
|
|
||||||
rsc.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
|
|
||||||
UUID.fromString(player), DataManager.DataManagerMessage.Action.LEAVE,
|
|
||||||
new DataManager.LogoutPayload(timestamp))));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cleanUpPlayer(String player, Pipeline rsc) {
|
|
||||||
rsc.srem("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", player);
|
|
||||||
rsc.hdel("player:" + player, "server", "ip", "proxy");
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
rsc.hset("player:" + player, "online", String.valueOf(timestamp));
|
|
||||||
rsc.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
|
|
||||||
UUID.fromString(player), DataManager.DataManagerMessage.Action.LEAVE,
|
|
||||||
new DataManager.LogoutPayload(timestamp))));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isRedisVersionRight(String redisVersion) {
|
|
||||||
// Need to use >=6.2 to use Lua optimizations.
|
|
||||||
String[] args = redisVersion.split("\\.");
|
|
||||||
if (args.length < 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int major = Integer.parseInt(args[0]);
|
|
||||||
int minor = Integer.parseInt(args[1]);
|
|
||||||
return major >= 6 && minor >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ham1255: i am keeping this if some plugin uses this *IF*
|
|
||||||
@Deprecated
|
|
||||||
public static boolean canUseLua(String redisVersion) {
|
|
||||||
return isRedisVersionRight(redisVersion);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.events;
|
|
||||||
|
|
||||||
import lombok.ToString;
|
|
||||||
import net.md_5.bungee.api.plugin.Event;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is sent when a player connects to a new server. RedisBungee sends the event only when
|
|
||||||
* the proxy the player has been connected to is different than the local proxy.
|
|
||||||
* <p>
|
|
||||||
* This event corresponds to {@link net.md_5.bungee.api.event.ServerConnectedEvent}, and is fired
|
|
||||||
* asynchronously.
|
|
||||||
*
|
|
||||||
* @since 0.3.4
|
|
||||||
*/
|
|
||||||
@ToString
|
|
||||||
public class PlayerChangedServerNetworkEvent extends Event {
|
|
||||||
private final UUID uuid;
|
|
||||||
private final String previousServer;
|
|
||||||
private final String server;
|
|
||||||
|
|
||||||
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.previousServer = previousServer;
|
|
||||||
this.server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getUuid() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getServer() {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPreviousServer() {
|
|
||||||
return previousServer;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.events;
|
|
||||||
|
|
||||||
import lombok.ToString;
|
|
||||||
import net.md_5.bungee.api.plugin.Event;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is sent when a player joins the network. RedisBungee sends the event only when
|
|
||||||
* the proxy the player has been connected to is different than the local proxy.
|
|
||||||
* <p>
|
|
||||||
* This event corresponds to {@link net.md_5.bungee.api.event.PostLoginEvent}, and is fired
|
|
||||||
* asynchronously.
|
|
||||||
*
|
|
||||||
* @since 0.3.4
|
|
||||||
*/
|
|
||||||
@ToString
|
|
||||||
public class PlayerJoinedNetworkEvent extends Event {
|
|
||||||
private final UUID uuid;
|
|
||||||
|
|
||||||
public PlayerJoinedNetworkEvent(UUID uuid) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getUuid() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.events;
|
|
||||||
|
|
||||||
import lombok.ToString;
|
|
||||||
import net.md_5.bungee.api.plugin.Event;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is sent when a player disconnects. RedisBungee sends the event only when
|
|
||||||
* the proxy the player has been connected to is different than the local proxy.
|
|
||||||
* <p>
|
|
||||||
* This event corresponds to {@link net.md_5.bungee.api.event.PlayerDisconnectEvent}, and is fired
|
|
||||||
* asynchronously.
|
|
||||||
*
|
|
||||||
* @since 0.3.4
|
|
||||||
*/
|
|
||||||
@ToString
|
|
||||||
public class PlayerLeftNetworkEvent extends Event {
|
|
||||||
private final UUID uuid;
|
|
||||||
|
|
||||||
public PlayerLeftNetworkEvent(UUID uuid) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getUuid() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
# RedisBungee configuration file.
|
|
||||||
# PLEASE READ THE WIKI: https://github.com/Limework/RedisBungee/wiki
|
|
||||||
|
|
||||||
# The Redis server you use.
|
|
||||||
# Get Redis from http://redis.io/
|
|
||||||
redis-server: 127.0.0.1
|
|
||||||
redis-port: 6379
|
|
||||||
# OPTIONAL: If your Redis server uses AUTH, set the password required.
|
|
||||||
redis-password: ""
|
|
||||||
# Maximum connections that will be maintained to the Redis server.
|
|
||||||
# The default is 8. This setting should be left as-is unless you have some wildly
|
|
||||||
# inefficient plugins or a lot of players.
|
|
||||||
max-redis-connections: 8
|
|
||||||
|
|
||||||
# since redis can support ssl by version 6 you can use ssl in redis bungee too!
|
|
||||||
# you must disable this if redis version is under 6 you must disable this or connection wont work!!!
|
|
||||||
useSSL: false
|
|
||||||
|
|
||||||
# An identifier for this BungeeCord instance. Will randomly generate if leaving it blank.
|
|
||||||
server-id: "test1"
|
|
||||||
# Should use random string? if this is enabled the proxy id will be like this if server-id is test1: "test1-66cd2aeb-91f3-43a7-a106-e0307b098652"
|
|
||||||
# or if id is limework-bungee it will be "limework-bungee-66cd2aeb-91f3-43a7-a106-e0307b098652"
|
|
||||||
# this great for servers who run replicas in Kubernetes or any auto deploying replica service
|
|
||||||
# and used for if proxy died in a kubernetes network and deleted then new proxy setup itself.
|
|
||||||
use-random-id-string: false
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
register-bungee-commands: true
|
|
||||||
|
|
||||||
# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic
|
|
||||||
# restart scripts.
|
|
||||||
exempt-ip-addresses: []
|
|
@ -1,24 +0,0 @@
|
|||||||
local c = redis.call
|
|
||||||
|
|
||||||
local curTime = c("TIME")
|
|
||||||
local time = tonumber(curTime[1])
|
|
||||||
|
|
||||||
local heartbeats = c("HGETALL", "heartbeats")
|
|
||||||
local total = 0
|
|
||||||
local key
|
|
||||||
|
|
||||||
for _, v in ipairs(heartbeats) do
|
|
||||||
if not key then
|
|
||||||
key = v
|
|
||||||
else
|
|
||||||
local n = tonumber(v)
|
|
||||||
if n then
|
|
||||||
if n + 30 >= time then
|
|
||||||
total = total + c("SCARD", "proxy:" .. key .. ":usersOnline")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
key = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return total
|
|
@ -1,18 +0,0 @@
|
|||||||
local call = redis.call
|
|
||||||
local ipairs = ipairs
|
|
||||||
|
|
||||||
local serverToData = {}
|
|
||||||
|
|
||||||
for _, proxy in ipairs(ARGV) do
|
|
||||||
local players = call("SMEMBERS", "proxy:" .. proxy .. ":usersOnline")
|
|
||||||
for _, player in ipairs(players) do
|
|
||||||
local server = call("HGET", "player:" .. player, "server")
|
|
||||||
if server then
|
|
||||||
local sz = #serverToData
|
|
||||||
serverToData[sz + 1] = server
|
|
||||||
serverToData[sz + 2] = player
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return serverToData
|
|
@ -1,9 +0,0 @@
|
|||||||
name: ${project.name}
|
|
||||||
main: com.imaginarycode.minecraft.redisbungee.RedisBungee
|
|
||||||
version: ${project.version}
|
|
||||||
author: Chunkr and Govindas limework
|
|
||||||
authors:
|
|
||||||
- chunkr
|
|
||||||
- Govindas Limework
|
|
||||||
# This is used so that we can automatically override default BungeeCord behavior.
|
|
||||||
softDepends: ["cmd_find", "cmd_list"]
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.test;
|
|
||||||
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.RedisUtil;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class RedisUtilTest {
|
|
||||||
@Test
|
|
||||||
public void testRedisLuaCheck() {
|
|
||||||
Assert.assertTrue(RedisUtil.canUseLua("6.2.0"));
|
|
||||||
Assert.assertTrue(RedisUtil.canUseLua("6.1.0"));
|
|
||||||
Assert.assertTrue(RedisUtil.canUseLua("6.0.0"));
|
|
||||||
Assert.assertFalse(RedisUtil.canUseLua("2.6.0"));
|
|
||||||
Assert.assertFalse(RedisUtil.canUseLua("2.2.12"));
|
|
||||||
Assert.assertFalse(RedisUtil.canUseLua("1.2.4"));
|
|
||||||
Assert.assertFalse(RedisUtil.canUseLua("2.8.4"));
|
|
||||||
Assert.assertFalse(RedisUtil.canUseLua("3.0.0"));
|
|
||||||
Assert.assertFalse(RedisUtil.canUseLua("3.2.1"));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package com.imaginarycode.minecraft.redisbungee.test;
|
|
||||||
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.util.uuid.NameFetcher;
|
|
||||||
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDFetcher;
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class UUIDNameTest {
|
|
||||||
private String[] uuidsToTest = {"68ec43f7234b41b48764dfb38b9ffe8c", "652a2bc4e8cd405db7b698156ee2dc09"};
|
|
||||||
private String[] namesToTest = {"vemacs"};
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUuidToName() throws IOException {
|
|
||||||
OkHttpClient httpClient = new OkHttpClient();
|
|
||||||
NameFetcher.setHttpClient(httpClient);
|
|
||||||
for (String uuid : uuidsToTest) {
|
|
||||||
List<String> names = NameFetcher.nameHistoryFromUuid(UUIDFetcher.getUUID(uuid));
|
|
||||||
String currentName = names.get(names.size() - 1);
|
|
||||||
System.out.println("Current name for UUID " + uuid + " is " + currentName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNameToUuid() throws IOException {
|
|
||||||
OkHttpClient httpClient = new OkHttpClient();
|
|
||||||
UUIDFetcher.setHttpClient(httpClient);
|
|
||||||
for (String name : namesToTest) {
|
|
||||||
Map<String, UUID> uuidMap1;
|
|
||||||
try {
|
|
||||||
uuidMap1 = new UUIDFetcher(Collections.singletonList(name)).call();
|
|
||||||
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
|
|
||||||
if (entry.getKey().equalsIgnoreCase(name)) {
|
|
||||||
System.out.println("Current UUID for name " + name + " is " + entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user