mirror of
https://github.com/proxiodev/RedisBungee.git
synced 2024-12-23 18:48:02 +00:00
RedisBungee 0.3 base code. A lot has changed. There is more to come.
This commit is contained in:
parent
d3a6170e78
commit
1362739b27
2
pom.xml
2
pom.xml
@ -14,7 +14,7 @@
|
||||
|
||||
<groupId>com.imaginarycode.minecraft</groupId>
|
||||
<artifactId>RedisBungee</artifactId>
|
||||
<version>0.2.6-SNAPSHOT</version>
|
||||
<version>0.3-SNAPSHOT</version>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
|
@ -12,7 +12,9 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.google.gson.Gson;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.util.UUIDTranslator;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
@ -31,10 +33,7 @@ import redis.clients.jedis.exceptions.JedisException;
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -51,6 +50,12 @@ public final class RedisBungee extends Plugin {
|
||||
private static Configuration configuration;
|
||||
@Getter
|
||||
private JedisPool pool;
|
||||
@Getter
|
||||
private RedisBungeeConsumer consumer;
|
||||
@Getter
|
||||
private UUIDTranslator uuidTranslator;
|
||||
@Getter
|
||||
private static Gson gson = new Gson();
|
||||
private static RedisBungeeAPI api;
|
||||
private static PubSubListener psl = null;
|
||||
private List<String> serverIds;
|
||||
@ -81,7 +86,7 @@ public final class RedisBungee extends Plugin {
|
||||
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
|
||||
try {
|
||||
long stamp = Long.valueOf(entry.getValue());
|
||||
if (stamp + 30000 < System.currentTimeMillis())
|
||||
if (System.currentTimeMillis() < stamp + 30000)
|
||||
servers.add(entry.getKey());
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
@ -99,9 +104,9 @@ public final class RedisBungee extends Plugin {
|
||||
return psl;
|
||||
}
|
||||
|
||||
final Multimap<String, String> serversToPlayers() {
|
||||
ImmutableMultimap.Builder<String, String> multimapBuilder = ImmutableMultimap.builder();
|
||||
for (String p : getPlayers()) {
|
||||
final Multimap<String, UUID> serversToPlayers() {
|
||||
ImmutableMultimap.Builder<String, UUID> multimapBuilder = ImmutableMultimap.builder();
|
||||
for (UUID p : getPlayers()) {
|
||||
ServerInfo si = getServerFor(p);
|
||||
if (si != null)
|
||||
multimapBuilder = multimapBuilder.put(si.getName(), p);
|
||||
@ -118,15 +123,19 @@ public final class RedisBungee extends Plugin {
|
||||
if (pool != null) {
|
||||
Jedis rsc = pool.getResource();
|
||||
try {
|
||||
for (String i : getServerIds()) {
|
||||
if (i.equals(configuration.getString("server-id"))) continue;
|
||||
if (rsc.exists("server:" + i + ":playerCount"))
|
||||
try {
|
||||
c += Integer.valueOf(rsc.get("server:" + i + ":playerCount"));
|
||||
} catch (NumberFormatException e) {
|
||||
getLogger().severe("I found a funny number for " + i + "'s player count. Resetting it to 0.");
|
||||
rsc.set("server:" + i + ":playerCount", "0");
|
||||
}
|
||||
List<String> serverIds = getServerIds();
|
||||
Map<String, String> counts = rsc.hgetAll("playerCounts");
|
||||
for (Map.Entry<String, String> entry : counts.entrySet()) {
|
||||
if (!serverIds.contains(entry.getKey()))
|
||||
continue;
|
||||
|
||||
if (entry.getKey().equals(configuration.getString("server-id"))) continue;
|
||||
|
||||
try {
|
||||
c += Integer.valueOf(entry.getValue());
|
||||
} catch (NumberFormatException e) {
|
||||
rsc.hset("playerCounts", entry.getKey(), "0");
|
||||
}
|
||||
}
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
@ -140,23 +149,35 @@ public final class RedisBungee extends Plugin {
|
||||
return c;
|
||||
}
|
||||
|
||||
final Set<String> getLocalPlayers() {
|
||||
ImmutableSet.Builder<String> setBuilder = ImmutableSet.builder();
|
||||
final Set<UUID> getLocalPlayers() {
|
||||
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
|
||||
for (ProxiedPlayer pp : getProxy().getPlayers())
|
||||
setBuilder = setBuilder.add(pp.getName());
|
||||
setBuilder = setBuilder.add(pp.getUniqueId());
|
||||
return setBuilder.build();
|
||||
}
|
||||
|
||||
final Set<String> getPlayers() {
|
||||
ImmutableSet.Builder<String> setBuilder = ImmutableSet.<String>builder().addAll(getLocalPlayers());
|
||||
final Set<String> getLocalPlayersAsUuidStrings() {
|
||||
ImmutableSet.Builder<String> setBuilder = ImmutableSet.builder();
|
||||
for (ProxiedPlayer pp : getProxy().getPlayers())
|
||||
setBuilder = setBuilder.add(pp.getUniqueId().toString());
|
||||
return setBuilder.build();
|
||||
}
|
||||
|
||||
final Set<UUID> getPlayers() {
|
||||
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.<UUID>builder().addAll(getLocalPlayers());
|
||||
if (pool != null) {
|
||||
Jedis rsc = pool.getResource();
|
||||
try {
|
||||
for (String i : getServerIds()) {
|
||||
if (i.equals(configuration.getString("server-id"))) continue;
|
||||
Set<String> users = rsc.smembers("server:" + i + ":usersOnline");
|
||||
if (users != null && !users.isEmpty())
|
||||
setBuilder = setBuilder.addAll(users);
|
||||
if (users != null && !users.isEmpty()) {
|
||||
for (String user : users) {
|
||||
if (UUIDTranslator.UUID_PATTERN.matcher(user).find()) {
|
||||
setBuilder = setBuilder.add(UUID.fromString(user));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
@ -170,24 +191,24 @@ public final class RedisBungee extends Plugin {
|
||||
return setBuilder.build();
|
||||
}
|
||||
|
||||
final Set<String> getPlayersOnServer(@NonNull String server) {
|
||||
final Set<UUID> getPlayersOnServer(@NonNull String server) {
|
||||
checkArgument(getProxy().getServerInfo(server) != null, "server doesn't exist");
|
||||
return ImmutableSet.copyOf(serversToPlayers().get(server));
|
||||
}
|
||||
|
||||
final ServerInfo getServerFor(@NonNull String name) {
|
||||
final ServerInfo getServerFor(@NonNull UUID uuid) {
|
||||
ServerInfo server = null;
|
||||
if (getProxy().getPlayer(name) != null) return getProxy().getPlayer(name).getServer().getInfo();
|
||||
if (getProxy().getPlayer(uuid) != null) return getProxy().getPlayer(uuid).getServer().getInfo();
|
||||
if (pool != null) {
|
||||
Jedis tmpRsc = pool.getResource();
|
||||
try {
|
||||
if (tmpRsc.hexists("player:" + name, "server"))
|
||||
server = getProxy().getServerInfo(tmpRsc.hget("player:" + name, "server"));
|
||||
if (tmpRsc.hexists("player:" + uuid, "server"))
|
||||
server = getProxy().getServerInfo(tmpRsc.hget("player:" + uuid, "server"));
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
|
||||
pool.returnBrokenResource(tmpRsc);
|
||||
throw new RuntimeException("Unable to get server for " + name, e);
|
||||
throw new RuntimeException("Unable to get server for " + uuid, e);
|
||||
} finally {
|
||||
pool.returnResource(tmpRsc);
|
||||
}
|
||||
@ -195,21 +216,21 @@ public final class RedisBungee extends Plugin {
|
||||
return server;
|
||||
}
|
||||
|
||||
final long getLastOnline(@NonNull String name) {
|
||||
final long getLastOnline(@NonNull UUID uuid) {
|
||||
long time = -1L;
|
||||
if (getProxy().getPlayer(name) != null) return 0;
|
||||
if (getProxy().getPlayer(uuid) != null) return 0;
|
||||
if (pool != null) {
|
||||
Jedis tmpRsc = pool.getResource();
|
||||
try {
|
||||
if (tmpRsc.hexists("player:" + name, "online"))
|
||||
if (tmpRsc.hexists("player:" + uuid, "online"))
|
||||
try {
|
||||
time = Long.valueOf(tmpRsc.hget("player:" + name, "online"));
|
||||
time = Long.valueOf(tmpRsc.hget("player:" + uuid, "online"));
|
||||
} catch (NumberFormatException e) {
|
||||
getLogger().info("I found a funny number for when " + name + " was last online!");
|
||||
getLogger().info("I found a funny number for when " + uuid + " was last online!");
|
||||
boolean found = false;
|
||||
for (String proxyId : getServerIds()) {
|
||||
if (proxyId.equals(configuration.getString("server-id"))) continue;
|
||||
if (tmpRsc.sismember("server:" + proxyId + ":usersOnline", name)) {
|
||||
if (tmpRsc.sismember("server:" + proxyId + ":usersOnline", uuid.toString())) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@ -217,18 +238,18 @@ public final class RedisBungee extends Plugin {
|
||||
String value = "0";
|
||||
if (!found) {
|
||||
value = String.valueOf(System.currentTimeMillis());
|
||||
getLogger().info(name + " isn't online. Setting to current time.");
|
||||
getLogger().info(uuid + " isn't online. Setting to current time.");
|
||||
} else {
|
||||
getLogger().info(name + " is online. Setting to 0. Please check your BungeeCord instances.");
|
||||
getLogger().info(uuid + " is online. Setting to 0. Please check your BungeeCord instances.");
|
||||
getLogger().info("If they are working properly, and this error does not resolve in a few minutes, please let Tux know!");
|
||||
}
|
||||
tmpRsc.hset("player:" + name, "online", value);
|
||||
tmpRsc.hset("player:" + uuid, "online", value);
|
||||
}
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
|
||||
pool.returnBrokenResource(tmpRsc);
|
||||
throw new RuntimeException("Unable to get last time online for " + name, e);
|
||||
throw new RuntimeException("Unable to get last time online for " + uuid, e);
|
||||
} finally {
|
||||
pool.returnResource(tmpRsc);
|
||||
}
|
||||
@ -236,20 +257,20 @@ public final class RedisBungee extends Plugin {
|
||||
return time;
|
||||
}
|
||||
|
||||
final InetAddress getIpAddress(@NonNull String name) {
|
||||
if (getProxy().getPlayer(name) != null)
|
||||
return getProxy().getPlayer(name).getAddress().getAddress();
|
||||
final InetAddress getIpAddress(@NonNull UUID uuid) {
|
||||
if (getProxy().getPlayer(uuid) != null)
|
||||
return getProxy().getPlayer(uuid).getAddress().getAddress();
|
||||
InetAddress ia = null;
|
||||
if (pool != null) {
|
||||
Jedis tmpRsc = pool.getResource();
|
||||
try {
|
||||
if (tmpRsc.hexists("player:" + name, "ip"))
|
||||
ia = InetAddress.getByName(tmpRsc.hget("player:" + name, "ip"));
|
||||
if (tmpRsc.hexists("player:" + uuid, "ip"))
|
||||
ia = InetAddress.getByName(tmpRsc.hget("player:" + uuid, "ip"));
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
|
||||
pool.returnBrokenResource(tmpRsc);
|
||||
throw new RuntimeException("Unable to fetch IP address for " + name, e);
|
||||
throw new RuntimeException("Unable to fetch IP address for " + uuid, e);
|
||||
} catch (UnknownHostException ignored) {
|
||||
// Best to just return null
|
||||
} finally {
|
||||
@ -286,36 +307,20 @@ public final class RedisBungee extends Plugin {
|
||||
if (pool != null) {
|
||||
Jedis tmpRsc = pool.getResource();
|
||||
try {
|
||||
tmpRsc.set("server:" + configuration.getString("server-id") + ":playerCount", "0"); // reset
|
||||
tmpRsc.hset("playerCounts", configuration.getString("server-id"), "0"); // reset
|
||||
tmpRsc.hset("heartbeats", configuration.getString("server-id"), String.valueOf(System.currentTimeMillis()));
|
||||
if (tmpRsc.scard("server:" + configuration.getString("server-id") + ":usersOnline") > 0) {
|
||||
for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline")) {
|
||||
// Are they simply on a different proxy?
|
||||
boolean found = false;
|
||||
for (String proxyId : tmpRsc.smembers("servers")) {
|
||||
if (proxyId.equals(configuration.getString("server-id"))) continue;
|
||||
if (tmpRsc.sismember("server:" + proxyId + ":usersOnline", member)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
||||
else
|
||||
tmpRsc.srem("server:" + configuration.getString("server-id") + ":usersOnline", member);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pool.returnResource(tmpRsc);
|
||||
}
|
||||
globalCount = getCurrentCount();
|
||||
serverIds = getCurrentServerIds();
|
||||
globalCount = getCurrentCount();
|
||||
uuidTranslator = new UUIDTranslator(this);
|
||||
getProxy().getScheduler().schedule(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Jedis rsc = pool.getResource();
|
||||
try {
|
||||
rsc.set("server:" + configuration.getString("server-id") + ":playerCount", String.valueOf(getProxy().getOnlineCount()));
|
||||
rsc.hset("playerCounts", configuration.getString("server-id"), String.valueOf(getProxy().getOnlineCount()));
|
||||
rsc.hset("heartbeats", configuration.getString("server-id"), String.valueOf(System.currentTimeMillis()));
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
@ -324,16 +329,19 @@ public final class RedisBungee extends Plugin {
|
||||
} finally {
|
||||
pool.returnResource(rsc);
|
||||
}
|
||||
globalCount = getCurrentCount();
|
||||
serverIds = getCurrentServerIds();
|
||||
globalCount = getCurrentCount();
|
||||
}
|
||||
}, 0, 3, TimeUnit.SECONDS);
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand());
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand());
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand());
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand());
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll());
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId());
|
||||
consumer = new RedisBungeeConsumer(this);
|
||||
new Thread(consumer, "RedisBungee Consumer Thread").start();
|
||||
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().registerListener(this, new RedisBungeeListener(this));
|
||||
api = new RedisBungeeAPI(this);
|
||||
psl = new PubSubListener();
|
||||
@ -343,7 +351,7 @@ public final class RedisBungee extends Plugin {
|
||||
public void run() {
|
||||
Jedis tmpRsc = pool.getResource();
|
||||
try {
|
||||
Set<String> players = getLocalPlayers();
|
||||
Set<String> players = getLocalPlayersAsUuidStrings();
|
||||
for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline"))
|
||||
if (!players.contains(member)) {
|
||||
// Are they simply on a different proxy?
|
||||
@ -368,7 +376,7 @@ public final class RedisBungee extends Plugin {
|
||||
pool.returnResource(tmpRsc);
|
||||
}
|
||||
}
|
||||
}, 1, 3, TimeUnit.MINUTES);
|
||||
}, 0, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
getProxy().registerChannel("RedisBungee");
|
||||
}
|
||||
@ -378,6 +386,8 @@ public final class RedisBungee extends Plugin {
|
||||
if (pool != null) {
|
||||
// Poison the PubSub listener
|
||||
getProxy().getScheduler().cancel(this);
|
||||
getLogger().info("Waiting for consumer to finish writing data...");
|
||||
consumer.stop();
|
||||
Jedis tmpRsc = pool.getResource();
|
||||
try {
|
||||
tmpRsc.set("server:" + configuration.getString("server-id") + ":playerCount", "0"); // reset
|
||||
@ -385,7 +395,7 @@ public final class RedisBungee extends Plugin {
|
||||
for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline"))
|
||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
||||
}
|
||||
tmpRsc.srem("servers", configuration.getString("server-id"));
|
||||
tmpRsc.hdel("heartbeats", configuration.getString("server-id"));
|
||||
} finally {
|
||||
pool.returnResource(tmpRsc);
|
||||
}
|
||||
@ -437,13 +447,12 @@ public final class RedisBungee extends Plugin {
|
||||
File crashFile = new File(getDataFolder(), "restarted_from_crash.txt");
|
||||
if (crashFile.exists())
|
||||
crashFile.delete();
|
||||
else if (rsc.sismember("servers", configuration.getString("server-id"))) {
|
||||
else if (rsc.hexists("heartbeat", configuration.getString("server-id"))) {
|
||||
getLogger().severe("You have launched a possible imposter 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 imposter instance!");
|
||||
}
|
||||
rsc.sadd("servers", configuration.getString("server-id"));
|
||||
getLogger().log(Level.INFO, "Successfully connected to Redis.");
|
||||
} catch (JedisConnectionException e) {
|
||||
if (rsc != null)
|
||||
|
@ -13,6 +13,7 @@ import net.md_5.bungee.api.config.ServerInfo;
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungee#getApi()}.
|
||||
@ -43,7 +44,7 @@ public class RedisBungeeAPI {
|
||||
* @param player a player name
|
||||
* @return the last time a player was on, if online returns a 0
|
||||
*/
|
||||
public final long getLastOnline(@NonNull String player) {
|
||||
public final long getLastOnline(@NonNull UUID player) {
|
||||
return plugin.getLastOnline(player);
|
||||
}
|
||||
|
||||
@ -54,36 +55,38 @@ public class RedisBungeeAPI {
|
||||
* @param player a player name
|
||||
* @return a {@link net.md_5.bungee.api.config.ServerInfo} for the server the player is on.
|
||||
*/
|
||||
public final ServerInfo getServerFor(@NonNull String player) {
|
||||
public final ServerInfo getServerFor(@NonNull UUID player) {
|
||||
return plugin.getServerFor(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a combined list of players on this network.
|
||||
* <p>
|
||||
* <p/>
|
||||
* <strong>Note that this function returns an immutable {@link java.util.Set}.</strong>
|
||||
*
|
||||
* @return a Set with all players found
|
||||
*/
|
||||
public final Set<String> getPlayersOnline() {
|
||||
public final Set<UUID> getPlayersOnline() {
|
||||
return plugin.getPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full list of players on all servers.
|
||||
*
|
||||
* @return a immutable Multimap with all players found on this server
|
||||
* @since 0.2.5
|
||||
*/
|
||||
public final Multimap<String, String> getServerToPlayers() {
|
||||
public final Multimap<String, UUID> getServerToPlayers() {
|
||||
return plugin.serversToPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of players on the server with the given name.
|
||||
*
|
||||
* @param server a server name
|
||||
* @return a Set with all players found on this server
|
||||
*/
|
||||
public final Set<String> getPlayersOnServer(@NonNull String server) {
|
||||
public final Set<UUID> getPlayersOnServer(@NonNull String server) {
|
||||
return plugin.getPlayersOnServer(server);
|
||||
}
|
||||
|
||||
@ -93,7 +96,7 @@ public class RedisBungeeAPI {
|
||||
* @param player a player name
|
||||
* @return if the player is online
|
||||
*/
|
||||
public final boolean isPlayerOnline(@NonNull String player) {
|
||||
public final boolean isPlayerOnline(@NonNull UUID player) {
|
||||
return getLastOnline(player) == 0;
|
||||
}
|
||||
|
||||
@ -104,12 +107,13 @@ public class RedisBungeeAPI {
|
||||
* @return an {@link java.net.InetAddress} if the player is online, null otherwise
|
||||
* @since 0.2.4
|
||||
*/
|
||||
public final InetAddress getPlayerIp(@NonNull String player) {
|
||||
public final InetAddress getPlayerIp(@NonNull UUID player) {
|
||||
return plugin.getIpAddress(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a proxy command to all proxies.
|
||||
*
|
||||
* @param command the command to send and execute
|
||||
* @see #sendProxyCommand(String, String)
|
||||
* @since 0.2.5
|
||||
@ -120,6 +124,7 @@ public class RedisBungeeAPI {
|
||||
|
||||
/**
|
||||
* Sends a proxy command to the proxy with the given ID. "allservers" means all proxies.
|
||||
*
|
||||
* @param proxyId a proxy ID
|
||||
* @param command the command to send and execute
|
||||
* @see #getServerId()
|
||||
@ -132,9 +137,10 @@ public class RedisBungeeAPI {
|
||||
|
||||
/**
|
||||
* Get the current BungeeCord server ID for this server.
|
||||
*
|
||||
* @return the current server ID
|
||||
* @since 0.2.5
|
||||
* @see #getAllServers()
|
||||
* @since 0.2.5
|
||||
*/
|
||||
public final String getServerId() {
|
||||
return RedisBungee.getConfiguration().getString("server-id");
|
||||
@ -142,9 +148,10 @@ public class RedisBungeeAPI {
|
||||
|
||||
/**
|
||||
* Get all the linked proxies in this network.
|
||||
*
|
||||
* @return the list of all proxies
|
||||
* @since 0.2.5
|
||||
* @see #getServerId()
|
||||
* @since 0.2.5
|
||||
*/
|
||||
public final List<String> getAllServers() {
|
||||
return plugin.getServerIds();
|
||||
@ -169,4 +176,26 @@ public class RedisBungeeAPI {
|
||||
public final void unregisterPubSubChannels(String... channels) {
|
||||
RedisBungee.getPubSubListener().removeChannel(channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a name from the specified UUID.
|
||||
*
|
||||
* @param uuid the UUID to fetch the name for
|
||||
* @return the name for the UUID
|
||||
* @since 0.3
|
||||
*/
|
||||
public final String getNameFromUuid(UUID uuid) {
|
||||
return plugin.getUuidTranslator().getNameFromUuid(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a UUID from the specified name.
|
||||
*
|
||||
* @param name the UUID to fetch the name for
|
||||
* @return the UUID for the name
|
||||
* @since 0.3
|
||||
*/
|
||||
public final UUID getUuidFromName(String name) {
|
||||
return plugin.getUuidTranslator().getTranslatedUuid(name);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This class is the CommandSender that RedisBungee uses to dispatch commands to BungeeCord.
|
||||
* <p>
|
||||
* <p/>
|
||||
* It inherits all permissions of the console command sender. Sending messages and modifying permissions are no-ops.
|
||||
*
|
||||
* @author tuxed
|
||||
|
@ -7,6 +7,7 @@
|
||||
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;
|
||||
@ -18,7 +19,9 @@ import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Map;
|
||||
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.
|
||||
@ -37,119 +40,158 @@ class RedisBungeeCommands {
|
||||
new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
|
||||
|
||||
public static class GlistCommand extends Command {
|
||||
GlistCommand() {
|
||||
private final RedisBungee plugin;
|
||||
|
||||
GlistCommand(RedisBungee plugin) {
|
||||
super("glist", "bungeecord.command.list", "redisbungee", "rglist");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
int count = RedisBungee.getApi().getPlayerCount();
|
||||
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW).append(String.valueOf(count))
|
||||
.append(" player(s) are currently online.").create();
|
||||
if (args.length > 0 && args[0].equals("showall")) {
|
||||
if (RedisBungee.getConfiguration().getBoolean("canonical-glist", true)) {
|
||||
Multimap<String, String> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
|
||||
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(serverToPlayers.get(server)));
|
||||
sender.sendMessage(serverName, serverCount, serverPlayers);
|
||||
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(String.valueOf(count))
|
||||
.append(" player(s) are currently online.").create();
|
||||
if (args.length > 0 && args[0].equals("showall")) {
|
||||
if (RedisBungee.getConfiguration().getBoolean("canonical-glist", true)) {
|
||||
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()));
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(new ComponentBuilder("Players: " + Joiner.on(", ").join(RedisBungee.getApi().getPlayersOnline()))
|
||||
.color(ChatColor.YELLOW).create());
|
||||
}
|
||||
sender.sendMessage(playersOnline);
|
||||
} else {
|
||||
sender.sendMessage(playersOnline);
|
||||
sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(new ComponentBuilder("Players: " + Joiner.on(", ").join(RedisBungee.getApi().getPlayersOnline()))
|
||||
.color(ChatColor.YELLOW).create());
|
||||
}
|
||||
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 {
|
||||
FindCommand() {
|
||||
private final RedisBungee plugin;
|
||||
|
||||
FindCommand(RedisBungee plugin) {
|
||||
super("find", "bungeecord.command.find", "rfind");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length > 0) {
|
||||
ServerInfo si = RedisBungee.getApi().getServerFor(args[0]);
|
||||
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);
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
ServerInfo si = RedisBungee.getApi().getServerFor(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class LastSeenCommand extends Command {
|
||||
LastSeenCommand() {
|
||||
private final RedisBungee plugin;
|
||||
|
||||
LastSeenCommand(RedisBungee plugin) {
|
||||
super("lastseen", "redisbungee.command.lastseen", "rlastseen");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length > 0) {
|
||||
long secs = RedisBungee.getApi().getLastOnline(args[0]);
|
||||
TextComponent message = new TextComponent();
|
||||
if (secs == 0) {
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is currently online.");
|
||||
sender.sendMessage(message);
|
||||
} else if (secs != -1) {
|
||||
message.setColor(ChatColor.BLUE);
|
||||
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
message.setColor(ChatColor.RED);
|
||||
message.setText(args[0] + " has never been online.");
|
||||
sender.sendMessage(message);
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
long secs = RedisBungee.getApi().getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
|
||||
TextComponent message = new TextComponent();
|
||||
if (secs == 0) {
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is currently online.");
|
||||
sender.sendMessage(message);
|
||||
} else if (secs != -1) {
|
||||
message.setColor(ChatColor.BLUE);
|
||||
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
message.setColor(ChatColor.RED);
|
||||
message.setText(args[0] + " has never been online.");
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class IpCommand extends Command {
|
||||
IpCommand() {
|
||||
private final RedisBungee plugin;
|
||||
|
||||
IpCommand(RedisBungee plugin) {
|
||||
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length > 0) {
|
||||
InetAddress ia = RedisBungee.getApi().getPlayerIp(args[0]);
|
||||
if (ia != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is connected from " + ia.toString() + ".");
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
InetAddress ia = RedisBungee.getApi().getPlayerIp(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
|
||||
if (ia != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is connected from " + ia.toString() + ".");
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class SendToAll extends Command {
|
||||
SendToAll() {
|
||||
private final RedisBungee plugin;
|
||||
|
||||
SendToAll(RedisBungee plugin) {
|
||||
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -167,8 +209,11 @@ class RedisBungeeCommands {
|
||||
}
|
||||
|
||||
public static class ServerId extends Command {
|
||||
ServerId() {
|
||||
private final RedisBungee plugin;
|
||||
|
||||
ServerId(RedisBungee plugin) {
|
||||
super("serverid", "redisbungee.command.serverid", "rserverid");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -179,4 +224,18 @@ class RedisBungeeCommands {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.consumerevents.ConsumerEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerChangedServerConsumerEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedInConsumerEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedOffConsumerEvent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class RedisBungeeConsumer implements Runnable {
|
||||
private final RedisBungee plugin;
|
||||
private BlockingQueue<ConsumerEvent> consumerQueue = new LinkedBlockingQueue<>();
|
||||
private boolean stopped = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Jedis jedis = plugin.getPool().getResource();
|
||||
try {
|
||||
while (!stopped) {
|
||||
ConsumerEvent event;
|
||||
try {
|
||||
event = consumerQueue.take();
|
||||
} catch (InterruptedException e) {
|
||||
continue;
|
||||
}
|
||||
handle(event, jedis);
|
||||
}
|
||||
for (ConsumerEvent event : consumerQueue)
|
||||
handle(event, jedis);
|
||||
consumerQueue.clear();
|
||||
} finally {
|
||||
plugin.getPool().returnResource(jedis);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(ConsumerEvent event, Jedis jedis) {
|
||||
if (event instanceof PlayerLoggedInConsumerEvent) {
|
||||
PlayerLoggedInConsumerEvent event1 = (PlayerLoggedInConsumerEvent) event;
|
||||
jedis.sadd("server:" + RedisBungee.getConfiguration().getString("server-id", "") + ":usersOnline", event1.getPlayer().getUniqueId().toString());
|
||||
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "online", "0");
|
||||
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "ip", event1.getPlayer().getAddress().getAddress().getHostAddress());
|
||||
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "name", event1.getPlayer().getName());
|
||||
jedis.hset("uuids", event1.getPlayer().getName(), event1.getPlayer().getUniqueId().toString());
|
||||
} else if (event instanceof PlayerLoggedOffConsumerEvent) {
|
||||
PlayerLoggedOffConsumerEvent event1 = (PlayerLoggedOffConsumerEvent) event;
|
||||
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "online", String.valueOf(System.currentTimeMillis()));
|
||||
RedisUtil.cleanUpPlayer(event1.getPlayer().getUniqueId().toString(), jedis);
|
||||
} else if (event instanceof PlayerChangedServerConsumerEvent) {
|
||||
PlayerChangedServerConsumerEvent event1 = (PlayerChangedServerConsumerEvent) event;
|
||||
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "server", event1.getNewServer().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void queue(ConsumerEvent event) {
|
||||
if (!stopped)
|
||||
consumerQueue.add(event);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
stopped = true;
|
||||
while (!consumerQueue.isEmpty()) ;
|
||||
}
|
||||
}
|
@ -1,9 +1,18 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerChangedServerConsumerEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedInConsumerEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedOffConsumerEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.md_5.bungee.api.ServerPing;
|
||||
@ -14,7 +23,9 @@ import net.md_5.bungee.event.EventHandler;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class RedisBungeeListener implements Listener {
|
||||
@ -40,60 +51,17 @@ public class RedisBungeeListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerConnect(final PostLoginEvent event) {
|
||||
if (plugin.getPool() != null) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Jedis rsc = plugin.getPool().getResource();
|
||||
try {
|
||||
rsc.sadd("server:" + RedisBungee.getConfiguration().getString("server-id", "") + ":usersOnline", event.getPlayer().getName());
|
||||
rsc.hset("player:" + event.getPlayer().getName(), "online", "0");
|
||||
rsc.hset("player:" + event.getPlayer().getName(), "ip", event.getPlayer().getAddress().getAddress().getHostAddress());
|
||||
rsc.hset("player:" + event.getPlayer().getName(), "name", event.getPlayer().getName());
|
||||
} finally {
|
||||
plugin.getPool().returnResource(rsc);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// I used to have a task that eagerly waited for the user to be connected.
|
||||
// Well, upon further inspection of BungeeCord's source code, this turned
|
||||
// out to not be needed at all, since ServerConnectedEvent is called anyway.
|
||||
plugin.getConsumer().queue(new PlayerLoggedInConsumerEvent(event.getPlayer()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDisconnect(final PlayerDisconnectEvent event) {
|
||||
if (plugin.getPool() != null) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Jedis rsc = plugin.getPool().getResource();
|
||||
try {
|
||||
rsc.hset("player:" + event.getPlayer().getName(), "online", String.valueOf(System.currentTimeMillis()));
|
||||
RedisUtil.cleanUpPlayer(event.getPlayer().getName(), rsc);
|
||||
} finally {
|
||||
plugin.getPool().returnResource(rsc);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
plugin.getConsumer().queue(new PlayerLoggedOffConsumerEvent(event.getPlayer()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerChange(final ServerConnectedEvent event) {
|
||||
if (plugin.getPool() != null) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Jedis rsc = plugin.getPool().getResource();
|
||||
try {
|
||||
rsc.hset("player:" + event.getPlayer().getName(), "server", event.getServer().getInfo().getName());
|
||||
} finally {
|
||||
plugin.getPool().returnResource(rsc);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
plugin.getConsumer().queue(new PlayerChangedServerConsumerEvent(event.getPlayer(), event.getServer().getInfo()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -101,11 +69,11 @@ public class RedisBungeeListener implements Listener {
|
||||
ServerPing old = event.getResponse();
|
||||
ServerPing reply = new ServerPing();
|
||||
if (RedisBungee.getConfiguration().getBoolean("player-list-in-ping", false)) {
|
||||
Set<String> players = plugin.getPlayers();
|
||||
Set<UUID> players = plugin.getPlayers();
|
||||
ServerPing.PlayerInfo[] info = new ServerPing.PlayerInfo[players.size()];
|
||||
int idx = 0;
|
||||
for (String player : players) {
|
||||
info[idx] = new ServerPing.PlayerInfo(player, "");
|
||||
for (UUID player : players) {
|
||||
info[idx] = new ServerPing.PlayerInfo(plugin.getUuidTranslator().getNameFromUuid(player), "");
|
||||
idx++;
|
||||
}
|
||||
reply.setPlayers(new ServerPing.Players(old.getPlayers().getMax(), players.size(), info));
|
||||
@ -130,18 +98,21 @@ public class RedisBungeeListener implements Listener {
|
||||
switch (subchannel) {
|
||||
case "PlayerList":
|
||||
out.writeUTF("Players");
|
||||
Set<String> source = Collections.emptySet();
|
||||
Set<UUID> original = Collections.emptySet();
|
||||
type = in.readUTF();
|
||||
if (type.equals("ALL")) {
|
||||
out.writeUTF("ALL");
|
||||
source = plugin.getPlayers();
|
||||
original = plugin.getPlayers();
|
||||
} else {
|
||||
try {
|
||||
source = plugin.getPlayersOnServer(type);
|
||||
original = plugin.getPlayersOnServer(type);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
out.writeUTF(Joiner.on(',').join(source));
|
||||
Set<String> players = new HashSet<>();
|
||||
for (UUID uuid : original)
|
||||
players.add(plugin.getUuidTranslator().getNameFromUuid(uuid));
|
||||
out.writeUTF(Joiner.on(',').join(players));
|
||||
break;
|
||||
case "PlayerCount":
|
||||
out.writeUTF("PlayerCount");
|
||||
@ -163,7 +134,7 @@ public class RedisBungeeListener implements Listener {
|
||||
String user = in.readUTF();
|
||||
out.writeUTF("LastOnline");
|
||||
out.writeUTF(user);
|
||||
out.writeLong(plugin.getLastOnline(user));
|
||||
out.writeLong(plugin.getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(user)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee.consumerevents;
|
||||
|
||||
public interface ConsumerEvent {
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee.consumerevents;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class PlayerChangedServerConsumerEvent implements ConsumerEvent {
|
||||
private final ProxiedPlayer player;
|
||||
private final ServerInfo newServer;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee.consumerevents;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class PlayerLoggedInConsumerEvent implements ConsumerEvent {
|
||||
private final ProxiedPlayer player;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee.consumerevents;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class PlayerLoggedOffConsumerEvent implements ConsumerEvent {
|
||||
private final ProxiedPlayer player;
|
||||
}
|
@ -11,7 +11,7 @@ import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* This event is posted when a PubSub message is received.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Warning</strong>: This event is fired in a separate thread!
|
||||
*
|
||||
* @since 0.2.6
|
||||
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/* Credits to evilmidget38 for this class. I modified it to use Gson. */
|
||||
public class NameFetcher implements Callable<Map<UUID, String>> {
|
||||
private static final String PROFILE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
||||
private final List<UUID> uuids;
|
||||
|
||||
public NameFetcher(List<UUID> uuids) {
|
||||
this.uuids = ImmutableList.copyOf(uuids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, String> call() throws Exception {
|
||||
Map<UUID, String> uuidStringMap = new HashMap<>();
|
||||
for (UUID uuid : uuids) {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(PROFILE_URL + uuid.toString().replace("-", "")).openConnection();
|
||||
Map<String, String> response = RedisBungee.getGson().fromJson(new InputStreamReader(connection.getInputStream()), new TypeToken<Map<String, String>>() {
|
||||
}.getType());
|
||||
String name = response.get("name");
|
||||
if (name == null) {
|
||||
continue;
|
||||
}
|
||||
String cause = response.get("cause");
|
||||
String errorMessage = response.get("errorMessage");
|
||||
if (cause != null && cause.length() > 0) {
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
uuidStringMap.put(uuid, name);
|
||||
}
|
||||
return uuidStringMap;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/* Credits to evilmidget38 for this class. I modified it to use Gson. */
|
||||
public class UUIDFetcher implements Callable<Map<String, UUID>> {
|
||||
private static final double PROFILES_PER_REQUEST = 100;
|
||||
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
|
||||
private final List<String> names;
|
||||
private final boolean rateLimiting;
|
||||
|
||||
public UUIDFetcher(List<String> names, boolean rateLimiting) {
|
||||
this.names = ImmutableList.copyOf(names);
|
||||
this.rateLimiting = rateLimiting;
|
||||
}
|
||||
|
||||
public UUIDFetcher(List<String> names) {
|
||||
this(names, true);
|
||||
}
|
||||
|
||||
public Map<String, UUID> call() throws Exception {
|
||||
Map<String, UUID> uuidMap = new HashMap<>();
|
||||
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
|
||||
for (int i = 0; i < requests; i++) {
|
||||
HttpURLConnection connection = createConnection();
|
||||
String body = RedisBungee.getGson().toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
|
||||
writeBody(connection, body);
|
||||
Profile[] array = RedisBungee.getGson().fromJson(new InputStreamReader(connection.getInputStream()), Profile[].class);
|
||||
for (Profile profile : array) {
|
||||
UUID uuid = UUIDFetcher.getUUID(profile.id);
|
||||
uuidMap.put(profile.name, uuid);
|
||||
}
|
||||
if (rateLimiting && i != requests - 1) {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
}
|
||||
return uuidMap;
|
||||
}
|
||||
|
||||
private static void writeBody(HttpURLConnection connection, String body) throws Exception {
|
||||
OutputStream stream = connection.getOutputStream();
|
||||
stream.write(body.getBytes());
|
||||
stream.flush();
|
||||
stream.close();
|
||||
}
|
||||
|
||||
private static HttpURLConnection createConnection() throws Exception {
|
||||
URL url = new URL(PROFILE_URL);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setUseCaches(false);
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
return connection;
|
||||
}
|
||||
|
||||
private static UUID getUUID(String id) {
|
||||
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32));
|
||||
}
|
||||
|
||||
public static byte[] toBytes(UUID uuid) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
|
||||
byteBuffer.putLong(uuid.getMostSignificantBits());
|
||||
byteBuffer.putLong(uuid.getLeastSignificantBits());
|
||||
return byteBuffer.array();
|
||||
}
|
||||
|
||||
public static UUID fromBytes(byte[] array) {
|
||||
if (array.length != 16) {
|
||||
throw new IllegalArgumentException("Illegal byte array length: " + array.length);
|
||||
}
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
|
||||
long mostSignificant = byteBuffer.getLong();
|
||||
long leastSignificant = byteBuffer.getLong();
|
||||
return new UUID(mostSignificant, leastSignificant);
|
||||
}
|
||||
|
||||
public static UUID getUUIDOf(String name) throws Exception {
|
||||
return new UUIDFetcher(Collections.singletonList(name)).call().get(name);
|
||||
}
|
||||
|
||||
private class Profile {
|
||||
String id;
|
||||
String name;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright © 2013 tuxed <write@imaginarycode.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package com.imaginarycode.minecraft.redisbungee.util;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class UUIDTranslator {
|
||||
private final RedisBungee plugin;
|
||||
private BiMap<String, UUID> uuidMap = Maps.synchronizedBiMap(HashBiMap.<String, UUID>create());
|
||||
public 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}");
|
||||
|
||||
public UUID getTranslatedUuid(String player) {
|
||||
if (ProxyServer.getInstance().getPlayer(player) != null)
|
||||
return ProxyServer.getInstance().getPlayer(player).getUniqueId();
|
||||
|
||||
UUID uuid = uuidMap.get(player);
|
||||
if (uuid != null)
|
||||
return uuid;
|
||||
|
||||
if (!plugin.getProxy().getConfig().isOnlineMode()) {
|
||||
uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
|
||||
uuidMap.put(player, uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
// Okay, it wasn't locally cached. Let's try Redis.
|
||||
Jedis jedis = plugin.getPool().getResource();
|
||||
try {
|
||||
String stored = jedis.hget("uuids", player);
|
||||
if (stored != null && UUID_PATTERN.matcher(stored).find()) {
|
||||
// This is it!
|
||||
uuid = UUID.fromString(stored);
|
||||
uuidMap.put(stored, UUID.fromString(stored));
|
||||
return uuid;
|
||||
}
|
||||
|
||||
// That didn't work. Let's ask Mojang.
|
||||
uuid = UUIDFetcher.getUUIDOf(player);
|
||||
|
||||
if (uuid != null) {
|
||||
uuidMap.put(stored, uuid);
|
||||
jedis.hset("uuids", player, uuid.toString());
|
||||
}
|
||||
|
||||
return uuid;
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Unable to fetch UUID for " + player, e);
|
||||
return null;
|
||||
} finally {
|
||||
plugin.getPool().returnResource(jedis);
|
||||
}
|
||||
}
|
||||
|
||||
public String getNameFromUuid(UUID player) {
|
||||
if (ProxyServer.getInstance().getPlayer(player) != null)
|
||||
return ProxyServer.getInstance().getPlayer(player).getName();
|
||||
|
||||
String name = uuidMap.inverse().get(player);
|
||||
|
||||
if (name != null)
|
||||
return name;
|
||||
|
||||
// Okay, it wasn't locally cached. Let's try Redis.
|
||||
Jedis jedis = plugin.getPool().getResource();
|
||||
try {
|
||||
String stored = jedis.hget("player:" + player, "name");
|
||||
if (stored != null) {
|
||||
name = stored;
|
||||
uuidMap.put(name, player);
|
||||
return name;
|
||||
}
|
||||
|
||||
// That didn't work. Let's ask Mojang.
|
||||
name = new NameFetcher(Collections.singletonList(player)).call().get(player);
|
||||
|
||||
if (name != null) {
|
||||
jedis.hset("player:" + player, "name", name);
|
||||
uuidMap.put(name, player);
|
||||
return name;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Unable to fetch name for " + player, e);
|
||||
return null;
|
||||
} finally {
|
||||
plugin.getPool().returnResource(jedis);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
name: RedisBungee
|
||||
main: com.imaginarycode.minecraft.redisbungee.RedisBungee
|
||||
version: 0.2.6
|
||||
version: 0.3
|
||||
author: tuxed
|
||||
# This is used so that we can automagically override default BungeeCord behavior.
|
||||
softDepends: ["cmd_find", "cmd_list"]
|
Loading…
Reference in New Issue
Block a user