diff --git a/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java b/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java
index 8ec5b2a351..1de7d524f1 100644
--- a/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java
+++ b/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java
@@ -172,4 +172,48 @@ public class ChatClient
throw new IOException(ex);
}
}
+
+ public boolean submitPb(String username, String boss, int pb) throws IOException
+ {
+ HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
+ .addPathSegment("chat")
+ .addPathSegment("pb")
+ .addQueryParameter("name", username)
+ .addQueryParameter("boss", boss)
+ .addQueryParameter("pb", Integer.toString(pb))
+ .build();
+
+ Request request = new Request.Builder()
+ .post(RequestBody.create(null, new byte[0]))
+ .url(url)
+ .build();
+
+ try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
+ {
+ return response.isSuccessful();
+ }
+ }
+
+ public int getPb(String username, String boss) throws IOException
+ {
+ HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
+ .addPathSegment("chat")
+ .addPathSegment("pb")
+ .addQueryParameter("name", username)
+ .addQueryParameter("boss", boss)
+ .build();
+
+ Request request = new Request.Builder()
+ .url(url)
+ .build();
+
+ try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
+ {
+ if (!response.isSuccessful())
+ {
+ throw new IOException("Unable to look up personal best!");
+ }
+ return Integer.parseInt(response.body().string());
+ }
+ }
}
diff --git a/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java b/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java
index d1c6d71b7a..5b7b6b6b98 100644
--- a/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java
+++ b/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java
@@ -133,4 +133,26 @@ public class ChatController
{
return chatService.getTask(name);
}
+
+ @PostMapping("/pb")
+ public void submitPb(@RequestParam String name, @RequestParam int pb)
+ {
+ if (pb < 0)
+ {
+ return;
+ }
+
+ chatService.setPb(name, pb);
+ }
+
+ @GetMapping("/pb")
+ public int getPb(@RequestParam String name)
+ {
+ Integer pb = chatService.getPb(name);
+ if (pb == null)
+ {
+ throw new NotFoundException();
+ }
+ return pb;
+ }
}
diff --git a/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java b/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java
index 24b16086f8..78c16b4482 100644
--- a/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java
+++ b/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java
@@ -121,4 +121,22 @@ public class ChatService
jedis.expire(key, (int) EXPIRE.getSeconds());
}
}
+
+ public Integer getPb(String name)
+ {
+ String value;
+ try (Jedis jedis = jedisPool.getResource())
+ {
+ value = jedis.get("pb." + name);
+ }
+ return value == null ? null : Integer.parseInt(value);
+ }
+
+ public void setPb(String name, int pb)
+ {
+ try (Jedis jedis = jedisPool.getResource())
+ {
+ jedis.setex("pb." + name, (int) EXPIRE.getSeconds(), Integer.toString(pb));
+ }
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java
index 2c2c321713..16c40d54b7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java
@@ -88,6 +88,17 @@ public interface ChatCommandsConfig extends Config
@ConfigItem(
position = 5,
+ keyName = "pb",
+ name = "PB Command",
+ description = "Configures whether the personal best command is enabled
!pb"
+ )
+ default boolean pb()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 6,
keyName = "clearShortcuts",
name = "Clear shortcuts",
description = "Enable shortcuts (ctrl+w and backspace) for clearing the chatbox"
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java
index 1245ab1991..4a6034a887 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java
@@ -85,6 +85,8 @@ public class ChatCommandsPlugin extends Plugin
private static final Pattern RAIDS_PATTERN = Pattern.compile("Your completed (.+) count is:
(\\d+).");
private static final Pattern WINTERTODT_PATTERN = Pattern.compile("Your subdued Wintertodt count is: (\\d+).");
private static final Pattern BARROWS_PATTERN = Pattern.compile("Your Barrows chest count is: (\\d+).");
+ private static final Pattern KILL_DURATION_PATTERN = Pattern.compile("Fight duration: [0-9:]+. Personal best: ([0-9:]+).");
+ private static final Pattern NEW_PB_PATTERN = Pattern.compile("Fight duration: ([0-9:]+) \\(new personal best\\).");
private static final String TOTAL_LEVEL_COMMAND_STRING = "!total";
private static final String PRICE_COMMAND_STRING = "!price";
private static final String LEVEL_COMMAND_STRING = "!lvl";
@@ -92,12 +94,14 @@ public class ChatCommandsPlugin extends Plugin
private static final String KILLCOUNT_COMMAND_STRING = "!kc";
private static final String CMB_COMMAND_STRING = "!cmb";
private static final String QP_COMMAND_STRING = "!qp";
+ private static final String PB_COMMAND = "!pb";
private final HiscoreClient hiscoreClient = new HiscoreClient();
private final ChatClient chatClient = new ChatClient();
private boolean logKills;
private HiscoreEndpoint hiscoreEndpoint; // hiscore endpoint for current player
+ private String lastBossKill;
@Inject
private Client client;
@@ -138,11 +142,14 @@ public class ChatCommandsPlugin extends Plugin
chatCommandManager.registerCommandAsync(CLUES_COMMAND_STRING, this::clueLookup);
chatCommandManager.registerCommandAsync(KILLCOUNT_COMMAND_STRING, this::killCountLookup, this::killCountSubmit);
chatCommandManager.registerCommandAsync(QP_COMMAND_STRING, this::questPointsLookup, this::questPointsSubmit);
+ chatCommandManager.registerCommandAsync(PB_COMMAND, this::personalBestLookup, this::personalBestSubmit);
}
@Override
public void shutDown()
{
+ lastBossKill = null;
+
keyManager.unregisterKeyListener(chatKeyboardListener);
chatCommandManager.unregisterCommand(TOTAL_LEVEL_COMMAND_STRING);
@@ -152,6 +159,7 @@ public class ChatCommandsPlugin extends Plugin
chatCommandManager.unregisterCommand(CLUES_COMMAND_STRING);
chatCommandManager.unregisterCommand(KILLCOUNT_COMMAND_STRING);
chatCommandManager.unregisterCommand(QP_COMMAND_STRING);
+ chatCommandManager.unregisterCommand(PB_COMMAND);
}
@Provides
@@ -173,6 +181,19 @@ public class ChatCommandsPlugin extends Plugin
return killCount == null ? 0 : killCount;
}
+ private void setPb(String boss, int seconds)
+ {
+ configManager.setConfiguration("personalbest." + client.getUsername().toLowerCase(),
+ boss.toLowerCase(), seconds);
+ }
+
+ private int getPb(String boss)
+ {
+ Integer personalBest = configManager.getConfiguration("personalbest." + client.getUsername().toLowerCase(),
+ boss.toLowerCase(), int.class);
+ return personalBest == null ? 0 : personalBest;
+ }
+
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
@@ -189,6 +210,8 @@ public class ChatCommandsPlugin extends Plugin
int kc = Integer.parseInt(matcher.group(2));
setKc(boss, kc);
+ lastBossKill = boss;
+ return;
}
matcher = WINTERTODT_PATTERN.matcher(message);
@@ -206,6 +229,8 @@ public class ChatCommandsPlugin extends Plugin
int kc = Integer.parseInt(matcher.group(2));
setKc(boss, kc);
+ lastBossKill = boss;
+ return;
}
matcher = BARROWS_PATTERN.matcher(message);
@@ -215,6 +240,35 @@ public class ChatCommandsPlugin extends Plugin
setKc("Barrows Chests", kc);
}
+
+ if (lastBossKill != null)
+ {
+ matcher = KILL_DURATION_PATTERN.matcher(message);
+ if (matcher.find())
+ {
+ matchPb(matcher);
+ }
+
+ matcher = NEW_PB_PATTERN.matcher(message);
+ if (matcher.find())
+ {
+ matchPb(matcher);
+ }
+ }
+
+ lastBossKill = null;
+ }
+
+ private void matchPb(Matcher matcher)
+ {
+ String personalBest = matcher.group(1);
+ String[] s = personalBest.split(":");
+ if (s.length == 2)
+ {
+ int seconds = Integer.parseInt(s[0]) * 60 + Integer.parseInt(s[1]);
+ log.debug("Got personal best for {}: {}", lastBossKill, seconds);
+ setPb(lastBossKill, seconds);
+ }
}
@Subscribe
@@ -411,7 +465,91 @@ public class ChatCommandsPlugin extends Plugin
}
catch (Exception ex)
{
- log.warn("unable to submit quest poinits", ex);
+ log.warn("unable to submit quest points", ex);
+ }
+ finally
+ {
+ chatInput.resume();
+ }
+ });
+
+ return true;
+ }
+
+ private void personalBestLookup(SetMessage setMessage, String message)
+ {
+ if (!config.pb())
+ {
+ return;
+ }
+
+ ChatMessageType type = setMessage.getType();
+ String search = message.substring(PB_COMMAND.length() + 1);
+
+ final String player;
+ if (type.equals(ChatMessageType.PRIVATE_MESSAGE_SENT))
+ {
+ player = client.getLocalPlayer().getName();
+ }
+ else
+ {
+ player = sanitize(setMessage.getName());
+ }
+
+ search = longBossName(search);
+
+ final int pb;
+ try
+ {
+ pb = chatClient.getPb(player, search);
+ }
+ catch (IOException ex)
+ {
+ log.debug("unable to lookup personal best", ex);
+ return;
+ }
+
+ int minutes = pb / 60;
+ int seconds = pb % 60;
+
+ String response = new ChatMessageBuilder()
+ .append(ChatColorType.HIGHLIGHT)
+ .append(search)
+ .append(ChatColorType.NORMAL)
+ .append(" personal best: ")
+ .append(ChatColorType.HIGHLIGHT)
+ .append(String.format("%d:%02d", minutes, seconds))
+ .build();
+
+ log.debug("Setting response {}", response);
+ final MessageNode messageNode = setMessage.getMessageNode();
+ messageNode.setRuneLiteFormatMessage(response);
+ chatMessageManager.update(messageNode);
+ client.refreshChat();
+ }
+
+ private boolean personalBestSubmit(ChatInput chatInput, String value)
+ {
+ int idx = value.indexOf(' ');
+ final String boss = longBossName(value.substring(idx + 1));
+
+ final int pb = getPb(boss);
+ if (pb <= 0)
+ {
+ return false;
+ }
+
+ final String playerName = client.getLocalPlayer().getName();
+
+ executor.execute(() ->
+ {
+ try
+ {
+ chatClient.submitPb(playerName, boss, pb);
+ }
+ catch (Exception ex)
+ {
+ log.warn("unable to submit personal best", ex);
}
finally
{
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java
index 858f72ac8d..a553b73f89 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java
@@ -37,6 +37,7 @@ import net.runelite.client.config.ConfigManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import static org.mockito.Matchers.eq;
import org.mockito.Mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -128,4 +129,38 @@ public class ChatCommandsPluginTest
verify(configManager).setConfiguration("killcount.adam", "barrows chests", 277);
}
+
+ @Test
+ public void testPersonalBest()
+ {
+ final String FIGHT_DURATION = "Fight duration: 2:06. Personal best: 1:19.";
+
+ when(client.getUsername()).thenReturn("Adam");
+
+ // This sets lastBoss
+ ChatMessage chatMessage = new ChatMessage(SERVER, "", "Your Kree'arra kill count is: 4.", null);
+ chatCommandsPlugin.onChatMessage(chatMessage);
+
+ chatMessage = new ChatMessage(SERVER, "", FIGHT_DURATION, null);
+ chatCommandsPlugin.onChatMessage(chatMessage);
+
+ verify(configManager).setConfiguration(eq("personalbest.adam"), eq("kree'arra"), eq(79));
+ }
+
+ @Test
+ public void testNewPersonalBest()
+ {
+ final String NEW_PB = "Fight duration: 3:01 (new personal best).";
+
+ when(client.getUsername()).thenReturn("Adam");
+
+ // This sets lastBoss
+ ChatMessage chatMessage = new ChatMessage(SERVER, "", "Your Kree'arra kill count is: 4.", null);
+ chatCommandsPlugin.onChatMessage(chatMessage);
+
+ chatMessage = new ChatMessage(SERVER, "", NEW_PB, null);
+ chatCommandsPlugin.onChatMessage(chatMessage);
+
+ verify(configManager).setConfiguration(eq("personalbest.adam"), eq("kree'arra"), eq(181));
+ }
}
\ No newline at end of file