diff --git a/src/main/java/io/blert/challenges/colosseum/ColosseumChallenge.java b/src/main/java/io/blert/challenges/colosseum/ColosseumChallenge.java index ea25d76..374a0b3 100644 --- a/src/main/java/io/blert/challenges/colosseum/ColosseumChallenge.java +++ b/src/main/java/io/blert/challenges/colosseum/ColosseumChallenge.java @@ -26,6 +26,7 @@ import io.blert.core.*; import io.blert.events.ChallengeEndEvent; import io.blert.events.ChallengeStartEvent; +import io.blert.util.ChatText; import io.blert.util.DeferredTask; import io.blert.util.Location; import io.blert.util.Tick; @@ -36,12 +37,12 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.*; import net.runelite.client.callback.ClientThread; -import net.runelite.client.util.Text; import org.apache.commons.lang3.tuple.Pair; @Slf4j @@ -172,8 +173,9 @@ public void onGameObjectSpawned(GameObjectSpawned event) { @Override public void onChatMessage(ChatMessage event) { - if (!getState().isInactive()) { - Matcher matcher = ColosseumChallenge.COLOSSEUM_END_REGEX.matcher(Text.removeTags(event.getMessage())); + if (!getState().isInactive() && event.getType() == ChatMessageType.GAMEMESSAGE) { + Matcher matcher = + ColosseumChallenge.COLOSSEUM_END_REGEX.matcher(ChatText.stripFormatting(event.getMessage())); if (matcher.find()) { try { reportedChallengeTicks = Tick.fromTimeString(matcher.group(1)) diff --git a/src/main/java/io/blert/challenges/colosseum/WaveDataTracker.java b/src/main/java/io/blert/challenges/colosseum/WaveDataTracker.java index d109634..367386e 100644 --- a/src/main/java/io/blert/challenges/colosseum/WaveDataTracker.java +++ b/src/main/java/io/blert/challenges/colosseum/WaveDataTracker.java @@ -26,6 +26,7 @@ import io.blert.core.*; import io.blert.events.NpcAttackEvent; import io.blert.events.colosseum.*; +import io.blert.util.ChatText; import io.blert.util.Tick; import java.util.*; import java.util.regex.Matcher; @@ -38,7 +39,6 @@ import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.*; -import net.runelite.client.util.Text; import org.apache.commons.lang3.tuple.Pair; @Slf4j @@ -298,7 +298,7 @@ protected void onGraphicsObjectCreation(GraphicsObjectCreated event) { @Override protected void onMessage(ChatMessage event) { - String stripped = Text.removeTags(event.getMessage()); + String stripped = ChatText.stripFormatting(event.getMessage()); if (stripped.equals(waveStartMessage)) { startWave(0); diff --git a/src/main/java/io/blert/challenges/inferno/InfernoChallenge.java b/src/main/java/io/blert/challenges/inferno/InfernoChallenge.java index 48e70d3..b4493a5 100644 --- a/src/main/java/io/blert/challenges/inferno/InfernoChallenge.java +++ b/src/main/java/io/blert/challenges/inferno/InfernoChallenge.java @@ -26,6 +26,7 @@ import io.blert.core.*; import io.blert.events.ChallengeEndEvent; import io.blert.events.ChallengeStartEvent; +import io.blert.util.ChatText; import io.blert.util.DeferredTask; import io.blert.util.Location; import io.blert.util.Tick; @@ -35,6 +36,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.NPC; @@ -43,7 +45,6 @@ import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.NpcSpawned; import net.runelite.client.callback.ClientThread; -import net.runelite.client.util.Text; import org.apache.commons.lang3.tuple.Pair; @Slf4j @@ -161,8 +162,8 @@ public void onGameStateChanged(GameStateChanged event) { @Override public void onChatMessage(ChatMessage event) { - if (!getState().isInactive()) { - String stripped = Text.removeTags(event.getMessage()); + if (!getState().isInactive() && event.getType() == ChatMessageType.GAMEMESSAGE) { + String stripped = ChatText.stripFormatting(event.getMessage()); if (stripped.equals(WAVE_1_START_MESSAGE)) { challengeStartTick = client.getTickCount() - WAVE_1_TIME_OFFSET_TICKS; } else { diff --git a/src/main/java/io/blert/challenges/inferno/WaveDataTracker.java b/src/main/java/io/blert/challenges/inferno/WaveDataTracker.java index 4b4f663..43a4ded 100644 --- a/src/main/java/io/blert/challenges/inferno/WaveDataTracker.java +++ b/src/main/java/io/blert/challenges/inferno/WaveDataTracker.java @@ -26,6 +26,7 @@ import io.blert.core.*; import io.blert.events.NpcAttackEvent; import io.blert.events.inferno.InfernoWaveStartEvent; +import io.blert.util.ChatText; import java.util.Optional; import java.util.regex.Matcher; import javax.annotation.Nullable; @@ -37,7 +38,6 @@ import net.runelite.api.events.ChatMessage; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; -import net.runelite.client.util.Text; @Slf4j public class WaveDataTracker extends DataTracker { @@ -138,7 +138,7 @@ protected void onAnimation(AnimationChanged event) { @Override protected void onMessage(ChatMessage event) { - String stripped = Text.removeTags(event.getMessage()); + String stripped = ChatText.stripFormatting(event.getMessage()); if (stripped.equals(waveStartMessage)) { start(); } else if (stripped.equals(waveEndMessage)) { diff --git a/src/main/java/io/blert/challenges/mokhaiotl/DelveDataTracker.java b/src/main/java/io/blert/challenges/mokhaiotl/DelveDataTracker.java index 5f93408..49b6e22 100644 --- a/src/main/java/io/blert/challenges/mokhaiotl/DelveDataTracker.java +++ b/src/main/java/io/blert/challenges/mokhaiotl/DelveDataTracker.java @@ -27,6 +27,7 @@ import io.blert.core.*; import io.blert.events.NpcAttackEvent; import io.blert.events.mokhaiotl.*; +import io.blert.util.ChatText; import io.blert.util.Location; import io.blert.util.Tick; import java.util.*; @@ -39,7 +40,6 @@ import net.runelite.api.*; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.*; -import net.runelite.client.util.Text; @Slf4j public class DelveDataTracker extends DataTracker { @@ -203,7 +203,7 @@ protected void onTick() { @Override protected void onMessage(ChatMessage event) { - String stripped = Text.removeTags(event.getMessage()); + String stripped = ChatText.stripFormatting(event.getMessage()); Matcher matcher = delveEndRegex.matcher(stripped); if (matcher.find()) { diff --git a/src/main/java/io/blert/challenges/mokhaiotl/MokhaiotlChallenge.java b/src/main/java/io/blert/challenges/mokhaiotl/MokhaiotlChallenge.java index 01c37db..686fe1b 100644 --- a/src/main/java/io/blert/challenges/mokhaiotl/MokhaiotlChallenge.java +++ b/src/main/java/io/blert/challenges/mokhaiotl/MokhaiotlChallenge.java @@ -26,6 +26,7 @@ import io.blert.core.*; import io.blert.events.ChallengeEndEvent; import io.blert.events.ChallengeStartEvent; +import io.blert.util.ChatText; import io.blert.util.Location; import io.blert.util.Tick; import java.util.List; @@ -34,11 +35,11 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ChatMessage; import net.runelite.client.callback.ClientThread; -import net.runelite.client.util.Text; import org.apache.commons.lang3.tuple.Pair; @Slf4j @@ -109,8 +110,9 @@ protected Stage getStage() { @Override public void onChatMessage(ChatMessage event) { - if (!getState().isInactive()) { - Matcher matcher = MokhaiotlChallenge.MOKHAIOTL_END_REGEX.matcher(Text.removeTags(event.getMessage())); + if (!getState().isInactive() && event.getType() == ChatMessageType.GAMEMESSAGE) { + Matcher matcher = + MokhaiotlChallenge.MOKHAIOTL_END_REGEX.matcher(ChatText.stripFormatting(event.getMessage())); if (matcher.find()) { try { reportedChallengeTicks = Tick.fromTimeString(matcher.group(1)) diff --git a/src/main/java/io/blert/challenges/tob/TheatreChallenge.java b/src/main/java/io/blert/challenges/tob/TheatreChallenge.java index c5fd555..2f65994 100644 --- a/src/main/java/io/blert/challenges/tob/TheatreChallenge.java +++ b/src/main/java/io/blert/challenges/tob/TheatreChallenge.java @@ -34,6 +34,7 @@ import io.blert.events.ChallengeEndEvent; import io.blert.events.ChallengeStartEvent; import io.blert.events.StageUpdateEvent; +import io.blert.util.ChatText; import io.blert.util.DeferredTask; import io.blert.util.Tick; import java.util.Arrays; @@ -370,7 +371,7 @@ public void onChatMessage(ChatMessage message) { updateLocation(); if (message.getType() == ChatMessageType.GAMEMESSAGE) { - String stripped = Text.removeTags(message.getMessage()); + String stripped = ChatText.stripFormatting(message.getMessage()); if (getState().isInactive()) { // Listen for a chat message indicating the start of a raid, and queue the start action immediately diff --git a/src/main/java/io/blert/challenges/tob/rooms/RoomDataTracker.java b/src/main/java/io/blert/challenges/tob/rooms/RoomDataTracker.java index 5e802b8..c75d9c4 100644 --- a/src/main/java/io/blert/challenges/tob/rooms/RoomDataTracker.java +++ b/src/main/java/io/blert/challenges/tob/rooms/RoomDataTracker.java @@ -35,6 +35,7 @@ import io.blert.events.Event; import io.blert.events.EventHandler; import io.blert.events.PlayerDeathEvent; +import io.blert.util.ChatText; import io.blert.util.Tick; import java.util.ArrayList; import java.util.List; @@ -195,7 +196,7 @@ protected void onGameState(GameStateChanged event) { @Override protected final void onMessage(ChatMessage chatMessage) { - String stripped = Text.removeTags(chatMessage.getMessage()); + String stripped = ChatText.stripFormatting(chatMessage.getMessage()); Matcher matcher = waveEndRegex.matcher(stripped); if (matcher.find()) { try { diff --git a/src/main/java/io/blert/core/DataTracker.java b/src/main/java/io/blert/core/DataTracker.java index d13fe2e..261688f 100644 --- a/src/main/java/io/blert/core/DataTracker.java +++ b/src/main/java/io/blert/core/DataTracker.java @@ -790,7 +790,7 @@ public final void onProjectileMoved(ProjectileMoved event) { @Override public final void onChatMessage(ChatMessage event) { - if (state == State.IN_PROGRESS || event.getType() == ChatMessageType.GAMEMESSAGE) { + if (event.getType() == ChatMessageType.GAMEMESSAGE) { onMessage(event); } } diff --git a/src/main/java/io/blert/util/ChatText.java b/src/main/java/io/blert/util/ChatText.java new file mode 100644 index 0000000..9bce66e --- /dev/null +++ b/src/main/java/io/blert/util/ChatText.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Alexei Frolov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.blert.util; + +import java.util.regex.Pattern; +import net.runelite.client.util.Text; + +public class ChatText { + /** Jagex text macros are snake_case strings wrapped in @. */ + private static final Pattern MACRO_REGEX = Pattern.compile("@[a-z0-9_]{1,64}@"); + + /** + * Strips all formatting from a game message, leaving only its text content. + * + * @param message The raw message. + * @return The message with tags and macros removed. + */ + public static String stripFormatting(String message) { + return MACRO_REGEX.matcher(Text.removeTags(message)).replaceAll(""); + } +} diff --git a/src/test/java/io/blert/util/ChatTextTest.java b/src/test/java/io/blert/util/ChatTextTest.java new file mode 100644 index 0000000..7da9052 --- /dev/null +++ b/src/test/java/io/blert/util/ChatTextTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026 Alexei Frolov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.blert.util; + +import junit.framework.TestCase; + +public class ChatTextTest extends TestCase { + public void testStripsHtmlStyleTags() { + assertEquals("Wave: 1", ChatText.stripFormatting("Wave: 1")); + assertEquals( + "Wave 'The Maiden of Sugadinti' (Normal Mode) complete!Duration: 1:23.40", + ChatText.stripFormatting( + "Wave 'The Maiden of Sugadinti' (Normal Mode) complete!
Duration: 1:23.40")); + } + + public void testStripsLeadingMacro() { + assertEquals("Delve level: 1", ChatText.stripFormatting("@mes_hl_red@Delve level: 1")); + assertEquals( + "You resurrect a greater ghostly thrall.", + ChatText.stripFormatting("@mes_hl_mag@You resurrect a greater ghostly thrall.")); + } + + public void testStripsInlineMacros() { + assertEquals( + "Delve level: 1 duration: 0:41.40. Personal best: 0:21.00", + ChatText.stripFormatting( + "Delve level: 1 duration: @mes_hl_red@0:41.40. Personal best: @mes_hl_red@0:21.00")); + assertEquals("Total duration: 0:41.40", ChatText.stripFormatting("Total duration: @mes_hl_red@0:41.40")); + assertEquals( + "You still have unclaimed loot! Are you sure you want to teleport away?", + ChatText.stripFormatting( + "You still have @mes_hl_red@unclaimed loot! Are you sure you want to teleport away?")); + } + + public void testStripsSemanticMacros() { + assertEquals( + "You have failed Darkness Is Your Ally?: You equipped a demonbane weapon.", + ChatText.stripFormatting( + "You have failed @ach_comp@Darkness Is Your Ally?: You equipped a demonbane weapon.")); + } + + public void testLeavesUnformattedTextUnchanged() { + assertEquals("Jagex breaks plugins", ChatText.stripFormatting("Jagex breaks plugins")); + } + + public void testLeavesNonMacroAtUnchanged() { + assertEquals("meet @ bank", ChatText.stripFormatting("meet @ bank")); + assertEquals("support@jagex.com", ChatText.stripFormatting("support" + "@jagex.com")); + } +}