Repository: pajlads/DinkPlugin
Pull request #347 (2023-10-26 09:00:31)

feat: include owned pets in login notifications
Relies upon base RL chat commands plugin



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e7373c..6643265 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## Unreleased
 
+- Minor: Include owned pets in login notification metadata. (#347)
 - Minor: Include individual skill XP in login notification metadata. (#345)
 - Bugfix: Improve handling of queued notifications upon concurrent config changes. (#355)
 
diff --git a/README.md b/README.md
index 3a00c6f..4eb8804 100644
--- a/README.md
+++ b/README.md
@@ -859,13 +859,25 @@ On login, Dink can submit a character summary containing data that spans multipl
     "slayer": {
       "points": 2204,
       "streak": 1074
-    }
+    },
+    "pets": [
+      {
+        "itemId": 11995,
+        "name": "Pet chaos elemental"
+      },
+      {
+        "itemId": 13071,
+        "name": "Chompy chick"
+      }
+    ]
   }
 }
 ```
 
 Note: `clanName` requires `Advanced > Send Clan Name` to be enabled (default: on). The `groupIronClanName` and `discordUser` fields also have similar toggles in the Advanced config section.
 
+Note: `extra.pets` requires the base Chat Commands plugin to be enabled.
+
 </details>
 
 ## Credits
diff --git a/src/main/java/dinkplugin/notifiers/MetaNotifier.java b/src/main/java/dinkplugin/notifiers/MetaNotifier.java
index 9b435db..4269fb6 100644
--- a/src/main/java/dinkplugin/notifiers/MetaNotifier.java
+++ b/src/main/java/dinkplugin/notifiers/MetaNotifier.java
@@ -1,5 +1,7 @@
 package dinkplugin.notifiers;
 
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
 import dinkplugin.domain.AchievementDiary;
 import dinkplugin.message.NotificationBody;
 import dinkplugin.message.NotificationType;
@@ -7,7 +9,9 @@ import dinkplugin.message.templating.Replacements;
 import dinkplugin.message.templating.Template;
 import dinkplugin.notifiers.data.LoginNotificationData;
 import dinkplugin.notifiers.data.Progress;
+import dinkplugin.util.SerializedPet;
 import dinkplugin.util.Utils;
+import lombok.extern.slf4j.Slf4j;
 import net.runelite.api.Experience;
 import net.runelite.api.GameState;
 import net.runelite.api.Skill;
@@ -15,18 +19,26 @@ import net.runelite.api.VarPlayer;
 import net.runelite.api.Varbits;
 import net.runelite.api.events.GameStateChanged;
 import net.runelite.client.callback.ClientThread;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.config.RuneLiteConfig;
+import net.runelite.client.plugins.chatcommands.ChatCommandsPlugin;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.VisibleForTesting;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+@Slf4j
 @Singleton
 public class MetaNotifier extends BaseNotifier {
+    static final @VisibleForTesting String RL_CHAT_CMD_PLUGIN_NAME = ChatCommandsPlugin.class.getSimpleName().toLowerCase();
     static final @VisibleForTesting int INIT_TICKS = 10; // 6 seconds after login
 
     private final AtomicInteger loginTicks = new AtomicInteger(-1);
@@ -35,6 +47,12 @@ public class MetaNotifier extends BaseNotifier {
     @Inject
     private ClientThread clientThread;
 
+    @Inject
+    private ConfigManager configManager;
+
+    @Inject
+    private Gson gson;
+
     @Override
     public boolean isEnabled() {
         return StringUtils.isNotBlank(config.metadataWebhook()) && super.isEnabled();
@@ -123,7 +141,8 @@ public class MetaNotifier extends BaseNotifier {
             new LoginNotificationData.SkillData(experienceTotal, levelTotal, skillLevels, skillExperience),
             Progress.of(questsCompleted, questsTotal),
             Progress.of(questPoints, questPointsTotal),
-            new LoginNotificationData.SlayerData(slayerPoints, slayerStreak)
+            new LoginNotificationData.SlayerData(slayerPoints, slayerStreak),
+            getPets()
         );
         createMessage(false, NotificationBody.builder()
             .type(NotificationType.LOGIN)
@@ -134,4 +153,28 @@ public class MetaNotifier extends BaseNotifier {
         );
     }
 
+    @VisibleForTesting
+    List<SerializedPet> getPets() {
+        if ("false".equals(configManager.getConfiguration(RuneLiteConfig.GROUP_NAME, RL_CHAT_CMD_PLUGIN_NAME)))
+            return null;
+
+        String json = configManager.getRSProfileConfiguration("chatcommands", "pets2");
+        if (json == null || json.isEmpty())
+            return null;
+
+        int[] petItemIds;
+        try {
+            petItemIds = gson.fromJson(json, int[].class);
+        } catch (JsonSyntaxException e) {
+            log.info("Failed to deserialize owned pet IDs", e);
+            return null;
+        }
+
+        List<SerializedPet> pets = new ArrayList<>(petItemIds.length);
+        for (int itemId : petItemIds) {
+            pets.add(new SerializedPet(itemId, client.getItemDefinition(itemId).getMembersName()));
+        }
+        return pets;
+    }
+
 }
diff --git a/src/main/java/dinkplugin/notifiers/data/LoginNotificationData.java b/src/main/java/dinkplugin/notifiers/data/LoginNotificationData.java
index b0f859f..9fcd417 100644
--- a/src/main/java/dinkplugin/notifiers/data/LoginNotificationData.java
+++ b/src/main/java/dinkplugin/notifiers/data/LoginNotificationData.java
@@ -1,9 +1,11 @@
 package dinkplugin.notifiers.data;
 
+import dinkplugin.util.SerializedPet;
 import lombok.EqualsAndHashCode;
 import lombok.Value;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.List;
 import java.util.Map;
 
 @Value
@@ -21,6 +23,8 @@ public class LoginNotificationData extends NotificationData {
     Progress questCount;
     Progress questPoints;
     SlayerData slayer;
+    @Nullable // requires Chat Commands plugin to be enabled
+    List<SerializedPet> pets;
 
     @Value
     public static class SkillData {
diff --git a/src/main/java/dinkplugin/util/SerializedPet.java b/src/main/java/dinkplugin/util/SerializedPet.java
new file mode 100644
index 0000000..2cac4bf
--- /dev/null
+++ b/src/main/java/dinkplugin/util/SerializedPet.java
@@ -0,0 +1,9 @@
+package dinkplugin.util;
+
+import lombok.Value;
+
+@Value
+public class SerializedPet {
+    int itemId;
+    String name;
+}


Test patch:
diff --git a/src/test/java/dinkplugin/notifiers/MetaNotifierTest.java b/src/test/java/dinkplugin/notifiers/MetaNotifierTest.java
index 5938d9d..23c186b 100644
--- a/src/test/java/dinkplugin/notifiers/MetaNotifierTest.java
+++ b/src/test/java/dinkplugin/notifiers/MetaNotifierTest.java
@@ -5,18 +5,23 @@ import dinkplugin.message.NotificationBody;
 import dinkplugin.message.NotificationType;
 import dinkplugin.notifiers.data.LoginNotificationData;
 import dinkplugin.notifiers.data.Progress;
+import dinkplugin.util.SerializedPet;
 import net.runelite.api.Experience;
 import net.runelite.api.GameState;
+import net.runelite.api.ItemID;
 import net.runelite.api.Skill;
 import net.runelite.api.VarPlayer;
 import net.runelite.api.Varbits;
 import net.runelite.api.events.GameStateChanged;
+import net.runelite.client.config.RuneLiteConfig;
 import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -100,7 +105,8 @@ class MetaNotifierTest extends MockedNotifierTest {
             new LoginNotificationData.BarbarianAssault(666),
             new LoginNotificationData.SkillData(xp * skillCount, level * skillCount, levels, exp),
             Progress.of(21, 158), Progress.of(43, 300),
-            new LoginNotificationData.SlayerData(2484, 300)
+            new LoginNotificationData.SlayerData(2484, 300),
+            null
         );
         verify(messageHandler).createMessage(
             url,
@@ -139,7 +145,8 @@ class MetaNotifierTest extends MockedNotifierTest {
             new LoginNotificationData.BarbarianAssault(666),
             new LoginNotificationData.SkillData(xp * skillCount, level * skillCount, levels, exp),
             Progress.of(21, 158), Progress.of(43, 300),
-            new LoginNotificationData.SlayerData(2484, 300)
+            new LoginNotificationData.SlayerData(2484, 300),
+            null
         );
         verify(messageHandler).createMessage(
             url,
@@ -168,6 +175,24 @@ class MetaNotifierTest extends MockedNotifierTest {
         verify(messageHandler, never()).createMessage(any(), anyBoolean(), any());
     }
 
+    @Test
+    void testPetDeserialization() {
+        when(configManager.getConfiguration(RuneLiteConfig.GROUP_NAME, MetaNotifier.RL_CHAT_CMD_PLUGIN_NAME))
+            .thenReturn(Boolean.TRUE.toString());
+
+        when(configManager.getRSProfileConfiguration("chatcommands", "pets2"))
+            .thenReturn(String.format("[%d, %d]", ItemID.HERBI, ItemID.BABY_MOLE));
+
+        mockItem(ItemID.HERBI, 0, "Herbi");
+        mockItem(ItemID.BABY_MOLE, 0, "Baby mole");
+
+        List<SerializedPet> expected = List.of(
+            new SerializedPet(ItemID.HERBI, "Herbi"),
+            new SerializedPet(ItemID.BABY_MOLE, "Baby mole")
+        );
+        Assertions.assertEquals(expected, notifier.getPets());
+    }
+
     private static GameStateChanged event(GameState state) {
         GameStateChanged event = new GameStateChanged();
         event.setGameState(state);
diff --git a/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java b/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java
index 4664753..fd99d9a 100644
--- a/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java
+++ b/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java
@@ -138,6 +138,7 @@ abstract class MockedNotifierTest extends MockedTestBase {
         ItemComposition item = mock(ItemComposition.class);
         when(item.getName()).thenReturn(name);
         when(item.getMembersName()).thenReturn(name);
+        when(client.getItemDefinition(id)).thenReturn(item);
         when(itemManager.getItemComposition(id)).thenReturn(item);
         when(itemManager.canonicalize(id)).thenReturn(id);
     }