SpamFilter.java

package dev.aura.bungeechat.filter;

import com.google.common.annotations.VisibleForTesting;
import dev.aura.bungeechat.api.account.BungeeChatAccount;
import dev.aura.bungeechat.api.filter.BlockMessageException;
import dev.aura.bungeechat.api.filter.BungeeChatFilter;
import dev.aura.bungeechat.api.filter.FilterManager;
import dev.aura.bungeechat.message.Messages;
import dev.aura.bungeechat.permission.Permission;
import dev.aura.bungeechat.permission.PermissionManager;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

public class SpamFilter implements BungeeChatFilter {
  @VisibleForTesting static long expiryTimer = TimeUnit.MINUTES.toNanos(1);

  private final ConcurrentMap<UUID, Queue<Long>> playerMessageTimePointStorage;
  private final int messagesPerMinute;
  private final boolean noPermissions;

  public SpamFilter(int messagesPerMinute) {
    this(messagesPerMinute, false);
  }

  @VisibleForTesting
  SpamFilter(int messagesPerMinute, boolean noPermissions) {
    playerMessageTimePointStorage = new ConcurrentHashMap<>();
    this.messagesPerMinute = messagesPerMinute;
    this.noPermissions = noPermissions;
  }

  @Override
  public String applyFilter(BungeeChatAccount sender, String message) throws BlockMessageException {
    if (!noPermissions && PermissionManager.hasPermission(sender, Permission.BYPASS_ANTI_SPAM))
      return message;

    final UUID uuid = sender.getUniqueId();

    if (!playerMessageTimePointStorage.containsKey(uuid)) {
      playerMessageTimePointStorage.put(uuid, new ArrayDeque<>(messagesPerMinute));
    }

    final Queue<Long> timePoints = playerMessageTimePointStorage.get(uuid);
    final long now = System.nanoTime();
    final long expiry = now - expiryTimer;

    while (!timePoints.isEmpty() && (timePoints.peek() < expiry)) {
      timePoints.poll();
    }

    if (timePoints.size() >= messagesPerMinute)
      throw new ExtendedBlockMessageException(Messages.ANTI_SPAM, sender, message);

    timePoints.add(now);

    return message;
  }

  @Override
  public int getPriority() {
    return FilterManager.SPAM_FILTER_PRIORITY;
  }

  @VisibleForTesting
  void clear() {
    playerMessageTimePointStorage.clear();
  }
}