public class BadApiKeyException extends Exception{
    public BadApiKeyException(String message) {
        super(message);
    }
} public class Client {
    private Socket client;
    private OutputStream outputStream;
    private InputStream inputStream;

    public static class ResponseHandler {
        public void bans(List<Ban> bans){}
        public void err(String error){}
        public void ack(){}
        public void partial(long total, long current){}
    }
    public static Client cast(Client client) {
        return new Client(client);
    }
    public Client(Client c) {
        setClient(c.getClient());

        setInputStream(c.getInputStream());
        setOutputStream(c.getOutputStream());
    }
    public Client(String apiKey) throws IOException, BadApiKeyException, TooLargeException {
        client = new Socket("api.v4.direct.mcbans.com", 8082);
        client.setKeepAlive(true);
        client.setSoTimeout(3000);
        outputStream = client.getOutputStream();
        inputStream = client.getInputStream();
        registerClient(apiKey);
    }

    void sendCommand(ServerMCBansCommands command, long referenceId) throws IOException {
        outputStream.write(command.getValue()); // register server to session command
        outputStream.write(ByteBuffer.allocate(8).putLong(referenceId).array());
        outputStream.flush();
    }
    void sendCommand(ServerMCBansCommands command) throws IOException {
        sendCommand(command, -1);
    }
    Command getCommand(InputStream inputStream) throws IOException {
        byte[] commandId = ReadFromInputStream.readByteData(inputStream, 1);
        long referenceIdLong = ByteBuffer.wrap(ReadFromInputStream.readByteData(inputStream, 8)).getLong();
        return new Command(referenceIdLong, commandId[0]);
    }

    void registerClient(String apiKey) throws BadApiKeyException, IOException, TooLargeException {
        sendCommand(ServerMCBansCommands.SessionRegister);
        WriteToOutputStream.writeString(outputStream, apiKey); // apikey
        WriteToOutputStream.writeString(outputStream, ""); // secretKey
        Command command = getCommand(inputStream);
        switch(command.getCommand()){
            case -126:
                throw new BadApiKeyException(ReadFromInputStream.readString(inputStream, 128) + ": "+apiKey);
            case 126:
                System.out.println("Acknowledge received");
                break;
        }
    }

    public void verifyConnection() throws IOException, TooLargeException {
        sendCommand(ServerMCBansCommands.VerifyConnection);
        Command command = getCommand(inputStream);
        switch(command.getCommand()) {
            case 126:
                //System.out.println("Acknowledge received");
                break;
        }
    }


    public void close() throws IOException {
        sendCommand(ServerMCBansCommands.SessionClose);
    }

    public Socket getClient() {
        return client;
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setClient(Socket client) {
        this.client = client;
    }

    public void setOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

} public class ConnectionPool {
  private static int connections = 0;
  private static Queue<Client> clients = new LinkedBlockingDeque<>();
  private static Map<Client, Timer> keepAliveTimer = new HashMap<>();
  public static Client getConnection(String ApiKey) throws IOException, BadApiKeyException, TooLargeException, InterruptedException {
    Timer timer;
    Client client = clients.poll();
    if(client!=null){
      if((timer = keepAliveTimer.getOrDefault(client, null))!=null){
        timer.cancel();
      }
      return client;
    }
    if(connections<10){
      return new Client(ApiKey);
    }
    connections++;
    while((client=clients.poll())==null){
      Thread.sleep(100);
    }
    if((timer = keepAliveTimer.getOrDefault(client, null))!=null){
      timer.cancel();
    }
    return client;
  }
  public static void release(Client client){
    keepAliveTimer.put(client, new Timer());
    keepAliveTimer.get(client).scheduleAtFixedRate(new TimerTask() {
      public void run() {
        if(clients.remove(client)) { // only run if it hasn't been taken out of queue
          try {
            client.verifyConnection();
            clients.add(client);
          } catch (IOException | TooLargeException e) {
            this.cancel();
            connections--;
            try {
              client.close();
            } catch (IOException ioException) {
              System.out.println("client disconnected");
            }
          }
        }
      }
    }, 5000, 30000);
    clients.add(client);
  }

} public class InformationCallbackClient extends Client{
  public InformationCallbackClient(Client c) {
    super(c);
  }

  public InformationCallbackClient(String apiKey) throws IOException, BadApiKeyException, TooLargeException {
    super(apiKey);
  }

  public static InformationCallbackClient cast(Client client) {
    return new InformationCallbackClient(client);
  }

  public void updateState(
    int maxPlayers,
    List<String> players,
    String mcbansVersion,
    String minecraftVersion,
    String bukkitVersion,
    boolean onlineMode,
    String serverName) throws IOException, TooLargeException {
    sendCommand(ServerMCBansCommands.InformationCallback);
    WriteToOutputStream.writeInt(getClient().getOutputStream(), maxPlayers);
    WriteToOutputStream.writeByteArray(getClient().getOutputStream(), ObjectSerializer.serialize(players));
    WriteToOutputStream.writeString(getClient().getOutputStream(), mcbansVersion);
    WriteToOutputStream.writeString(getClient().getOutputStream(), minecraftVersion);
    WriteToOutputStream.writeString(getClient().getOutputStream(), bukkitVersion);
    WriteToOutputStream.writeBoolean(getClient().getOutputStream(), onlineMode);
    WriteToOutputStream.writeString(getClient().getOutputStream(), serverName);
    Command c = getCommand(getInputStream());
    switch (c.getCommand()){
      case 126:
        System.out.println("success");
        break;
      case 124:
        String errorMessage = ReadFromInputStream.readString(getInputStream(), 255);
        System.out.println(errorMessage);
        break;
    }
  }
} public class Plugin implements Serializable {
  String name;
  String version;
  List<String> authors;
  List<String> permissions;

  public Plugin() {
  }

  public Plugin(String name, String version, List<String> authors, List<String> permissions) {
    this.name = name;
    this.version = version;
    this.authors = authors;
    this.permissions = permissions;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getVersion() {
    return version;
  }

  public void setVersion(String version) {
    this.version = version;
  }

  public List<String> getAuthors() {
    return authors;
  }

  public void setAuthors(List<String> authors) {
    this.authors = authors;
  }

  public List<String> getPermissions() {
    return permissions;
  }

  public void setPermissions(List<String> permissions) {
    this.permissions = permissions;
  }
} public class JsonHandler {
    private final MCBans plugin;
    private final ActionLog log;
    private final ConfigurationManager config;

    public JsonHandler(MCBans plugin) {
        this.plugin = plugin;
        this.log = plugin.getLog();
        this.config = plugin.getConfigs();
    }

    public JSONObject get_data(String json_text) {
        try {
            return new JSONObject(json_text);
        } catch (JSONException e) {
            if (config.isDebug()) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public HashMap<String, String> mainRequest(HashMap<String, String> items) {
        HashMap<String, String> out = new HashMap<String, String>();
        String url_req = this.urlparse(items);
        String json_text = this.request_from_api(url_req);
        if (config.isDebug()){
            log.info("Requested: '" + url_req + "'");
            log.info("Converting response '" + json_text + "'");
        }
        if (json_text == null || json_text.length() <= 0){
            if (config.isDebug()) log.severe("Null Response. Please contact an MCBans developer.");
            out.clear();
            return out;
        }
        
        JSONObject output = this.get_data(json_text);
        if (output != null) {
            Iterator<String> i = output.keys();
            if (i != null) {
                while (i.hasNext()) {
                    String next = i.next();
                    try {
                        out.put(next, output.getString(next));
                    } catch (JSONException e) {
                        if (config.isDebug()) {
                            log.severe("JSON Error On Retrieval!");
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return out;
    }

    public JSONObject hdl_jobj(HashMap<String, String> items) {
        String urlReq = urlparse(items);
        String jsonText = request_from_api(urlReq);
        return get_data(jsonText);
    }

    public String request_from_api(String data) {
        return request_from_api(data, plugin.apiServer);
    }

    public String request_from_api(String data, String server) {
        OutputStreamWriter wr = null;
        BufferedReader rd = null;
        try {
            // check valid api key
            if (!config.isValidApiKey()){
                return "";
            }
            if (config.isDebug()) {
                log.info("Sending API request: '" + data + "'");
            }
            URL url = new URL("http://" + server + "/v3/" + config.getApiKey());
            URLConnection conn = url.openConnection();
            conn.setConnectTimeout(25000);
            conn.setReadTimeout(25000);
            conn.setDoOutput(true);
            
            wr = new OutputStreamWriter(conn.getOutputStream());
            wr.write(data);
            wr.flush();
            
            StringBuilder buf = new StringBuilder();
            rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = rd.readLine()) != null) {
                buf.append(line);
            }
            
            if (plugin.getConfigs().isDebug()) {
                log.info("Result: " + buf.toString());
            }
            return buf.toString();
        } catch (Exception e) {
            if (config.isDebug()) {
                log.severe("Error fetching data!");
                e.printStackTrace();
            }
            return "";
        } finally {
            if (wr != null){
                try { wr.close(); } catch (Exception e) {}
            }
            if (rd != null){
                try { rd.close(); } catch (Exception e) {}
            }
        }
    }

    public String urlparse(HashMap<String, String> items) {
        String data = "";
        try {
            for (Map.Entry<String, String> entry : items.entrySet()) {
                String key = entry.getKey();
                String val = entry.getValue();
                if(val!=null && !val.equals("")){
                	if (data.equals("")) {
                    	data = URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(val, "UTF-8");
                	} else {
                		data += "&" + URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(val, "UTF-8");
                	}
                }
            }
        } catch (UnsupportedEncodingException e) {
            if (config.isDebug()) {
                e.printStackTrace();
            }
        }
        return data;
    }
} public class TooLargeException extends Exception{
} public class ActionLog {
    private final Logger logger = Logger.getLogger("Minecraft");
    private final String logPrefix = "[MCBans] ";

    private final MCBans plugin;
    private static ActionLog instance;

    public ActionLog(final MCBans plugin){
        instance = this;
        this.plugin = plugin;
    }

    public void log(final Level level, final String message, final boolean logToFile){
        logger.log(level, logPrefix + message);
        if (logToFile && plugin.getConfigs() != null && plugin.getConfigs().isEnableLog()) {
            writeLog(message);
        }
    }

    public void log(final Level level, final String message){
        log (level, message, true);
    }

    public void fine(final String message){
        log(Level.FINE, message);
    }
    public void info(final String message){
        log(Level.INFO, message);
    }
    public void warning(final String message){
        log(Level.WARNING, message);
    }
    public void severe(final String message){
        log(Level.SEVERE, message);
    }

    /**
     * Write message to mcbans log file
     * @param message message to write
     */
    private void writeLog(final String message){
        try {
            appendLine(plugin.getConfigs().getLogFile(),
                    "[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "] " + message);
        } catch (IOException ex) {
            logger.warning(logPrefix + "Could not write log file! " + ex.getMessage());
        }
    }

    /**
     * Append line to file
     * @param file target file
     * @param line message to append line
     * @throws IOException IOException
     */
    private void appendLine(final String file, final String line) throws IOException  {
        PrintWriter outputStream = null;
        try {
            outputStream = new PrintWriter(new FileWriter(file, true));
            outputStream.println(line);
        } finally {
            if(outputStream != null) {
                outputStream.close();
            }
        }
    }

    public static ActionLog getInstance(){
        return instance;
    }
} public class MCBans extends JavaPlugin {
  public final String apiRequestSuffix = "4.4.3";
  private static MCBans instance;

  private OfflineBanList offlineBanList;
  public int taskID = 0;
  public HashMap<String, Integer> connectionData = new HashMap<String, Integer>();
  public HashMap<String, BanResponse> playerCache = new HashMap<>();
  public HashMap<String, Long> resetTime = new HashMap<String, Long>();
  public Properties lastSyncs = new Properties();
  public ArrayList<String> mcbStaff = new ArrayList<String>();
  public long last_req = 0;
  public long timeRecieved = 0;
  public Thread callbackThread = null;
  public BanSync bansync = null;
  public Thread syncBan = null;
  public PendingActions pendingActions = null;
  public long lastID = -1;
  public File syncIni = null;
  public long lastSync = 0;
  public String lastType = "";
  public boolean syncRunning = false;
  public long lastCallBack = 0;
  public long lastPendingActions = 0;
  public static boolean AnnounceAll = false;
  public String apiServer = null;

  private ActionLog log = null;
  private RollbackHandler rbHandler = null;
  private boolean ncpEnabled = false;
  private boolean acEnabled = false;
  private ConfigurationManager config;
  private MCBansCommandHandler commandHandler;


  @Override
  public void onDisable() {
    if (callbackThread != null) {
      if (callbackThread.isAlive()) {
        callbackThread.interrupt();
      }
    }
    if (syncBan != null) {
      if (syncBan.isAlive()) {
        syncBan.interrupt();
      }
    }

    getServer().getScheduler().cancelTasks(this);
    instance = null;

    final PluginDescriptionFile pdfFile = this.getDescription();
    log.info(pdfFile.getName() + " version " + pdfFile.getVersion() + " is disabled!");
  }

  @Override
  public void onEnable() {

    try {
      offlineBanList = new OfflineBanList(this);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    instance = this;
    PluginManager pm = getServer().getPluginManager();
    log = new ActionLog(this); // setup logger

    // check online-mode, Do NOT remove this check!
        /*if (!this.getServer().getOnlineMode()) {
            log.severe("This server is not in online mode!");
            pm.disablePlugin(this);
            return;
        }*/
    //load sync configuration
    syncIni = new File(this.getDataFolder(), "sync.ini");
    if (syncIni.exists()) {
      try {
        lastSyncs.load(new FileInputStream(syncIni));
        lastID = Long.valueOf(lastSyncs.getProperty("lastId"));
        lastType = lastSyncs.getProperty("lastType");
      } catch (Exception e) {
      }
    } else {
      lastType = "bans";
      lastID = -1;
    }

    // load configuration
    config = new ConfigurationManager(this);
    try {
      config.loadConfig(true);
    } catch (Exception ex) {
      log.warning("An error occurred while trying to load the config file.");
      ex.printStackTrace();
    }
    if (!pm.isPluginEnabled(this)) {
      return;
    }

    // load language
    log.info("Loading language file: " + config.getLanguage());
    I18n.init(config.getLanguage());

    pm.registerEvents(new PlayerListener(this), this);

    // setup permissions
    Perms.setupPermissionHandler();

    // regist commands
    commandHandler = new MCBansCommandHandler(this);
    registerCommands();

    MainCallBack thisThread = new MainCallBack(this);
    thisThread.start();

    // ban sync
    bansync = new BanSync(this);
    bansync.start();

    pendingActions = new PendingActions(this);
    pendingActions.start();

    ServerChoose serverChooser = new ServerChoose(this);
    (new Thread(serverChooser)).start();

    // rollback handler
    rbHandler = new RollbackHandler(this);
    rbHandler.setupHandler();

    // hookup integration plugin
    //checkPlugin(true);
    //if (ncpEnabled) log.info("NoCheatPlus plugin found! Enabled this integration!");
    //if (acEnabled) log.info("AntiCheat plugin found! Enabled this integration!");

    final PluginDescriptionFile pdfFile = this.getDescription();
    log.info(pdfFile.getName() + " version " + pdfFile.getVersion() + " is enabled!");
  }

  @Override
  public boolean onCommand(final CommandSender sender, final Command command, final String label, final String[] args) {
    return commandHandler.onCommand(sender, command, label, args);
  }

  @Override
  public List<String> onTabComplete(final CommandSender sender, final Command command, final String alias, final String[] args) {
    return commandHandler.onTabComplete(sender, command, alias, args);
  }

  private void registerCommands() {
    List<BaseCommand> cmds = new ArrayList<BaseCommand>();
    // Banning Commands
    cmds.add(new CommandBan());
    cmds.add(new CommandGlobalban());
    cmds.add(new CommandTempban());
    cmds.add(new CommandRban());

    // IP Banning Commands
    cmds.add(new CommandBanip());

    // Other action commands
    cmds.add(new CommandUnban());
    cmds.add(new CommandKick());

    // Other commands
    cmds.add(new CommandLookup());
    cmds.add(new CommandBanlookup());
    cmds.add(new CommandAltlookup());
    cmds.add(new CommandMCBans());
    cmds.add(new CommandPrevious());

    cmds.add(new CommandMCBansSettings());

    for (final BaseCommand cmd : cmds) {
      commandHandler.registerCommand(cmd);
    }
  }

  public void debug(final String message) {
    if (getConfigs().isDebug()) {
      getLog().info(message);
    }
  }

    /*public void checkPlugin(final boolean startup){
        // Check NoCheatPlus
        //Plugin checkNCP = getServer().getPluginManager().getPlugin("NoCheatPlus");
        this.ncpEnabled = (checkNCP != null && checkNCP instanceof NoCheatPlus);
        // Check AntiCheat
        //Plugin checkAC = getServer().getPluginManager().getPlugin("AntiCheat");
        this.acEnabled = (checkAC != null && checkAC instanceof Anticheat);

        if (!startup){
            if (ncpEnabled) ncpEnabled = (checkNCP.isEnabled());
            if (acEnabled) acEnabled = (checkAC.isEnabled());
        }
    }*/

  public boolean isEnabledNCP() {
    return this.ncpEnabled;
  }

  public boolean isEnabledAC() {
    return this.acEnabled;
  }

  public RollbackHandler getRbHandler() {
    return this.rbHandler;
  }

  public MCBansAPI getAPI(final Plugin plugin) {
    return MCBansAPI.getHandle(this, plugin);
  }


  public static UUID fromString(String uuid){
    return UUID.fromString(uuid.replaceAll("(?ism)([a-z0-9]{8})([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{12})", "$1-$2-$3-$4-$5"));
  }

  public static Player getPlayer(Plugin plugin, UUID uuid) {
    return plugin.getServer().getPlayer(uuid);
  }

  public static Player getPlayer(Plugin plugin, String target) {
    return plugin.getServer().getPlayerExact(target);
  }

  /*public void act(String act, String uuid){
    uuid = uuid.replaceAll("(?ism)([a-z0-9]{8})([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{12})", "$1-$2-$3-$4-$5");
    if(uuid.matches("([a-z0-9]{8})-([a-z0-9]{4})-([a-z0-9]{4})-([a-z0-9]{4})-([a-z0-9]{12})")){
      OfflinePlayer d = getServer().getOfflinePlayer(UUID.fromString(uuid));
      if (d != null){
        if(d.isBanned()){
              if(act.equals("unban")){
                  d.setBanned(false);
              }
          }else{
              if(act.equals("ban")){
                  d.setBanned(true);
              }
          }
      }
    }
  }*/
  public ConfigurationManager getConfigs() {
    return this.config;
  }

  public ActionLog getLog() {
    return this.log;
  }

  public static String getPrefix() {
    return instance.config.getPrefix();
  }

  public static MCBans getInstance() {
    return instance;
  }

  public OfflineBanList getOfflineBanList() {
    return offlineBanList;
  }
} public enum Perms {
    /* Permission Nodes */

    // Admin permission
    ADMIN           ("admin"),
    
    // Ban permissions
    BAN_GLOBAL      ("ban.global"),
    BAN_LOCAL       ("ban.local"),
    BAN_TEMP        ("ban.temp"),
    BAN_ROLLBACK    ("ban.rollback"),
    BAN_IP          ("ban.ip"),
    UNBAN           ("unban"),
    KICK            ("kick"),

    // View permissions
    VIEW_ALTS       ("view.alts"),
    VIEW_BANS       ("view.bans"),
    VIEW_STAFF      ("view.staff"),
    VIEW_PREVIOUS   ("view.previous"),
    VIEW_PROXY      ("view.proxy"),
    ANNOUNCE        ("announce"),
    HIDE_VIEW       ("hideview"),
    
    // Exempt
    EXEMPT_KICK     ("kick.exempt"),
    EXEMPT_BAN      ("ban.exempt"),
    EXEMPT_MAXALTS	("maxalts.exempt"),

    // Others
    LOOKUP_PLAYER   ("lookup.player"),
    LOOKUP_BAN      ("lookup.ban"),
    LOOKUP_ALT      ("lookup.alt"),
    ;

    // Node header
    final static String HEADER = "mcbans.";
    private String node;

    /**
     * Constructor
     * @param node Permission node excluded the header
     */
    Perms(final String node){
        this.node = HEADER + node;
    }

    /**
     * Check permissible has this permission.
     * @param perm check target Sender, Player etc.
     * @return true if permissible has this permission.
     */
    public boolean has(final Permissible perm){
        if (perm == null) return false;
        return handler.has(perm, this.node);
    }

    /**
     * Check player has this permission.
     * @param playerName check target player name.
     * @return true if permissible has this permission.
     */
    public boolean has(final String playerName){
        if (playerName == null) return false;
        return has(MCBans.getInstance().getServer().getPlayer(playerName));
    }

    /**
     * Check permissible has that permission.
     * @param perm check target Sender, Player etc.
     * @param node Permission node excluded the header
     * @return true if permissible has that permission.
     */
    public static boolean has(final Permissible perm, String node) {
        if (perm == null || node == null) return false;
        return handler.has(perm, HEADER + node);
    }

    /**
     * Send message to players has this permission.
     * @param message send message.
     */
    public void message(final String message){
        for (Player player : Bukkit.getServer().getOnlinePlayers()){
            if (this.has(player)){
                Util.message(player, message);
            }
        }
    }
    
    public String getNode(){
        return this.node;
    }

    public Set<Player> getPlayers(){
        Set<Player> players = new HashSet<Player>();
        for (Player player : Bukkit.getServer().getOnlinePlayers()){
            if (this.has(player)){
                players.add(player);
            }
        }
        return players;
    }
    
    public Set<String> getPlayerNames(){
        Set<String> names = new HashSet<String>();
        for (Player player : getPlayers()){
            names.add(player.getName());
        }
        return names;
    }

    /* PermissionHandler */
    private static PermissionHandler handler = null;
    public static void setupPermissionHandler(){
        if (handler == null){
            handler = PermissionHandler.getInstance();
        }
        handler.setupPermissions();
    }
}
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.stream.Collectors;
import com.mcbans.client.BadApiKeyException;
import com.mcbans.client.Client;
import com.mcbans.client.ConnectionPool;
import com.mcbans.client.InformationCallbackClient;
import com.mcbans.domain.models.client.Plugin;
import com.mcbans.plugin.request.JsonHandler;
import com.mcbans.utils.TooLargeException;
import org.bukkit.ChatColor;
import com.mcbans.plugin.ActionLog;
import com.mcbans.plugin.MCBans;
import com.mcbans.plugin.permission.Perms;
package com.mcbans.plugin.callBacks;




public class MainCallBack {
  private final MCBans plugin;
  private final ActionLog log;