chatfilter: add collapse duplicate chat option
Co-authored-by: Adam <Adam@sigterm.info>
This commit is contained in:
@@ -108,4 +108,26 @@ public interface ChatFilterConfig extends Config
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "collapseGameChat",
|
||||
name = "Collapse Game Chat",
|
||||
description = "Collapse duplicate game chat messages into a single line",
|
||||
position = 9
|
||||
)
|
||||
default boolean collapseGameChat()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "collapsePlayerChat",
|
||||
name = "Collapse Player Chat",
|
||||
description = "Collapse duplicate player chat messages into a single line",
|
||||
position = 10
|
||||
)
|
||||
default boolean collapsePlayerChat()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,23 +28,36 @@ package net.runelite.client.plugins.chatfilter;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import static net.runelite.api.ChatMessageType.ENGINE;
|
||||
import static net.runelite.api.ChatMessageType.GAMEMESSAGE;
|
||||
import static net.runelite.api.ChatMessageType.ITEM_EXAMINE;
|
||||
import static net.runelite.api.ChatMessageType.MODCHAT;
|
||||
import static net.runelite.api.ChatMessageType.NPC_EXAMINE;
|
||||
import static net.runelite.api.ChatMessageType.OBJECT_EXAMINE;
|
||||
import static net.runelite.api.ChatMessageType.PUBLICCHAT;
|
||||
import static net.runelite.api.ChatMessageType.SPAM;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.MessageNode;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.OverheadTextChanged;
|
||||
import net.runelite.api.events.ScriptCallbackEvent;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.game.ClanManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
@@ -66,10 +79,38 @@ public class ChatFilterPlugin extends Plugin
|
||||
@VisibleForTesting
|
||||
static final String CENSOR_MESSAGE = "Hey, everyone, I just tried to say something very silly!";
|
||||
|
||||
private static final Set<ChatMessageType> COLLAPSIBLE_MESSAGETYPES = ImmutableSet.of(
|
||||
ENGINE,
|
||||
GAMEMESSAGE,
|
||||
ITEM_EXAMINE,
|
||||
NPC_EXAMINE,
|
||||
OBJECT_EXAMINE,
|
||||
SPAM,
|
||||
PUBLICCHAT,
|
||||
MODCHAT
|
||||
);
|
||||
|
||||
private final CharMatcher jagexPrintableCharMatcher = Text.JAGEX_PRINTABLE_CHAR_MATCHER;
|
||||
private final List<Pattern> filteredPatterns = new ArrayList<>();
|
||||
private final List<Pattern> filteredNamePatterns = new ArrayList<>();
|
||||
|
||||
private static class Duplicate
|
||||
{
|
||||
int messageId;
|
||||
int count;
|
||||
}
|
||||
|
||||
private final LinkedHashMap<String, Duplicate> duplicateChatCache = new LinkedHashMap<String, Duplicate>()
|
||||
{
|
||||
private static final int MAX_ENTRIES = 100;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<String, Duplicate> eldest)
|
||||
{
|
||||
return size() > MAX_ENTRIES;
|
||||
}
|
||||
};
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@@ -96,6 +137,7 @@ public class ChatFilterPlugin extends Plugin
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
filteredPatterns.clear();
|
||||
duplicateChatCache.clear();
|
||||
client.refreshChat();
|
||||
}
|
||||
|
||||
@@ -109,10 +151,18 @@ public class ChatFilterPlugin extends Plugin
|
||||
|
||||
int[] intStack = client.getIntStack();
|
||||
int intStackSize = client.getIntStackSize();
|
||||
int messageType = intStack[intStackSize - 2];
|
||||
int messageId = intStack[intStackSize - 1];
|
||||
String[] stringStack = client.getStringStack();
|
||||
int stringStackSize = client.getStringStackSize();
|
||||
|
||||
final int messageType = intStack[intStackSize - 2];
|
||||
final int messageId = intStack[intStackSize - 1];
|
||||
String message = stringStack[stringStackSize - 1];
|
||||
|
||||
ChatMessageType chatMessageType = ChatMessageType.of(messageType);
|
||||
final MessageNode messageNode = client.getMessages().get(messageId);
|
||||
final String name = messageNode.getName();
|
||||
int duplicateCount = 0;
|
||||
boolean blockMessage = false;
|
||||
|
||||
// Only filter public chat and private messages
|
||||
switch (chatMessageType)
|
||||
@@ -123,32 +173,34 @@ public class ChatFilterPlugin extends Plugin
|
||||
case PRIVATECHAT:
|
||||
case MODPRIVATECHAT:
|
||||
case FRIENDSCHAT:
|
||||
if (shouldFilterPlayerMessage(name))
|
||||
{
|
||||
message = censorMessage(name, message);
|
||||
blockMessage = message == null;
|
||||
}
|
||||
break;
|
||||
case LOGINLOGOUTNOTIFICATION:
|
||||
if (config.filterLogin())
|
||||
{
|
||||
// Block the message
|
||||
intStack[intStackSize - 3] = 0;
|
||||
blockMessage = true;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
MessageNode messageNode = client.getMessages().get(messageId);
|
||||
String name = messageNode.getName();
|
||||
if (!shouldFilterPlayerMessage(name))
|
||||
boolean shouldCollapse = chatMessageType == PUBLICCHAT || chatMessageType == MODCHAT
|
||||
? config.collapsePlayerChat()
|
||||
: COLLAPSIBLE_MESSAGETYPES.contains(chatMessageType) && config.collapseGameChat();
|
||||
if (!blockMessage && shouldCollapse)
|
||||
{
|
||||
return;
|
||||
Duplicate duplicateCacheEntry = duplicateChatCache.get(name + ":" + message);
|
||||
if (duplicateCacheEntry != null)
|
||||
{
|
||||
blockMessage = duplicateCacheEntry.messageId != messageId;
|
||||
duplicateCount = duplicateCacheEntry.count;
|
||||
}
|
||||
}
|
||||
|
||||
String[] stringStack = client.getStringStack();
|
||||
int stringStackSize = client.getStringStackSize();
|
||||
|
||||
String message = stringStack[stringStackSize - 1];
|
||||
String censoredMessage = censorMessage(name, message);
|
||||
|
||||
if (censoredMessage == null)
|
||||
if (blockMessage)
|
||||
{
|
||||
// Block the message
|
||||
intStack[intStackSize - 3] = 0;
|
||||
@@ -156,7 +208,12 @@ public class ChatFilterPlugin extends Plugin
|
||||
else
|
||||
{
|
||||
// Replace the message
|
||||
stringStack[stringStackSize - 1] = censoredMessage;
|
||||
if (duplicateCount > 1)
|
||||
{
|
||||
message += " (" + duplicateCount + ")";
|
||||
}
|
||||
|
||||
stringStack[stringStackSize - 1] = message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +235,25 @@ public class ChatFilterPlugin extends Plugin
|
||||
event.getActor().setOverheadText(message);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage chatMessage)
|
||||
{
|
||||
if (COLLAPSIBLE_MESSAGETYPES.contains(chatMessage.getType()))
|
||||
{
|
||||
// remove and re-insert into map to move to end of list
|
||||
final String key = chatMessage.getName() + ":" + chatMessage.getMessage();
|
||||
Duplicate duplicate = duplicateChatCache.remove(key);
|
||||
if (duplicate == null)
|
||||
{
|
||||
duplicate = new Duplicate();
|
||||
}
|
||||
|
||||
duplicate.count++;
|
||||
duplicate.messageId = chatMessage.getMessageNode().getId();
|
||||
duplicateChatCache.put(key, duplicate);
|
||||
}
|
||||
}
|
||||
|
||||
boolean shouldFilterPlayerMessage(String playerName)
|
||||
{
|
||||
boolean isMessageFromSelf = playerName.equals(client.getLocalPlayer().getName());
|
||||
|
||||
@@ -34,6 +34,7 @@ import net.runelite.api.Client;
|
||||
import net.runelite.api.IterableHashTable;
|
||||
import net.runelite.api.MessageNode;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.ScriptCallbackEvent;
|
||||
import net.runelite.client.game.ClanManager;
|
||||
import static net.runelite.client.plugins.chatfilter.ChatFilterPlugin.CENSOR_MESSAGE;
|
||||
@@ -108,6 +109,13 @@ public class ChatFilterPluginTest
|
||||
return node;
|
||||
}
|
||||
|
||||
private MessageNode mockMessageNode(int id)
|
||||
{
|
||||
MessageNode node = mock(MessageNode.class);
|
||||
when(node.getId()).thenReturn(id);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCensorWords()
|
||||
{
|
||||
@@ -322,4 +330,42 @@ public class ChatFilterPluginTest
|
||||
chatFilterPlugin.onScriptCallbackEvent(event);
|
||||
assertEquals(CENSOR_MESSAGE, client.getStringStack()[client.getStringStackSize() - 1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateChatFiltered()
|
||||
{
|
||||
when(chatFilterConfig.collapseGameChat()).thenReturn(true);
|
||||
chatFilterPlugin.onChatMessage(new ChatMessage(mockMessageNode(0), ChatMessageType.GAMEMESSAGE, null, "testMessage", null, 0));
|
||||
ScriptCallbackEvent event = createCallbackEvent(null, "testMessage", ChatMessageType.GAMEMESSAGE);
|
||||
chatFilterPlugin.onScriptCallbackEvent(event);
|
||||
|
||||
assertEquals(0, client.getIntStack()[client.getIntStackSize() - 3]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDuplicate()
|
||||
{
|
||||
when(chatFilterConfig.collapseGameChat()).thenReturn(true);
|
||||
chatFilterPlugin.onChatMessage(new ChatMessage(mockMessageNode(1), ChatMessageType.GAMEMESSAGE, null, "testMessage", null, 0));
|
||||
ScriptCallbackEvent event = createCallbackEvent(null, "testMessage", ChatMessageType.GAMEMESSAGE);
|
||||
chatFilterPlugin.onScriptCallbackEvent(event);
|
||||
|
||||
assertEquals(1, client.getIntStack()[client.getIntStackSize() - 3]);
|
||||
assertEquals("testMessage", client.getStringStack()[client.getStringStackSize() - 1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateChatCount()
|
||||
{
|
||||
when(chatFilterConfig.collapseGameChat()).thenReturn(true);
|
||||
chatFilterPlugin.onChatMessage(new ChatMessage(mockMessageNode(4), ChatMessageType.GAMEMESSAGE, null, "testMessage", null, 0));
|
||||
chatFilterPlugin.onChatMessage(new ChatMessage(mockMessageNode(3), ChatMessageType.GAMEMESSAGE, null, "testMessage", null, 0));
|
||||
chatFilterPlugin.onChatMessage(new ChatMessage(mockMessageNode(2), ChatMessageType.GAMEMESSAGE, null, "testMessage", null, 0));
|
||||
chatFilterPlugin.onChatMessage(new ChatMessage(mockMessageNode(1), ChatMessageType.GAMEMESSAGE, null, "testMessage", null, 0));
|
||||
ScriptCallbackEvent event = createCallbackEvent(null, "testMessage", ChatMessageType.GAMEMESSAGE);
|
||||
chatFilterPlugin.onScriptCallbackEvent(event);
|
||||
|
||||
assertEquals(1, client.getIntStack()[client.getIntStackSize() - 3]);
|
||||
assertEquals("testMessage (4)", client.getStringStack()[client.getStringStackSize() - 1]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user