/** * Copyright © 2013 tuxed * 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.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.util.RedisCallable; import lombok.AllArgsConstructor; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ComponentBuilder; 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 java.net.InetAddress; import java.util.*; import java.util.concurrent.Callable; @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\nIf you were disconnected forcefully, please wait up to one minute.\nIf this does not resolve your issue, please contact staff.") .color(ChatColor.GRAY) .create(); private final RedisBungee plugin; private final List exemptAddresses; @EventHandler public void onPlayerConnect(final PostLoginEvent event) { Jedis rsc = plugin.getPool().getResource(); try { for (String server : plugin.getServerIds()) { if (rsc.sismember("proxy:" + server + ":usersOnline", event.getPlayer().getUniqueId().toString())) { event.getPlayer().disconnect(ALREADY_LOGGED_IN); return; } } plugin.getService().submit(new RedisCallable(plugin) { @Override protected Void call(Jedis jedis) { jedis.sadd("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", event.getPlayer().getUniqueId().toString()); jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "online", "0"); jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "ip", event.getPlayer().getAddress().getAddress().getHostAddress()); plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), jedis); jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "proxy", RedisBungee.getConfiguration().getServerId()); 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; } }); } finally { plugin.getPool().returnResource(rsc); } } @EventHandler public void onPlayerDisconnect(final PlayerDisconnectEvent event) { plugin.getService().submit(new RedisCallable(plugin) { @Override protected Void call(Jedis jedis) { long timestamp = System.currentTimeMillis(); jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "online", String.valueOf(timestamp)); RedisUtil.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), jedis); jedis.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>( event.getPlayer().getUniqueId(), DataManager.DataManagerMessage.Action.LEAVE, new DataManager.LogoutPayload(timestamp)))); return null; } }); } @EventHandler public void onServerChange(final ServerConnectedEvent event) { plugin.getService().submit(new RedisCallable(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())))); return null; } }); } @EventHandler(priority = EventPriority.LOWEST) public void onPing(final ProxyPingEvent event) { if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) { return; } event.registerIntent(plugin); plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { @Override public void run() { ServerPing old = event.getResponse(); ServerPing reply = new ServerPing(); reply.setPlayers(new ServerPing.Players(old.getPlayers().getMax(), plugin.getCount(), old.getPlayers().getSample())); reply.setDescription(old.getDescription()); reply.setFavicon(old.getFaviconObject()); reply.setVersion(old.getVersion()); event.setResponse(reply); event.completeIntent(plugin); } }); } @EventHandler public void onPluginMessage(final PluginMessageEvent event) { if (event.getTag().equals("RedisBungee") && event.getSender() instanceof Server) { final byte[] data = Arrays.copyOf(event.getData(), event.getData().length); plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { @Override public void run() { ByteArrayDataInput in = ByteStreams.newDataInput(data); String subchannel = in.readUTF(); ByteArrayDataOutput out = ByteStreams.newDataOutput(); String type; switch (subchannel) { case "PlayerList": out.writeUTF("PlayerList"); Set original = Collections.emptySet(); type = in.readUTF(); if (type.equals("ALL")) { out.writeUTF("ALL"); original = plugin.getPlayers(); } else { try { original = plugin.getPlayersOnServer(type); } catch (IllegalArgumentException ignored) { } } Set 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(plugin.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 = "COUNT"; try { type1 = in.readUTF(); } catch (Exception e) {} out.writeUTF("ServerPlayers"); Multimap multimap = RedisBungee.getApi().getServerToPlayers(); boolean includesUsers = type1.equals("PLAYERS"); if (includesUsers) { Multimap human = HashMultimap.create(); for (Map.Entry entry : multimap.entries()) { human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); } serializeMultimap(human, true, out); } else { // Due to Java generics, we are forced to coerce UUIDs into strings. This is less // expensive than looking up names, since we just want counts. Multimap flunk = HashMultimap.create(); for (Map.Entry entry : multimap.entries()) { flunk.put(entry.getKey(), entry.getValue().toString()); } serializeMultimap(flunk, false, out); } break; case "Proxy": out.writeUTF("Proxy"); out.writeUTF(RedisBungee.getConfiguration().getServerId()); break; default: break; } ((Server) event.getSender()).sendData("RedisBungee", out.toByteArray()); } }); } } private void serializeMultimap(Multimap collection, boolean includeNames, ByteArrayDataOutput output) { output.writeInt(collection.size()); for (Map.Entry> 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); } } }