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;
|
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.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.regex.PatternSyntaxException;
|
import java.util.regex.PatternSyntaxException;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import net.runelite.api.ChatMessageType;
|
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.Client;
|
||||||
import net.runelite.api.MessageNode;
|
import net.runelite.api.MessageNode;
|
||||||
import net.runelite.api.Player;
|
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.OverheadTextChanged;
|
||||||
import net.runelite.api.events.ScriptCallbackEvent;
|
import net.runelite.api.events.ScriptCallbackEvent;
|
||||||
import net.runelite.client.config.ConfigManager;
|
import net.runelite.client.config.ConfigManager;
|
||||||
import net.runelite.client.eventbus.Subscribe;
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
|
import net.runelite.client.events.ConfigChanged;
|
||||||
import net.runelite.client.game.ClanManager;
|
import net.runelite.client.game.ClanManager;
|
||||||
import net.runelite.client.plugins.Plugin;
|
import net.runelite.client.plugins.Plugin;
|
||||||
import net.runelite.client.plugins.PluginDescriptor;
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
@@ -66,10 +79,38 @@ public class ChatFilterPlugin extends Plugin
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String CENSOR_MESSAGE = "Hey, everyone, I just tried to say something very silly!";
|
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 CharMatcher jagexPrintableCharMatcher = Text.JAGEX_PRINTABLE_CHAR_MATCHER;
|
||||||
private final List<Pattern> filteredPatterns = new ArrayList<>();
|
private final List<Pattern> filteredPatterns = new ArrayList<>();
|
||||||
private final List<Pattern> filteredNamePatterns = 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
|
@Inject
|
||||||
private Client client;
|
private Client client;
|
||||||
|
|
||||||
@@ -96,6 +137,7 @@ public class ChatFilterPlugin extends Plugin
|
|||||||
protected void shutDown() throws Exception
|
protected void shutDown() throws Exception
|
||||||
{
|
{
|
||||||
filteredPatterns.clear();
|
filteredPatterns.clear();
|
||||||
|
duplicateChatCache.clear();
|
||||||
client.refreshChat();
|
client.refreshChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,10 +151,18 @@ public class ChatFilterPlugin extends Plugin
|
|||||||
|
|
||||||
int[] intStack = client.getIntStack();
|
int[] intStack = client.getIntStack();
|
||||||
int intStackSize = client.getIntStackSize();
|
int intStackSize = client.getIntStackSize();
|
||||||
int messageType = intStack[intStackSize - 2];
|
String[] stringStack = client.getStringStack();
|
||||||
int messageId = intStack[intStackSize - 1];
|
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);
|
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
|
// Only filter public chat and private messages
|
||||||
switch (chatMessageType)
|
switch (chatMessageType)
|
||||||
@@ -123,32 +173,34 @@ public class ChatFilterPlugin extends Plugin
|
|||||||
case PRIVATECHAT:
|
case PRIVATECHAT:
|
||||||
case MODPRIVATECHAT:
|
case MODPRIVATECHAT:
|
||||||
case FRIENDSCHAT:
|
case FRIENDSCHAT:
|
||||||
|
if (shouldFilterPlayerMessage(name))
|
||||||
|
{
|
||||||
|
message = censorMessage(name, message);
|
||||||
|
blockMessage = message == null;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case LOGINLOGOUTNOTIFICATION:
|
case LOGINLOGOUTNOTIFICATION:
|
||||||
if (config.filterLogin())
|
if (config.filterLogin())
|
||||||
{
|
{
|
||||||
// Block the message
|
blockMessage = true;
|
||||||
intStack[intStackSize - 3] = 0;
|
|
||||||
}
|
}
|
||||||
return;
|
break;
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageNode messageNode = client.getMessages().get(messageId);
|
boolean shouldCollapse = chatMessageType == PUBLICCHAT || chatMessageType == MODCHAT
|
||||||
String name = messageNode.getName();
|
? config.collapsePlayerChat()
|
||||||
if (!shouldFilterPlayerMessage(name))
|
: 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();
|
if (blockMessage)
|
||||||
int stringStackSize = client.getStringStackSize();
|
|
||||||
|
|
||||||
String message = stringStack[stringStackSize - 1];
|
|
||||||
String censoredMessage = censorMessage(name, message);
|
|
||||||
|
|
||||||
if (censoredMessage == null)
|
|
||||||
{
|
{
|
||||||
// Block the message
|
// Block the message
|
||||||
intStack[intStackSize - 3] = 0;
|
intStack[intStackSize - 3] = 0;
|
||||||
@@ -156,7 +208,12 @@ public class ChatFilterPlugin extends Plugin
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Replace the message
|
// 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);
|
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 shouldFilterPlayerMessage(String playerName)
|
||||||
{
|
{
|
||||||
boolean isMessageFromSelf = playerName.equals(client.getLocalPlayer().getName());
|
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.IterableHashTable;
|
||||||
import net.runelite.api.MessageNode;
|
import net.runelite.api.MessageNode;
|
||||||
import net.runelite.api.Player;
|
import net.runelite.api.Player;
|
||||||
|
import net.runelite.api.events.ChatMessage;
|
||||||
import net.runelite.api.events.ScriptCallbackEvent;
|
import net.runelite.api.events.ScriptCallbackEvent;
|
||||||
import net.runelite.client.game.ClanManager;
|
import net.runelite.client.game.ClanManager;
|
||||||
import static net.runelite.client.plugins.chatfilter.ChatFilterPlugin.CENSOR_MESSAGE;
|
import static net.runelite.client.plugins.chatfilter.ChatFilterPlugin.CENSOR_MESSAGE;
|
||||||
@@ -108,6 +109,13 @@ public class ChatFilterPluginTest
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MessageNode mockMessageNode(int id)
|
||||||
|
{
|
||||||
|
MessageNode node = mock(MessageNode.class);
|
||||||
|
when(node.getId()).thenReturn(id);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCensorWords()
|
public void testCensorWords()
|
||||||
{
|
{
|
||||||
@@ -322,4 +330,42 @@ public class ChatFilterPluginTest
|
|||||||
chatFilterPlugin.onScriptCallbackEvent(event);
|
chatFilterPlugin.onScriptCallbackEvent(event);
|
||||||
assertEquals(CENSOR_MESSAGE, client.getStringStack()[client.getStringStackSize() - 1]);
|
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