diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java b/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java index b3f910af..c2f7a8eb 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java @@ -1,8 +1,10 @@ package fr.neatmonster.nocheatplus.checks.chat; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.bukkit.entity.Player; @@ -71,6 +73,7 @@ public class ChatConfig implements CheckConfig { public final ActionList colorActions; public final boolean globalChatCheck; + public final Set globalChatCommands; public final float globalChatFrequencyFactor; public final float globalChatFrequencyWeight; public final double globalChatLevel; @@ -143,6 +146,13 @@ public class ChatConfig implements CheckConfig { colorActions = data.getActionList(ConfPaths.CHAT_COLOR_ACTIONS, Permissions.CHAT_COLOR); globalChatCheck = data.getBoolean(ConfPaths.CHAT_GLOBALCHAT_CHECK); + final List commands = data.getStringList(ConfPaths.CHAT_GLOBALCHAT_COMMANDS); + globalChatCommands = new HashSet(); + if (commands != null){ + for (String cmd : commands){ + globalChatCommands.add(cmd.trim().toLowerCase()); + } + } globalChatFrequencyFactor = (float) data.getDouble(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_FACTOR); globalChatFrequencyWeight = (float) data.getDouble(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_WEIGHT); globalChatLevel = data.getDouble(ConfPaths.CHAT_GLOBALCHAT_LEVEL); diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java b/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java index 95834722..0eddbda6 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java @@ -63,7 +63,7 @@ public class ChatListener implements Listener { // Then the no pwnage check. if (noPwnage.check(player, event.getMessage(), false)) event.setCancelled(true); - else if (globalChat.check(player, event.getMessage(), (ICaptcha) noPwnage)) + else if (globalChat.check(player, event.getMessage(), (ICaptcha) noPwnage, false)) // Only check those that got through. // (ICaptcha to start captcha if desired.) event.setCancelled(true); @@ -93,10 +93,12 @@ public class ChatListener implements Listener { * |_| */ final Player player = event.getPlayer(); - final String command = event.getMessage().split(" ")[0].substring(1).toLowerCase(); + final String command = event.getMessage().trim().split(" ")[0].substring(1).toLowerCase(); + final ChatConfig cc = ChatConfig.getConfig(player); + // Protect some commands to prevent players for seeing which plugins are installed. - if (ChatConfig.getConfig(player).protectPlugins) + if (cc.protectPlugins) if ((command.equalsIgnoreCase("plugins") || command.equalsIgnoreCase("pl") || command.equalsIgnoreCase("version") || command.equalsIgnoreCase("ver")) && !player.hasPermission(Permissions.ADMINISTRATION_PLUGINS)) { @@ -108,7 +110,7 @@ public class ChatListener implements Listener { } // Prevent /op and /deop commands from being used in chat. - if (ChatConfig.getConfig(player).opInConsoleOnly && (command.equals("op") || command.equals("deop"))) { + if (cc.opInConsoleOnly && (command.equals("op") || command.equals("deop"))) { event.getPlayer().sendMessage( ChatColor.RED + "I'm sorry, but this command can't be executed in chat. Use the console instead!"); event.setCancelled(true); @@ -121,6 +123,8 @@ public class ChatListener implements Listener { // Then the no pwnage check. if (noPwnage.check(player, event.getMessage(), true)) event.setCancelled(true); + else if ((cc.globalChatCommands.contains(command) || cc.globalChatCommands.contains("/"+command)) && globalChat.check(player, event.getMessage(), noPwnage, true)) + event.setCancelled(true); } /** diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java b/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java index 43329d11..3a1444d5 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java @@ -5,7 +5,6 @@ import org.bukkit.entity.Player; import fr.neatmonster.nocheatplus.checks.Check; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager; -import fr.neatmonster.nocheatplus.players.Permissions; /** * Some alternative more or less advanced analysis methods. @@ -28,18 +27,18 @@ public class GlobalChat extends Check{ * Used for starting captcha on failure, if configured so. * @return */ - public boolean check(final Player player, final String message, final ICaptcha captcha) { + public boolean check(final Player player, final String message, final ICaptcha captcha, boolean isMainThread) { final ChatConfig cc = ChatConfig.getConfig(player); - // Checking the player, actually. - if (!cc.isEnabled(type) || NCPExemptionManager.isExempted(player, type)) + if (isMainThread && !isEnabled(player)) return false; + if (!isMainThread && (!cc.isEnabled(type) || NCPExemptionManager.isExempted(player, type))) return false; - + final ChatData data = ChatData.getData(player); synchronized (data) { - return unsafeCheck(player, message, captcha, cc, data); + return unsafeCheck(player, message, captcha, cc, data, isMainThread); } } @@ -50,10 +49,11 @@ public class GlobalChat extends Check{ * @param captcha * @param cc * @param data + * @param isMainThread * @return */ private boolean unsafeCheck(final Player player, final String message, final ICaptcha captcha, - final ChatConfig cc, final ChatData data) { + final ChatConfig cc, final ChatData data, boolean isMainThread) { // Take time once: final long time = System.currentTimeMillis(); @@ -68,6 +68,23 @@ public class GlobalChat extends Check{ // Weight of this chat message. float weight = 1.0f; + + final MessageLetterCount letterCounts = new MessageLetterCount(message); + + final int length = message.length(); + // Upper case. + if (length > 8 && letterCounts.fullCount.upperCase > length / 4){ + weight += 0.6 * letterCounts.fullCount.getUpperCaseRatio(); + } + + // ? for words individually ? + + // Repetition of characters. + if (length > 4){ + final float fullRep = letterCounts.fullCount.getLetterRatio(); + score += (float) length / 15.0 * Math.abs(0.5 - fullRep); // Very small and very big are bad ! + } + // TODO Core checks.... // Add weight to frequency counts. @@ -81,13 +98,14 @@ public class GlobalChat extends Check{ } else{ data.globalChatVL += score / 10.0; - if (executeActionsThreadSafe(player, data.globalChatVL, score, cc.globalChatActions, Permissions.CHAT_GLOBALCHAT)) + if (executeActionsThreadSafe(player, data.globalChatVL, score, cc.globalChatActions, isMainThread)) cancel = true; } } else data.globalChatVL *= 0.95; + return cancel; } diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/MessageLetterCount.java b/src/fr/neatmonster/nocheatplus/checks/chat/MessageLetterCount.java new file mode 100644 index 00000000..78a2bbde --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/checks/chat/MessageLetterCount.java @@ -0,0 +1,96 @@ +package fr.neatmonster.nocheatplus.checks.chat; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Letter count for a message and sub words, including upper case count.
+ * NOTE: this is a pretty heavy implementation for testing purposes. + * @author mc_dev + * + */ +public class MessageLetterCount { + /** + * Letter count for a word. + * @author mc_dev + * + */ + public static final class WordLetterCount{ + public final String word; + public final Map counts; + public final int upperCase; + public WordLetterCount(final String word){ + this.word = word; + char[] a = word.toCharArray(); + // Preserve insertion order. + counts = new LinkedHashMap(a.length); + int upperCase = 0; + for (int i = 0; i < a.length; i++){ + final char c = a[i]; + final Character key; + if (Character.isUpperCase(c)){ + upperCase ++; + key = Character.toLowerCase(c); + } + else key = c; + final Integer count = counts.remove(key); + if (count == null) counts.put(key, 1); + else counts.put(key, count.intValue() + 1); + + } + this.upperCase = upperCase; + } + + public float getLetterRatio(){ + return (float) counts.size() / (float) word.length(); + } + + public float getUpperCaseRatio(){ + return (float) upperCase / (float) word.length(); + } + } + + public final String message; + + public final String split; + + public final WordLetterCount[] words; + + public final WordLetterCount fullCount; + + /** + * Constructor for splitting by a space. + * @param message + */ + public MessageLetterCount(final String message){ + this(message, " "); + } + + /** + * + * @param message + * @param split + */ + public MessageLetterCount(final String message, final String split){ + this.message = message; + this.split = split; + + final String[] parts = message.split(split); + words = new WordLetterCount[parts.length]; + + fullCount = new WordLetterCount(message); + // (Do not store 60 times "a".) + final Map done = new HashMap(words.length); + for (int i = 0; i < parts.length; i++){ + final String word = parts[i]; + if (done.containsKey(word)){ + words[i] = done.get(word); + continue; + } + done.put(word, words[i] = new WordLetterCount(word)); + } + done.clear(); + } + +} diff --git a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java index be424b35..cedd0137 100644 --- a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -141,6 +141,7 @@ public abstract class ConfPaths { public static final String CHAT_GLOBALCHAT = CHAT + "globalchat."; public static final String CHAT_GLOBALCHAT_CHECK = CHAT_GLOBALCHAT + "active"; + public static final String CHAT_GLOBALCHAT_COMMANDS = CHAT_GLOBALCHAT + "commands"; public static final String CHAT_GLOBALCHAT_FREQUENCY = CHAT_GLOBALCHAT + "frequency."; public static final String CHAT_GLOBALCHAT_FREQUENCY_WEIGHT = CHAT_GLOBALCHAT_FREQUENCY + "weight"; public static final String CHAT_GLOBALCHAT_FREQUENCY_FACTOR = CHAT_GLOBALCHAT_FREQUENCY + "factor"; diff --git a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java index e04e6f6a..c623b8e6 100644 --- a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java +++ b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java @@ -1,6 +1,8 @@ package fr.neatmonster.nocheatplus.config; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; /* * M""""""'YMM .8888b dP dP MM'""""'YMM .8888b oo @@ -126,6 +128,8 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.CHAT_COLOR_ACTIONS, "log:color:0:1:if cancel"); set(ConfPaths.CHAT_GLOBALCHAT_CHECK, true); + set(ConfPaths.CHAT_GLOBALCHAT_COMMANDS, new LinkedList(Arrays.asList( + new String[]{"/me"}))); set(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_FACTOR, 0.8D); set(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_WEIGHT, 6.0D); set(ConfPaths.CHAT_GLOBALCHAT_LEVEL, 30D);