diff options
Diffstat (limited to 'EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat')
6 files changed, 419 insertions, 0 deletions
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java new file mode 100644 index 000000000..b1f14deec --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java @@ -0,0 +1,79 @@ +package com.earth2me.essentials.anticheat.checks.chat; + +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.actions.ParameterName; +import com.earth2me.essentials.anticheat.checks.Check; +import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore; +import com.earth2me.essentials.anticheat.data.DataStore; + + +/** + * Abstract base class for Chat checks, provides some convenience methods for access to data and config that's relevant + * to this checktype + */ +public abstract class ChatCheck extends Check +{ + private static final String id = "chat"; + + public ChatCheck(NoCheat plugin, String name) + { + super(plugin, id, name); + } + + @Override + public String getParameter(ParameterName wildcard, NoCheatPlayer player) + { + + if (wildcard == ParameterName.TEXT) + // Filter colors from the players message when logging + { + return getData(player).message.replaceAll("\302\247.", "").replaceAll("\247.", ""); + } + else + { + return super.getParameter(wildcard, player); + } + } + + /** + * Get the "ChatData" object that belongs to the player. Will ensure that such a object exists and if not, create + * one + * + * @param player + * @return + */ + public static ChatData getData(NoCheatPlayer player) + { + DataStore base = player.getDataStore(); + ChatData data = base.get(id); + if (data == null) + { + data = new ChatData(); + base.set(id, data); + } + return data; + } + + /** + * Get the ChatConfig object that belongs to the world that the player currently resides in. + * + * @param player + * @return + */ + public static ChatConfig getConfig(NoCheatPlayer player) + { + return getConfig(player.getConfigurationStore()); + } + + public static ChatConfig getConfig(ConfigurationCacheStore cache) + { + ChatConfig config = cache.get(id); + if (config == null) + { + config = new ChatConfig(cache.getConfiguration()); + cache.set(id, config); + } + return config; + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java new file mode 100644 index 000000000..965a374aa --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java @@ -0,0 +1,108 @@ +package com.earth2me.essentials.anticheat.checks.chat; + +import com.earth2me.essentials.anticheat.EventManager; +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore; +import com.earth2me.essentials.anticheat.config.Permissions; +import java.util.LinkedList; +import java.util.List; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + + +/** + * Central location to listen to events that are relevant for the chat checks + * + */ +public class ChatCheckListener implements Listener, EventManager +{ + private final SpamCheck spamCheck; + private final ColorCheck colorCheck; + private final NoCheat plugin; + + public ChatCheckListener(NoCheat plugin) + { + + this.plugin = plugin; + + spamCheck = new SpamCheck(plugin); + colorCheck = new ColorCheck(plugin); + } + + /** + * We listen to PlayerCommandPreprocess events because commands can be used for spamming too. + * + * @param event The PlayerCommandPreprocess Event + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void commandPreprocess(final PlayerCommandPreprocessEvent event) + { + // This type of event is derived from PlayerChatEvent, therefore + // just treat it like that + chat(event); + } + + /** + * We listen to PlayerChat events for obvious reasons + * + * @param event The PlayerChat event + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void chat(final PlayerChatEvent event) + { + boolean cancelled = false; + + final NoCheatPlayer player = plugin.getPlayer(event.getPlayer()); + final ChatConfig cc = ChatCheck.getConfig(player); + final ChatData data = ChatCheck.getData(player); + + // Remember the original message + data.message = event.getMessage(); + + // Now do the actual checks + + // First the spam check + if (cc.spamCheck && !player.hasPermission(Permissions.CHAT_SPAM)) + { + cancelled = spamCheck.check(player, data, cc); + } + + // Second the color check + if (!cancelled && cc.colorCheck && !player.hasPermission(Permissions.CHAT_COLOR)) + { + cancelled = colorCheck.check(player, data, cc); + } + + // If one of the checks requested the event to be cancelled, do it + if (cancelled) + { + event.setCancelled(cancelled); + } + else + { + // In case one of the events modified the message, make sure that + // the new message gets used + event.setMessage(data.message); + } + } + + public List<String> getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList<String> s = new LinkedList<String>(); + + ChatConfig c = ChatCheck.getConfig(cc); + if (c.spamCheck) + { + s.add("chat.spam"); + } + if (c.colorCheck) + { + s.add("chat.color"); + } + return s; + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java new file mode 100644 index 000000000..06ad5c9fc --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java @@ -0,0 +1,64 @@ +package com.earth2me.essentials.anticheat.checks.chat; + +import java.util.LinkedList; +import java.util.List; +import com.earth2me.essentials.anticheat.ConfigItem; +import com.earth2me.essentials.anticheat.actions.types.ActionList; +import com.earth2me.essentials.anticheat.config.ConfPaths; +import com.earth2me.essentials.anticheat.config.NoCheatConfiguration; +import com.earth2me.essentials.anticheat.config.Permissions; + + +/** + * Configurations specific for the "Chat" checks Every world gets one of these assigned to it, or if a world doesn't get + * it's own, it will use the "global" version + * + */ +public class ChatConfig implements ConfigItem +{ + public final boolean spamCheck; + public final String[] spamWhitelist; + public final long spamTimeframe; + public final int spamMessageLimit; + public final int spamCommandLimit; + public final ActionList spamActions; + public final boolean colorCheck; + public final ActionList colorActions; + + public ChatConfig(NoCheatConfiguration data) + { + + spamCheck = data.getBoolean(ConfPaths.CHAT_SPAM_CHECK); + spamWhitelist = splitWhitelist(data.getString(ConfPaths.CHAT_SPAM_WHITELIST)); + spamTimeframe = data.getInt(ConfPaths.CHAT_SPAM_TIMEFRAME) * 1000L; + spamMessageLimit = data.getInt(ConfPaths.CHAT_SPAM_MESSAGELIMIT); + spamCommandLimit = data.getInt(ConfPaths.CHAT_SPAM_COMMANDLIMIT); + spamActions = data.getActionList(ConfPaths.CHAT_SPAM_ACTIONS, Permissions.CHAT_SPAM); + colorCheck = data.getBoolean(ConfPaths.CHAT_COLOR_CHECK); + colorActions = data.getActionList(ConfPaths.CHAT_COLOR_ACTIONS, Permissions.CHAT_COLOR); + } + + /** + * Convenience method to split a string into an array on every occurance of the "," character, removing all + * whitespaces before and after it too. + * + * @param string The string containing text seperated by "," + * @return An array of the seperate texts + */ + private String[] splitWhitelist(String string) + { + + List<String> strings = new LinkedList<String>(); + string = string.trim(); + + for (String s : string.split(",")) + { + if (s != null && s.trim().length() > 0) + { + strings.add(s.trim()); + } + } + + return strings.toArray(new String[strings.size()]); + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java new file mode 100644 index 000000000..b05cb2579 --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java @@ -0,0 +1,22 @@ +package com.earth2me.essentials.anticheat.checks.chat; + +import com.earth2me.essentials.anticheat.DataItem; + + +/** + * Player specific data for the chat checks + * + */ +public class ChatData implements DataItem +{ + // Keep track of the violation levels for the two checks + public int spamVL; + public int colorVL; + // Count messages and commands + public int messageCount = 0; + public int commandCount = 0; + // Remember when the last check time period started + public long spamLastTime = 0; + // Remember the last chat message or command for logging purposes + public String message = ""; +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java new file mode 100644 index 000000000..2468c7065 --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java @@ -0,0 +1,50 @@ +package com.earth2me.essentials.anticheat.checks.chat; + +import java.util.Locale; +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.actions.ParameterName; +import com.earth2me.essentials.anticheat.data.Statistics.Id; + + +public class ColorCheck extends ChatCheck +{ + public ColorCheck(NoCheat plugin) + { + super(plugin, "chat.color"); + } + + public boolean check(NoCheatPlayer player, ChatData data, ChatConfig cc) + { + + if (data.message.contains("\247")) + { + + data.colorVL += 1; + incrementStatistics(player, Id.CHAT_COLOR, 1); + + boolean filter = executeActions(player, cc.colorActions, data.colorVL); + + if (filter) + { + // Remove color codes + data.message = data.message.replaceAll("\302\247.", "").replaceAll("\247.", ""); + } + } + + return false; + } + + public String getParameter(ParameterName wildcard, NoCheatPlayer player) + { + + if (wildcard == ParameterName.VIOLATIONS) + { + return String.format(Locale.US, "%d", getData(player).colorVL); + } + else + { + return super.getParameter(wildcard, player); + } + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java new file mode 100644 index 000000000..8bf893091 --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java @@ -0,0 +1,96 @@ +package com.earth2me.essentials.anticheat.checks.chat; + +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.actions.ParameterName; +import com.earth2me.essentials.anticheat.data.Statistics.Id; +import java.util.Locale; + + +/** + * The SpamCheck will count messages and commands over a short timeframe to see if the player tried to send too many of + * them + * + */ +public class SpamCheck extends ChatCheck +{ + public SpamCheck(NoCheat plugin) + { + super(plugin, "chat.spam"); + } + + public boolean check(NoCheatPlayer player, ChatData data, ChatConfig cc) + { + + boolean cancel = false; + // Maybe it's a command and on the whitelist + for (String s : cc.spamWhitelist) + { + if (data.message.startsWith(s)) + { + // It is + return false; + } + } + + int commandLimit = cc.spamCommandLimit; + int messageLimit = cc.spamMessageLimit; + long timeframe = cc.spamTimeframe; + + final long time = System.currentTimeMillis(); + + // Has enough time passed? Then reset the counters + if (data.spamLastTime + timeframe <= time) + { + data.spamLastTime = time; + data.messageCount = 0; + data.commandCount = 0; + } + // Security check, if the system time changes + else if (data.spamLastTime > time) + { + data.spamLastTime = Integer.MIN_VALUE; + } + + // Increment appropriate counter + if (data.message.startsWith("/")) + { + data.commandCount++; + } + else + { + data.messageCount++; + } + + // Did the player go over the limit on at least one of the counters? + if (data.messageCount > messageLimit || data.commandCount > commandLimit) + { + + // Set the vl as the number of messages above the limit and + // increment statistics + data.spamVL = Math.max(0, data.messageCount - messageLimit); + data.spamVL += Math.max(0, data.commandCount - commandLimit); + incrementStatistics(player, Id.CHAT_SPAM, 1); + + // Execute whatever actions are associated with this check and the + // violation level and find out if we should cancel the event + cancel = executeActions(player, cc.spamActions, data.spamVL); + } + + return cancel; + } + + @Override + public String getParameter(ParameterName wildcard, NoCheatPlayer player) + { + + if (wildcard == ParameterName.VIOLATIONS) + { + return String.format(Locale.US, "%d", getData(player).spamVL); + } + else + { + return super.getParameter(wildcard, player); + } + } +} |