@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
  public void onPlayerJoin(PlayerJoinEvent event) {
    if (!event.getPlayer().hasPermission("bm.notify.update")) return;

    Message.get("update.notify").sendTo(event.getPlayer());
    event
        .getPlayer()
        .sendMessage(ChatColor.GOLD + "http://dev.bukkit.org/bukkit-plugins/ban-management/");
  }
  @EventHandler(priority = EventPriority.MONITOR)
  public void onPlayerLogin(final PlayerLoginEvent event) {
    if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) {
      return;
    }

    if (plugin.getGeoIpConfig().isEnabled()
        && !event.getPlayer().hasPermission("bm.exempt.country")) {
      try {
        CountryResponse countryResponse =
            plugin.getGeoIpConfig().getCountryDatabase().country(event.getAddress());

        if (!plugin.getGeoIpConfig().isCountryAllowed(countryResponse)) {
          Message message =
              Message.get("deniedCountry")
                  .set("country", countryResponse.getCountry().getName())
                  .set("countryIso", countryResponse.getCountry().getIsoCode());
          event.disallow(PlayerLoginEvent.Result.KICK_BANNED, message.toString());
          return;
        }

      } catch (IOException | GeoIp2Exception e) {
      }
    }

    if (plugin.getConfiguration().getMaxOnlinePerIp() > 0) {
      long ip = IPUtils.toLong(event.getAddress());
      int count = 0;

      for (Player player : plugin.getServer().getOnlinePlayers()) {
        if (IPUtils.toLong(player.getAddress().getAddress()) == ip) {
          count++;
        }
      }

      if (count >= plugin.getConfiguration().getMaxOnlinePerIp()) {
        event.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.getString("deniedMaxIp"));
        return;
      }
    }

    if (!plugin.getConfiguration().isDuplicateIpCheckEnabled()) {
      return;
    }

    plugin
        .getServer()
        .getScheduler()
        .runTaskLaterAsynchronously(
            plugin,
            new Runnable() {

              @Override
              public void run() {
                final long ip = IPUtils.toLong(event.getAddress());
                final UUID uuid = event.getPlayer().getUniqueId();
                List<PlayerData> duplicates = plugin.getPlayerBanStorage().getDuplicates(ip);

                if (duplicates.isEmpty()) {
                  return;
                }

                if (plugin.getConfiguration().isDenyAlts()) {
                  try {
                    denyAlts(duplicates, uuid);
                  } catch (SQLException e) {
                    PluginLogger.warn(e);
                  }
                }

                if (plugin.getConfiguration().isPunishAlts()) {
                  try {
                    punishAlts(duplicates, uuid);
                  } catch (SQLException e) {
                    PluginLogger.warn(e);
                  }
                }

                StringBuilder sb = new StringBuilder();

                for (PlayerData player : duplicates) {
                  if (player.getUUID().equals(uuid)) {
                    continue;
                  }

                  sb.append(player.getName());
                  sb.append(", ");
                }

                if (sb.length() == 0) {
                  return;
                }
                if (sb.length() >= 2) {
                  sb.setLength(sb.length() - 2);
                }

                Message message = Message.get("duplicateIP");
                message.set("player", event.getPlayer().getName());
                message.set("players", sb.toString());

                CommandUtils.broadcast(message.toString(), "bm.notify.duplicateips");
              }
            },
            20L);
  }
  @EventHandler(priority = EventPriority.HIGHEST)
  public void banCheck(final AsyncPlayerPreLoginEvent event) {
    if (plugin.getConfiguration().isCheckOnJoin()) {
      // Check for new bans/mutes
      if (!plugin.getIpBanStorage().isBanned(event.getAddress())) {
        try {
          IpBanData ban = plugin.getIpBanStorage().retrieveBan(IPUtils.toLong(event.getAddress()));

          if (ban != null) {
            plugin.getIpBanStorage().addBan(ban);
          }
        } catch (SQLException e) {
          PluginLogger.warn(e);
        }
      }

      if (!plugin.getPlayerBanStorage().isBanned(event.getUniqueId())) {
        try {
          PlayerBanData ban = plugin.getPlayerBanStorage().retrieveBan(event.getUniqueId());

          if (ban != null) {
            plugin.getPlayerBanStorage().addBan(ban);
          }
        } catch (SQLException e) {
          PluginLogger.warn(e);
        }
      }

      if (!plugin.getPlayerMuteStorage().isMuted(event.getUniqueId())) {
        try {
          PlayerMuteData mute = plugin.getPlayerMuteStorage().retrieveMute(event.getUniqueId());

          if (mute != null) {
            plugin.getPlayerMuteStorage().addMute(mute);
          }
        } catch (SQLException e) {
          PluginLogger.warn(e);
        }
      }
    }

    if (plugin.getIpRangeBanStorage().isBanned(event.getAddress())) {
      IpRangeBanData data = plugin.getIpRangeBanStorage().getBan(event.getAddress());

      if (data.hasExpired()) {
        try {
          plugin.getIpRangeBanStorage().unban(data, plugin.getPlayerStorage().getConsole());
        } catch (SQLException e) {
          PluginLogger.warn(e);
        }

        return;
      }

      Message message;

      if (data.getExpires() == 0) {
        message = Message.get("baniprange.ip.disallowed");
      } else {
        message = Message.get("tempbaniprange.ip.disallowed");
        message.set("expires", DateUtils.getDifferenceFormat(data.getExpires()));
      }

      message.set("ip", event.getAddress().toString());
      message.set("reason", data.getReason());
      message.set("actor", data.getActor().getName());

      event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_BANNED);
      event.setKickMessage(message.toString());
      return;
    }

    if (plugin.getIpBanStorage().isBanned(event.getAddress())) {
      IpBanData data = plugin.getIpBanStorage().getBan(event.getAddress());

      if (data.hasExpired()) {
        try {
          plugin.getIpBanStorage().unban(data, plugin.getPlayerStorage().getConsole());
        } catch (SQLException e) {
          PluginLogger.warn(e);
        }

        return;
      }

      Message message;

      if (data.getExpires() == 0) {
        message = Message.get("banip.ip.disallowed");
      } else {
        message = Message.get("tempbanip.ip.disallowed");
        message.set("expires", DateUtils.getDifferenceFormat(data.getExpires()));
      }

      message.set("ip", event.getAddress().toString());
      message.set("reason", data.getReason());
      message.set("actor", data.getActor().getName());

      event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_BANNED);
      event.setKickMessage(message.toString());
      handleJoinDeny(event.getAddress().toString(), data.getReason());
      return;
    }

    PlayerBanData data = plugin.getPlayerBanStorage().getBan(event.getUniqueId());

    if (data != null && data.hasExpired()) {
      try {
        plugin.getPlayerBanStorage().unban(data, plugin.getPlayerStorage().getConsole());
      } catch (SQLException e) {
        PluginLogger.warn(e);
      }

      return;
    }

    if (data == null) {
      return;
    }

    Message message;

    if (data.getExpires() == 0) {
      message = Message.get("ban.player.disallowed");
    } else {
      message = Message.get("tempban.player.disallowed");
      message.set("expires", DateUtils.getDifferenceFormat(data.getExpires()));
    }

    message.set("player", data.getPlayer().getName());
    message.set("reason", data.getReason());
    message.set("actor", data.getActor().getName());

    event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_BANNED);
    event.setKickMessage(message.toString());
    handleJoinDeny(data.getPlayer(), data.getReason());
  }
  private void handleJoinDeny(String ip, String reason) {
    Message message = Message.get("deniedNotify.ip").set("ip", ip).set("reason", reason);

    CommandUtils.broadcast(message.toString(), "bm.notify.denied.ip");
  }
  private void handleJoinDeny(PlayerData player, String reason) {
    Message message =
        Message.get("deniedNotify.player").set("player", player.getName()).set("reason", reason);

    CommandUtils.broadcast(message.toString(), "bm.notify.denied.player");
  }
  @Override
  public boolean onCommand(
      final CommandSender sender, Command command, String commandName, String[] args) {
    CommandParser parser = new CommandParser(args);
    args = parser.getArgs();
    final boolean isSilent = parser.isSilent();

    if (isSilent && !sender.hasPermission(command.getPermission() + ".silent")) {
      sender.sendMessage(Message.getString("sender.error.noPermission"));
      return true;
    }

    if (args.length < 1) {
      return false;
    }

    if (CommandUtils.isValidNameDelimiter(args[0])) {
      CommandUtils.handleMultipleNames(sender, commandName, args);
      return true;
    }

    if (args[0].equalsIgnoreCase(sender.getName())) {
      sender.sendMessage(Message.getString("sender.error.noSelf"));
      return true;
    }

    String playerName = args[0];
    final Player player = plugin.getServer().getPlayer(playerName);

    if (player == null) {
      Message message = Message.get("sender.error.offline").set("[player]", playerName);

      sender.sendMessage(message.toString());
      return true;
    } else if (!sender.hasPermission("bm.exempt.override.kick")
        && player.hasPermission("bm.exempt.kick")) {
      Message.get("sender.error.exempt").set("player", player.getName()).sendTo(sender);
      return true;
    }

    final String reason = args.length > 1 ? CommandUtils.getReason(1, args).getMessage() : "";

    plugin
        .getServer()
        .getScheduler()
        .runTaskAsynchronously(
            plugin,
            new Runnable() {

              @Override
              public void run() {
                final PlayerData actor;

                if (sender instanceof Player) {
                  try {
                    actor =
                        plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes((Player) sender));
                  } catch (SQLException e) {
                    sender.sendMessage(Message.get("sender.error.exception").toString());
                    e.printStackTrace();
                    return;
                  }
                } else {
                  actor = plugin.getPlayerStorage().getConsole();
                }

                final Message kickMessage;

                if (reason.isEmpty()) {
                  kickMessage = Message.get("kick.player.noReason");
                } else {
                  kickMessage = Message.get("kick.player.reason").set("reason", reason);
                }

                kickMessage
                    .set("displayName", player.getDisplayName())
                    .set("player", player.getName())
                    .set("playerId", player.getUniqueId().toString())
                    .set("actor", actor.getName());

                plugin
                    .getServer()
                    .getScheduler()
                    .runTask(
                        plugin,
                        new Runnable() {

                          @Override
                          public void run() {
                            player.kickPlayer(kickMessage.toString());

                            Message message =
                                Message.get(
                                    reason.isEmpty()
                                        ? "kick.notify.noReason"
                                        : "kick.notify.reason");
                            message
                                .set("player", player.getName())
                                .set("actor", actor.getName())
                                .set("reason", reason);

                            if (isSilent || !sender.hasPermission("bm.notify.kick")) {
                              message.sendTo(sender);
                            }

                            if (!isSilent)
                              CommandUtils.broadcast(message.toString(), "bm.notify.kick");
                          }
                        });
              }
            });

    return true;
  }
  @Override
  public boolean onCommand(
      final CommandSender sender, Command command, String commandName, String[] args) {
    CommandParser parser = new CommandParser(args);
    args = parser.getArgs();
    final boolean isSilent = parser.isSilent();

    if (isSilent && !sender.hasPermission(command.getPermission() + ".silent")) {
      sender.sendMessage(Message.getString("sender.error.noPermission"));
      return true;
    }

    if (args.length < 2) {
      return false;
    }

    if (CommandUtils.isValidNameDelimiter(args[0])) {
      CommandUtils.handleMultipleNames(sender, commandName, args);
      return true;
    }

    if (args[0].equalsIgnoreCase(sender.getName())) {
      sender.sendMessage(Message.getString("sender.error.noSelf"));
      return true;
    }

    // Check if UUID vs name
    final String playerName = args[0];
    final boolean isUUID = playerName.length() > 16;

    Player onlinePlayer;

    if (isUUID) {
      onlinePlayer = plugin.getServer().getPlayer(UUID.fromString(playerName));
    } else {
      onlinePlayer = plugin.getServer().getPlayer(playerName);
    }

    if (onlinePlayer == null) {
      if (!sender.hasPermission("bm.command.report.offline")) {
        sender.sendMessage(Message.getString("sender.error.offlinePermission"));
        return true;
      }
    } else if (!sender.hasPermission("bm.exempt.override.report")
        && onlinePlayer.hasPermission("bm.exempt.report")) {
      Message.get("sender.error.exempt").set("player", onlinePlayer.getName()).sendTo(sender);
      return true;
    }

    final String reason = CommandUtils.getReason(1, args);

    plugin
        .getServer()
        .getScheduler()
        .runTaskAsynchronously(
            plugin,
            new Runnable() {

              @Override
              public void run() {
                final PlayerData player = CommandUtils.getPlayer(sender, playerName);

                if (player == null) {
                  sender.sendMessage(
                      Message.get("sender.error.notFound").set("player", playerName).toString());
                  return;
                }

                if (plugin.getExemptionsConfig().isExempt(player, "ban")) {
                  sender.sendMessage(
                      Message.get("sender.error.exempt").set("player", playerName).toString());
                  return;
                }

                try {
                  if (plugin.getPlayerReportStorage().isRecentlyReported(player)) {
                    Message.get("report.error.cooldown").sendTo(sender);
                    return;
                  }
                } catch (SQLException e) {
                  sender.sendMessage(Message.get("sender.error.exception").toString());
                  e.printStackTrace();
                  return;
                }

                final PlayerData actor = CommandUtils.getActor(sender);

                if (actor == null) return;

                PlayerReportData report = new PlayerReportData(player, actor, reason);

                try {
                  plugin.getPlayerReportStorage().report(report, isSilent);
                } catch (SQLException e) {
                  sender.sendMessage(Message.get("sender.error.exception").toString());
                  e.printStackTrace();
                }
              }
            });

    return true;
  }
  @Override
  public boolean onCommand(
      final CommandSender sender, Command command, String commandName, String[] args) {
    if (args.length < 2) return false;

    final String type = args[0];

    if (!DeleteCommand.types.contains(type)) {
      Message.get("bmdelete.error.invalid").sendTo(sender);
      return true;
    } else if (!sender.hasPermission("bm.command.delete." + type)) {
      Message.get("sender.error.noPermission").sendTo(sender);
      return true;
    }

    final ArrayList<Integer> ids = new ArrayList<>();

    for (int i = 1; i < args.length; i++) {
      try {
        ids.add(Integer.parseInt(args[i]));
      } catch (NumberFormatException e) {
        Message.get("bmdelete.error.invalidId").set("id", args[i]).sendTo(sender);
        return true;
      }
    }

    if (ids.size() == 0) return false;

    plugin
        .getServer()
        .getScheduler()
        .runTaskAsynchronously(
            plugin,
            new Runnable() {

              @Override
              public void run() {
                int rows = 0;

                try {
                  switch (type) {
                    case "banrecords":
                      rows = plugin.getPlayerBanRecordStorage().deleteIds(ids);
                      break;

                    case "muterecords":
                      rows = plugin.getPlayerMuteRecordStorage().deleteIds(ids);
                      break;

                    case "notes":
                      rows = plugin.getPlayerNoteStorage().deleteIds(ids);
                      break;

                    case "kicks":
                      rows = plugin.getPlayerKickStorage().deleteIds(ids);
                      break;

                    case "warnings":
                      rows = plugin.getPlayerWarnStorage().deleteIds(ids);
                      break;
                  }
                } catch (SQLException e) {
                  sender.sendMessage(Message.get("sender.error.exception").toString());
                  e.printStackTrace();
                  return;
                }

                Message.get("bmdelete.notify").set("rows", rows).sendTo(sender);
              }
            });

    return true;
  }
  @Override
  public boolean onCommand(
      final CommandSender sender, Command command, String commandName, String[] args) {
    if (args.length < 1) {
      return false;
    }

    if (CommandUtils.isValidNameDelimiter(args[0])) {
      CommandUtils.handleMultipleNames(sender, commandName, args);
      return true;
    }

    final String ipStr = args[0];
    final boolean isName = !InetAddresses.isInetAddress(ipStr);

    if (isName && ipStr.length() > 16) {
      Message message = Message.get("sender.error.invalidIp");
      message.set("ip", ipStr);

      sender.sendMessage(message.toString());
      return true;
    }

    final String reason = args.length > 1 ? CommandUtils.getReason(1, args).getMessage() : "";

    plugin
        .getServer()
        .getScheduler()
        .runTaskAsynchronously(
            plugin,
            new Runnable() {

              @Override
              public void run() {
                final Long ip = CommandUtils.getIp(ipStr);

                if (ip == null) {
                  sender.sendMessage(
                      Message.get("sender.error.notFound").set("player", ipStr).toString());
                  return;
                }

                if (!plugin.getIpMuteStorage().isMuted(ip)) {
                  Message message = Message.get("unmuteip.error.noExists");
                  message.set("ip", ipStr);

                  sender.sendMessage(message.toString());
                  return;
                }

                IpMuteData mute = plugin.getIpMuteStorage().getMute(ip);

                final PlayerData actor = CommandUtils.getActor(sender);

                if (actor == null) return;

                boolean unmuted;

                try {
                  unmuted = plugin.getIpMuteStorage().unmute(mute, actor, reason);
                } catch (SQLException e) {
                  sender.sendMessage(Message.get("sender.error.exception").toString());
                  e.printStackTrace();
                  return;
                }

                if (!unmuted) {
                  return;
                }

                Message message = Message.get("unmuteip.notify");
                message.set("ip", ipStr).set("actor", actor.getName()).set("reason", reason);

                if (!sender.hasPermission("bm.notify.unmuteip")) {
                  message.sendTo(sender);
                }

                CommandUtils.broadcast(message.toString(), "bm.notify.unmuteip");
              }
            });

    return true;
  }