diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index 778b271036..05c057a38d 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -45,6 +45,24 @@ public final class ScriptID */ public static final int CHATBOX_INPUT = 96; + /** + * Opens the Private Message chat interface + * + * Jagex refers to this script as {@code meslayer_mode6} + * + */ + public static final int OPEN_PRIVATE_MESSAGE_INTERFACE = 107; + + /** + * Rebuilds the text input widget inside the chat interface + * + */ + public static final int CHAT_TEXT_INPUT_REBUILD = 222; + /** * Layouts the bank widgets * diff --git a/runelite-api/src/main/java/net/runelite/api/VarClientStr.java b/runelite-api/src/main/java/net/runelite/api/VarClientStr.java index b3bf1b09b6..72f4ee0303 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarClientStr.java +++ b/runelite-api/src/main/java/net/runelite/api/VarClientStr.java @@ -36,6 +36,7 @@ public enum VarClientStr { CHATBOX_TYPED_TEXT(1), INPUT_TEXT(22), + PRIVATE_MESSAGE_TARGET(23), RECENT_CLAN_CHAT(129); private final int index; diff --git a/runelite-api/src/main/java/net/runelite/api/vars/InputType.java b/runelite-api/src/main/java/net/runelite/api/vars/InputType.java index db1301b281..c94ccd79ef 100644 --- a/runelite-api/src/main/java/net/runelite/api/vars/InputType.java +++ b/runelite-api/src/main/java/net/runelite/api/vars/InputType.java @@ -38,6 +38,7 @@ public enum InputType RUNELITE_CHATBOX_PANEL(-3), RUNELITE(-2), NONE(0), + PRIVATE_MESSAGE(6), SEARCH(11); private final int type; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java index 6ac89bf605..77fcb70df0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java @@ -25,39 +25,53 @@ package net.runelite.client.plugins.chathistory; import com.google.common.collect.EvictingQueue; -import com.google.common.collect.Sets; +import java.awt.event.KeyEvent; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; import java.util.Queue; -import java.util.Set; import javax.inject.Inject; import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.ScriptID; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarClientStr; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.vars.InputType; +import net.runelite.client.callback.ClientThread; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.QueuedMessage; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.Text; @PluginDescriptor( name = "Chat History", - description = "Retain your chat history when logging in/out or world hopping" + description = "Retain your chat history when logging in/out or world hopping", + tags = {"chat", "history", "retain", "cycle", "pm"} ) -public class ChatHistoryPlugin extends Plugin +public class ChatHistoryPlugin extends Plugin implements KeyListener { private static final String WELCOME_MESSAGE = "Welcome to Old School RuneScape."; private static final String CLEAR_HISTORY = "Clear history"; private static final String CLEAR_PRIVATE = "Private:"; - private static final Set ALLOWED_HISTORY = Sets.newHashSet( - ChatMessageType.PUBLIC, - ChatMessageType.PUBLIC_MOD, - ChatMessageType.CLANCHAT, - ChatMessageType.PRIVATE_MESSAGE_RECEIVED, - ChatMessageType.PRIVATE_MESSAGE_SENT, - ChatMessageType.PRIVATE_MESSAGE_RECEIVED_MOD, - ChatMessageType.GAME - ); + private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB; private Queue messageQueue; + private Deque friends; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private KeyManager keyManager; @Inject private ChatMessageManager chatMessageManager; @@ -66,6 +80,8 @@ public class ChatHistoryPlugin extends Plugin protected void startUp() { messageQueue = EvictingQueue.create(100); + friends = new ArrayDeque<>(5); + keyManager.registerKeyListener(this); } @Override @@ -73,6 +89,9 @@ public class ChatHistoryPlugin extends Plugin { messageQueue.clear(); messageQueue = null; + friends.clear(); + friends = null; + keyManager.unregisterKeyListener(this); } @Subscribe @@ -92,21 +111,33 @@ public class ChatHistoryPlugin extends Plugin return; } - if (ALLOWED_HISTORY.contains(chatMessage.getType())) + switch (chatMessage.getType()) { - final QueuedMessage queuedMessage = QueuedMessage.builder() - .type(chatMessage.getType()) - .name(chatMessage.getName()) - .sender(chatMessage.getSender()) - .value(nbsp(chatMessage.getMessage())) - .runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage())) - .timestamp(chatMessage.getTimestamp()) - .build(); + case PRIVATE_MESSAGE_SENT: + case PRIVATE_MESSAGE_RECEIVED: + case PRIVATE_MESSAGE_RECEIVED_MOD: + final String name = Text.removeTags(chatMessage.getName()); + // Remove to ensure uniqueness & its place in history + friends.remove(name); + friends.add(name); + // intentional fall-through + case PUBLIC: + case PUBLIC_MOD: + case CLANCHAT: + case GAME: + final QueuedMessage queuedMessage = QueuedMessage.builder() + .type(chatMessage.getType()) + .name(chatMessage.getName()) + .sender(chatMessage.getSender()) + .value(nbsp(chatMessage.getMessage())) + .runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage())) + .timestamp(chatMessage.getTimestamp()) + .build(); - if (!messageQueue.contains(queuedMessage)) - { - messageQueue.offer(queuedMessage); - } + if (!messageQueue.contains(queuedMessage)) + { + messageQueue.offer(queuedMessage); + } } } @@ -143,4 +174,64 @@ public class ChatHistoryPlugin extends Plugin return null; } + + @Override + public void keyPressed(KeyEvent e) + { + if (e.getKeyCode() != CYCLE_HOTKEY) + { + return; + } + + if (client.getVar(VarClientInt.INPUT_TYPE) != InputType.PRIVATE_MESSAGE.getType()) + { + return; + } + + clientThread.invoke(() -> + { + final String target = findPreviousFriend(); + if (target == null) + { + return; + } + + final String currentMessage = client.getVar(VarClientStr.INPUT_TEXT); + + client.runScript(ScriptID.OPEN_PRIVATE_MESSAGE_INTERFACE, target); + + client.setVar(VarClientStr.INPUT_TEXT, currentMessage); + client.runScript(ScriptID.CHAT_TEXT_INPUT_REBUILD, ""); + }); + } + + @Override + public void keyTyped(KeyEvent e) + { + } + + @Override + public void keyReleased(KeyEvent e) + { + } + + private String findPreviousFriend() + { + final String currentTarget = client.getVar(VarClientStr.PRIVATE_MESSAGE_TARGET); + if (currentTarget == null || friends.isEmpty()) + { + return null; + } + + for (Iterator it = friends.descendingIterator(); it.hasNext(); ) + { + String friend = it.next(); + if (friend.equals(currentTarget)) + { + return it.hasNext() ? it.next() : friends.getLast(); + } + } + + return friends.getLast(); + } }