/**
  * Removes the timeout role from the given user. This does NOT create or manage any
  * storage/persistence, it only sets the user's roles
  *
  * @param user The user to remove the timeout role
  * @param server The server on which to remove the user from the timeout role
  * @param invocationChannel The channel to send messages on error
  */
 public boolean removeTimeoutRole(User user, Server server, Channel invocationChannel) {
   String serverId = server.getId();
   TempServerConfig serverConfig = serverStorage.get(serverId);
   if (serverConfig == null) {
     serverConfig = new TempServerConfig(serverId);
     serverStorage.put(serverId, serverConfig);
   }
   ServerTimeoutStorage storage = serverConfig.getServerTimeouts();
   String serverName = server.getName();
   if (storage != null && storage.getTimeoutRoleId() != null) {
     String timeoutRoleId = storage.getTimeoutRoleId();
     Role timeoutRole = apiClient.getRole(timeoutRoleId, server);
     if (timeoutRole != NO_ROLE) {
       //  Get roles
       Set<Role> userRoles =
           apiClient.getMemberRoles(apiClient.getUserMember(user, server), server);
       //  Delete the ban role
       LinkedHashSet<String> newRoles = new LinkedHashSet<>(userRoles.size() - 1);
       userRoles
           .stream()
           .map(Role::getId)
           .filter(s -> !timeoutRoleId.equals(s))
           .forEach(newRoles::add);
       //  Update
       apiClient.updateRoles(user, server, newRoles);
       return userRoles.size() == newRoles.size();
     } else {
       LOGGER.warn(
           "Timeout role ID {} for server {} ({}) does not exist",
           timeoutRoleId,
           serverName,
           serverId);
       apiClient.sendMessage(
           loc.localize("message.mod.timeout.bad_role", timeoutRoleId), invocationChannel);
     }
   } else {
     storage = new ServerTimeoutStorage();
     serverConfig.setServerTimeouts(storage);
     serverStorage.put(serverId, serverConfig);
     LOGGER.warn(
         "Timeout role for server {} ({}) is not configured",
         storage.getTimeoutRoleId(),
         serverName,
         serverId);
     apiClient.sendMessage(loc.localize("message.mod.timeout.not_configured"), invocationChannel);
   }
   return false;
 }
 public void onTimeoutExpire(User user, Server server) {
   String serverId = server.getId();
   TempServerConfig serverConfig = serverStorage.get(serverId);
   if (serverConfig == null) {
     serverConfig = new TempServerConfig(serverId);
     serverStorage.put(serverId, serverConfig);
   }
   ServerTimeoutStorage storage = serverConfig.getServerTimeouts();
   if (storage != null) {
     ServerTimeout timeout = storage.getTimeouts().remove(user.getId());
     if (timeout != null) {
       saveServerConfig(serverConfig);
       LOGGER.info(
           "Expiring timeout for {} ({}) in {} ({})",
           user.getUsername(),
           user.getId(),
           server.getName(),
           server.getId());
       if (apiClient.getUserById(user.getId(), server) != NO_USER) {
         apiClient.sendMessage(
             loc.localize("message.mod.timeout.expire", user.getId()), server.getId());
       }
       removeTimeoutRole(user, server, apiClient.getChannelById(server.getId()));
       return;
     }
   }
   LOGGER.warn(
       "Unable to expire: find server or timeout entry for {} ({}) in {} ({})",
       user.getUsername(),
       user.getId(),
       server.getName(),
       server.getId());
 }
 private void setTimeoutRole(MessageContext context, String args) {
   DiscordApiClient apiClient = context.getApiClient();
   if (args.isEmpty()) {
     apiClient.sendMessage(
         loc.localize("commands.mod.settimeoutrole.response.missing"), context.getChannel());
     return;
   }
   Role role = apiClient.getRole(args, context.getServer());
   if (role == NO_ROLE) {
     apiClient.sendMessage(
         loc.localize("commands.mod.settimeoutrole.response.not_found", args),
         context.getChannel());
     return;
   }
   String serverId = context.getServer().getId();
   TempServerConfig serverConfig = serverStorage.get(serverId);
   if (serverConfig == null) {
     serverConfig = new TempServerConfig(serverId);
     serverStorage.put(serverId, serverConfig);
   }
   ServerTimeoutStorage storage = serverConfig.getServerTimeouts();
   if (storage == null) {
     storage = new ServerTimeoutStorage();
     serverConfig.setServerTimeouts(storage);
   }
   storage.setTimeoutRoleId(role.getId());
   apiClient.sendMessage(
       loc.localize("commands.mod.settimeoutrole.response", role.getName(), role.getId()),
       context.getChannel());
   saveServerConfig(serverConfig);
 }
 private void setFarewell(MessageContext context, String args) {
   if (context.getServer() == null || context.getServer() == NO_SERVER) {
     return;
   }
   String serverId = context.getServer().getId();
   TempServerConfig config = serverStorage.get(serverId);
   if (config == null) {
     config = new TempServerConfig(serverId);
     serverStorage.put(serverId, config);
   }
   Channel channel = context.getChannel();
   if (args.isEmpty() || args.equalsIgnoreCase("none")) {
     config.setCustomLeaveMessage("");
     apiClient.sendMessage(loc.localize("commands.mod.farewell.response.none"), channel);
   } else if (args.equalsIgnoreCase("default")) {
     config.setCustomLeaveMessage(null);
     apiClient.sendMessage(loc.localize("commands.mod.farewell.response.default"), channel);
   } else {
     config.setCustomLeaveMessage(args);
     apiClient.sendMessage(
         loc.localize(
             "commands.mod.farewell.response.set",
             EventListener.createJoinLeaveMessage(context.getAuthor(), context.getServer(), args)),
         channel);
   }
   saveServerConfig(config);
 }
 public boolean applyTimeout(
     User issuingUser, Channel noticeChannel, Server server, User user, Duration duration) {
   String serverId = server.getId();
   if (duration != null && !duration.isNegative() && !duration.isZero()) {
     ServerTimeout timeout =
         new ServerTimeout(
             duration,
             Instant.now(),
             user.getId(),
             serverId,
             user.getUsername(),
             issuingUser.getId());
     TempServerConfig serverConfig = serverStorage.get(serverId);
     if (serverConfig == null) {
       serverConfig = new TempServerConfig(serverId);
       serverStorage.put(serverId, serverConfig);
     }
     ServerTimeoutStorage storage = serverConfig.getServerTimeouts();
     if (storage == null) {
       storage = new ServerTimeoutStorage();
       serverConfig.setServerTimeouts(storage);
     }
     if (applyTimeoutRole(user, server, noticeChannel)) {
       storage.getTimeouts().put(user.getId(), timeout);
       ScheduledFuture future =
           timeoutService.schedule(
               () -> onTimeoutExpire(user, server), duration.getSeconds(), TimeUnit.SECONDS);
       timeout.setTimerFuture(future);
       saveServerConfig(serverConfig);
       String durationStr = formatDuration(duration);
       String instantStr = formatInstant(timeout.getEndTime());
       String msg =
           loc.localize(
               "commands.mod.timeout.response",
               user.getUsername(),
               user.getId(),
               durationStr,
               instantStr);
       apiClient.sendMessage(msg, noticeChannel);
       LOGGER.info(
           "[{}] '{}': Timing out {} ({}) for {} (until {}), issued by {} ({})",
           serverId,
           server.getName(),
           user.getUsername(),
           user.getId(),
           durationStr,
           instantStr,
           issuingUser.getUsername(),
           issuingUser.getId());
     }
     //  No else with error - applyTimeoutRole does that for us
     return true;
   } else {
     LOGGER.warn("Invalid duration format");
   }
   return false;
 }
 public boolean banImpl(String userId, String serverId) {
   Map<String, String> headers = new HashMap<>();
   headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
   headers.put(HttpHeaders.AUTHORIZATION, apiClient.getToken());
   try {
     HttpResponse<JsonNode> response =
         Unirest.put(
                 "https://discordapp.com/api/guilds/"
                     + serverId
                     + "/bans/"
                     + userId
                     + "?delete-message-days=1")
             .headers(headers)
             .asJson();
     //  Ignore
     return true;
   } catch (Exception e) {
     LOGGER.warn("Exception when trying to ban " + userId, e);
     return false;
   }
 }
 private void setDnTrackChannel(MessageContext context, String args) {
   if (context.getServer() == null || context.getServer() == NO_SERVER) {
     return;
   }
   String serverId = context.getServer().getId();
   TempServerConfig config = serverStorage.get(serverId);
   if (config == null) {
     config = new TempServerConfig(serverId);
     serverStorage.put(serverId, config);
   }
   Channel channel = context.getChannel();
   if (args.equalsIgnoreCase("off")) {
     config.setDnTrackChannel(null);
     apiClient.sendMessage(loc.localize("commands.mod.dntrack.response.none"), channel);
   } else {
     config.setDnTrackChannel(channel.getId());
     apiClient.sendMessage(loc.localize("commands.mod.dntrack.response.set"), channel);
   }
   saveServerConfig(config);
 }
 public void cancelTimeout(User user, Server server, Channel invocationChannel) {
   String serverId = server.getId();
   TempServerConfig serverConfig = serverStorage.get(serverId);
   if (serverConfig == null) {
     serverConfig = new TempServerConfig(serverId);
     serverStorage.put(serverId, serverConfig);
   }
   ServerTimeoutStorage storage = serverConfig.getServerTimeouts();
   removeTimeoutRole(user, server, apiClient.getChannelById(serverId));
   if (storage != null) {
     ServerTimeout timeout = storage.getTimeouts().remove(user.getId());
     saveServerConfig(serverConfig);
     if (timeout != null) {
       SafeNav.of(timeout.getTimerFuture()).ifPresent(f -> f.cancel(true));
       LOGGER.info(
           "Cancelling timeout for {} ({}) in {} ({})",
           user.getUsername(),
           user.getId(),
           server.getName(),
           serverId);
       apiClient.sendMessage(
           loc.localize("commands.mod.stoptimeout.response", user.getUsername(), user.getId()),
           invocationChannel);
       return;
     }
   }
   LOGGER.warn(
       "Unable to cancel: cannot find server or timeout entry for {} ({}) in {} ({})",
       user.getUsername(),
       user.getId(),
       server.getName(),
       server.getId());
   apiClient.sendMessage(
       loc.localize(
           "commands.mod.stoptimeout.response.not_found", user.getUsername(), user.getId()),
       invocationChannel);
 }
  private void vanish(MessageContext context, String args) {
    Message message = context.getMessage();
    if (args.isEmpty()) {
      return;
    }

    try {
      final int cap = 100;
      final int capPages = 10;
      String[] vals = args.split(" ");
      String userId;
      int numMsgs;
      if (vals.length == 2) {
        User user = findUser(context, vals[0]);
        userId = user.getId();
        if (user == NO_USER) {
          userId = vals[0];
        }
        numMsgs = Math.max(1, Math.min(cap, Integer.parseInt(vals[1])));
      } else if (vals.length == 1) {
        userId = "";
        numMsgs = Math.max(1, Math.min(cap, Integer.parseInt(vals[0])));
      } else {
        userId = "";
        numMsgs = 10;
      }

      int limit = numMsgs;
      String before = message.getId();
      //  Limit search to 10 pages (500 msgs)
      //  Instead of deleting them when we find them, we build a list instead
      List<String> messagesToDelete = new ArrayList<>(limit);
      for (int k = 0; k < capPages && limit > 0; k++) {
        Map<String, String> headers = new HashMap<>();
        headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
        headers.put(HttpHeaders.AUTHORIZATION, apiClient.getToken());
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("limit", 50);
        queryParams.put("before", before);
        HttpResponse<JsonNode> response =
            Unirest.get(ApiConst.CHANNELS_ENDPOINT + message.getChannelId() + "/messages")
                .headers(headers)
                .queryString(queryParams)
                .asJson();
        JSONArray ret = response.getBody().getArray();
        if (userId.isEmpty()) {
          for (int i = 0; i < ret.length() && limit > 0; i++) {
            JSONObject msg = ret.getJSONObject(i);
            String mid = msg.getString("id");
            before = mid;
            messagesToDelete.add(mid);
            limit--;
          }
        } else {
          for (int i = 0; i < ret.length() && limit > 0; i++) {
            JSONObject msg = ret.getJSONObject(i);
            String mid = msg.getString("id");
            JSONObject msgUsr = msg.getJSONObject("author");
            String uid = msgUsr.getString("id");
            before = mid;
            if (userId.equals(uid)) {
              messagesToDelete.add(mid);
              limit--;
            }
          }
        }
      }
      LOGGER.info("Deleting {} messages", messagesToDelete.size());
      //  Using bulk delete endpoint
      apiClient.bulkDeleteMessages(
          context.getChannel().getId(),
          messagesToDelete.toArray(new String[messagesToDelete.size()]));
    } catch (Exception e) {
      LOGGER.warn("Unable to get messages", e);
    }
  }
 private void setNick(MessageContext context, String args) {
   if (context.getServer() == null || context.getServer() == NO_SERVER) {
     return;
   }
   String serverId = context.getServer().getId();
   Channel channel = context.getChannel();
   String[] split = args.split(" ", 2);
   String newNickname;
   if (split.length == 1) {
     //  Clearing nickname, set to empty string
     newNickname = "";
   } else {
     newNickname = split[1];
   }
   User[] mentions = context.getMessage().getMentions();
   if (mentions.length == 0) {
     apiClient.sendMessage(loc.localize("commands.mod.setnick.response.blank"), channel);
   }
   User target = mentions[0];
   Map<String, String> headers = new HashMap<>();
   headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
   headers.put(HttpHeaders.AUTHORIZATION, apiClient.getToken());
   try {
     HttpResponse<JsonNode> response =
         Unirest.patch(
                 "https://discordapp.com/api/guilds/" + serverId + "/members/" + target.getId())
             .headers(headers)
             .body("{\"nick\":\"" + newNickname + "\"}")
             .asJson();
     int status = response.getStatus();
     if (status != 204) {
       if (status == 403) {
         apiClient.sendMessage(
             loc.localize("commands.mod.setnick.response.failed.no_perms"), channel);
       } else if (status == 404) {
         apiClient.sendMessage(
             loc.localize("commands.mod.setnick.response.failed.not_found"), channel);
       } else {
         apiClient.sendMessage(
             loc.localize(
                 "commands.mod.setnick.response.unknown_error",
                 status,
                 response.getStatusText(),
                 response.getBody().toString()),
             channel);
       }
     } else {
       if (newNickname.isEmpty()) {
         apiClient.sendMessage(
             loc.localize("commands.mod.setnick.response.removed", target.getUsername()), channel);
       } else {
         apiClient.sendMessage(
             loc.localize(
                 "commands.mod.setnick.response.changed", target.getUsername(), newNickname),
             channel);
       }
     }
   } catch (UnirestException e) {
     LOGGER.warn(
         "Exception while setting nickname for {} {} in {} {}",
         target.getUsername(),
         target.getId(),
         context.getServer().getName(),
         serverId);
     LOGGER.warn("Nickname set exception", e);
   }
 }
  public void loadServerConfig(Path path) {
    boolean purge = false;
    TempServerConfig config;
    ServerTimeoutStorage storage;
    try (Reader reader = Files.newBufferedReader(path, UTF_8)) {
      config = gson.fromJson(reader, TempServerConfig.class);
      serverStorage.put(config.getServerId(), config);
      storage = config.getServerTimeouts();
      if (storage != null) {
        Server server = apiClient.getServerByID(config.getServerId());
        if (server == NO_SERVER) {
          LOGGER.warn("Rejecting {} server storage file: server not found", config.getServerId());
          return;
        }
        LOGGER.info(
            "Loaded {} ({}) server storage file",
            server.getName(),
            server.getId(),
            storage.getTimeoutRoleId());
        //  Prune expired entries
        for (Iterator<Map.Entry<String, ServerTimeout>> iter =
                storage.getTimeouts().entrySet().iterator();
            iter.hasNext(); ) {
          Map.Entry<String, ServerTimeout> e = iter.next();
          ServerTimeout timeout = e.getValue();
          String userId = timeout.getUserId();
          User user = apiClient.getUserById(userId, server);
          if (!isUserTimedOut(userId, server.getId())) {
            //  Purge!
            purge = true;
            if (user == NO_USER) {
              LOGGER.info(
                  "Ending timeout for departed user {} ({}) in {} ({})",
                  timeout.getLastUsername(),
                  userId,
                  server.getName(),
                  server.getId());
              //
              // apiClient.sendMessage(loc.localize("message.mod.timeout.expire.not_found",
              //                                    user.getId()),
              //                                    server.getId());
              //  Don't need to remove the timeout role because leaving does that for us
            } else {
              //  Duplicated from onTimeoutExpire except without remove since we're removing in an
              // iter
              LOGGER.info(
                  "Expiring timeout for {} ({}) in {} ({})",
                  user.getUsername(),
                  user.getId(),
                  server.getName(),
                  server.getId());
              //  Only send message if they still have the role
              if (removeTimeoutRole(user, server, apiClient.getChannelById(server.getId()))) {
                //
                // apiClient.sendMessage(loc.localize("message.mod.timeout.expire",
                //                                        user.getId()),
                //                                        server.getId());
              }
            }
            SafeNav.of(timeout.getTimerFuture()).ifPresent(f -> f.cancel(true));
            iter.remove();
          } else {
            //  Start our futures
            Duration duration = Duration.between(Instant.now(), timeout.getEndTime());
            ScheduledFuture future =
                timeoutService.schedule(
                    () -> onTimeoutExpire(user, server), duration.getSeconds(), TimeUnit.SECONDS);
            timeout.setTimerFuture(future);
          }
        }
      }
    } catch (IOException | JsonParseException e) {
      LOGGER.warn("Unable to load server storage file " + path.toString(), e);
      return;
    }

    if (purge) {
      saveServerConfig(config);
    }
  }