Repository: pajlads/DinkPlugin
Pull request #429 (2024-02-24 22:12:50)

feat: add chat message upon noteworthy updates
Suggested by @zneix 

![image](https://github.com/pajlads/DinkPlugin/assets/8106344/4713a06a-cab7-4679-a4b2-67be6f723cbf)

When the plugin updates over multiple versions, the changelogs are concatenated into a single message (reporting the latest version)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13b9163..84c61d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## Unreleased
 
 - Major: Add item rarity to NPC loot notifications. (#425)
+- Minor: Add chat message upon noteworthy plugin updates. (#429)
 - Minor: Allow loot notifications for rare items below the min value threshold. (#426)
 - Minor: Include relevant kill count in collection log notifications. (#424)
 - Minor: Obtain kill count from chat commands plugin for loot notifications. (#392)
diff --git a/src/main/java/dinkplugin/DinkPlugin.java b/src/main/java/dinkplugin/DinkPlugin.java
index b83ac9c..0160982 100644
--- a/src/main/java/dinkplugin/DinkPlugin.java
+++ b/src/main/java/dinkplugin/DinkPlugin.java
@@ -47,6 +47,7 @@ import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.events.NpcLootReceived;
 import net.runelite.client.events.PlayerLootReceived;
+import net.runelite.client.events.ProfileChanged;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
 import net.runelite.client.plugins.loottracker.LootReceived;
@@ -69,6 +70,7 @@ public class DinkPlugin extends Plugin {
     private @Inject ChatMessageManager chatManager;
 
     private @Inject SettingsManager settingsManager;
+    private @Inject VersionManager versionManager;
 
     private @Inject KillCountService killCountService;
 
@@ -97,6 +99,7 @@ public class DinkPlugin extends Plugin {
     protected void startUp() {
         log.debug("Started up Dink");
         settingsManager.init();
+        versionManager.onStart();
         lootNotifier.init();
         deathNotifier.init();
     }
@@ -168,6 +171,7 @@ public class DinkPlugin extends Plugin {
             return;
         }
 
+        versionManager.onGameState(previousState, newState);
         settingsManager.onGameState(previousState, newState);
         collectionNotifier.onGameState(newState);
         levelNotifier.onGameStateChanged(gameStateChanged);
@@ -276,6 +280,11 @@ public class DinkPlugin extends Plugin {
         lootNotifier.onPlayerLootReceived(playerLootReceived);
     }
 
+    @Subscribe
+    public void onProfileChanged(ProfileChanged event) {
+        versionManager.onProfileChange();
+    }
+
     @Subscribe
     public void onLootReceived(LootReceived lootReceived) {
         killCountService.onLoot(lootReceived);
@@ -313,7 +322,7 @@ public class DinkPlugin extends Plugin {
         addChatMessage("Warning", Utils.RED, message);
     }
 
-    private void addChatMessage(String category, Color color, String message) {
+    void addChatMessage(String category, Color color, String message) {
         String formatted = String.format("[%s] %s: %s",
             ColorUtil.wrapWithColorTag(getName(), Utils.PINK),
             category,
diff --git a/src/main/java/dinkplugin/DinkPluginConfig.java b/src/main/java/dinkplugin/DinkPluginConfig.java
index 3012a89..a64331a 100644
--- a/src/main/java/dinkplugin/DinkPluginConfig.java
+++ b/src/main/java/dinkplugin/DinkPluginConfig.java
@@ -182,6 +182,16 @@ public interface DinkPluginConfig extends Config {
     )
     String advancedSection = "Advanced";
 
+    @ConfigItem(
+        keyName = VersionManager.VERSION_CONFIG_KEY,
+        name = "Plugin Version",
+        description = "The latest dink version used by the player that has a notable changelog entry",
+        hidden = true
+    )
+    default String pluginVersion() {
+        return "";
+    }
+
     @ConfigItem(
         keyName = "maxRetries",
         name = "Webhook Max Retries",
diff --git a/src/main/java/dinkplugin/SettingsManager.java b/src/main/java/dinkplugin/SettingsManager.java
index 40ca299..e855f8c 100644
--- a/src/main/java/dinkplugin/SettingsManager.java
+++ b/src/main/java/dinkplugin/SettingsManager.java
@@ -265,6 +265,21 @@ public class SettingsManager {
             clientThread.invoke(() -> checkVarbits(id, client.getVarbitValue(id)));
     }
 
+    boolean hasModifiedConfig() {
+        for (String webhookConfigKey : webhookConfigKeys) {
+            String webhookUrl = configManager.getConfiguration(SettingsManager.CONFIG_GROUP, webhookConfigKey);
+            if (!webhookUrl.isEmpty()) {
+                return true;
+            }
+        }
+        return config.notifyAchievementDiary() || config.notifyClue() || config.notifyCollectionLog() ||
+            config.notifyCombatTask() || config.notifyDeath() || config.notifyGamble() ||
+            config.notifyGrandExchange() || config.notifyGroupStorage() || config.notifyKillCount() ||
+            config.notifyLeagues() || config.notifyLevel() || config.notifyLoot() || config.notifyPet() ||
+            config.notifyPk() || config.notifyQuest() || config.notifySlayer() || config.notifySpeedrun() ||
+            config.notifyTrades();
+    }
+
     private boolean checkVarbits(int id, int value) {
         if (client.getGameState() != GameState.LOGGED_IN)
             return false; // try again on next tick
diff --git a/src/main/java/dinkplugin/VersionManager.java b/src/main/java/dinkplugin/VersionManager.java
new file mode 100644
index 0000000..d703408
--- /dev/null
+++ b/src/main/java/dinkplugin/VersionManager.java
@@ -0,0 +1,130 @@
+package dinkplugin;
+
+import dinkplugin.util.Utils;
+import lombok.Value;
+import net.runelite.api.GameState;
+import net.runelite.client.config.ConfigManager;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Comparator;
+import java.util.NavigableMap;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Singleton
+public class VersionManager {
+    public static final String VERSION_CONFIG_KEY = "pluginVersion";
+    private static final long NOTIFICATION_DELAY_SECONDS = 3;
+    private static final NavigableMap<Version, String> VERSIONS = new TreeMap<>(
+        Comparator.comparingInt(Version::getMajor)
+            .thenComparingInt(Version::getMinor)
+            .thenComparingInt(Version::getPatch)
+    );
+
+    private final Version latest = VERSIONS.lastKey();
+
+    @Inject
+    private DinkPlugin plugin;
+
+    @Inject
+    private DinkPluginConfig config;
+
+    @Inject
+    private ConfigManager configManager;
+
+    @Inject
+    private SettingsManager settingsManager;
+
+    @Inject
+    private ScheduledExecutorService executor;
+
+    void onStart() {
+        if (config.pluginVersion().isEmpty()) {
+            if (settingsManager.hasModifiedConfig()) {
+                // Dink was already installed/used & launched for the first time since VersionManager was created
+                setStoredVersion("1.8.4");
+            } else {
+                // first time launching Dink; no chat message necessary
+                setStoredVersion(latest.toString());
+            }
+        }
+    }
+
+    void onGameState(GameState oldState, GameState newState) {
+        if (newState != GameState.LOGGED_IN || oldState != GameState.LOGGING_IN) {
+            // only check version when LOGGING_IN => LOGGED_IN
+            return;
+        }
+
+        Version storedVersion = Version.of(config.pluginVersion());
+        if (storedVersion == null || storedVersion.equals(latest)) {
+            return;
+        }
+
+        setStoredVersion(latest.toString());
+        executor.schedule(() -> {
+            SortedMap<Version, String> latestUpdates = VERSIONS.tailMap(storedVersion, false);
+            if (latestUpdates.isEmpty()) {
+                return;
+            }
+            String displayVersion = latest.getPatch() == 0
+                ? String.format("%d.%d.X", latest.getMajor(), latest.getMinor()) // avoids needing changelog for each patch
+                : latest.toString();
+            plugin.addChatMessage(
+                "Updated to v" + displayVersion,
+                Utils.GREEN,
+                String.join("; ", latestUpdates.values())
+            );
+        }, NOTIFICATION_DELAY_SECONDS, TimeUnit.SECONDS);
+    }
+
+    void onProfileChange() {
+        // if profile changes, set pluginVersion to latest so users don't see old changelogs on login
+        setStoredVersion(latest.toString());
+    }
+
+    private void setStoredVersion(String version) {
+        configManager.setConfiguration(SettingsManager.CONFIG_GROUP, VERSION_CONFIG_KEY, version);
+    }
+
+    private static void register(String version, String changelog) {
+        VERSIONS.put(Version.of(version), changelog);
+    }
+
+    @Value
+    private static class Version {
+        int major;
+        int minor;
+        int patch;
+
+        @Override
+        public String toString() {
+            return String.format("%d.%d.%d", major, minor, patch);
+        }
+
+        @Nullable
+        public static Version of(@NotNull String version) {
+            String[] parts = StringUtils.split(version, '.');
+            if (parts.length < 3) return null;
+            int major, minor, patch;
+            try {
+                major = Integer.parseInt(parts[0]);
+                minor = Integer.parseInt(parts[1]);
+                patch = Integer.parseInt(parts[2]);
+            } catch (NumberFormatException ignored) {
+                return null;
+            }
+            return new Version(major, minor, patch);
+        }
+    }
+
+    static {
+        register("1.9.0", "Notifications now report monster drop rarity");
+    }
+}


Test patch:
diff --git a/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java b/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java
index 6894f48..630b402 100644
--- a/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java
+++ b/src/test/java/dinkplugin/notifiers/MockedNotifierTest.java
@@ -120,6 +120,7 @@ abstract class MockedNotifierTest extends MockedTestBase {
         when(configDescriptor.getItems()).thenReturn(Collections.emptyList());
 
         // init config mocks
+        when(config.pluginVersion()).thenReturn("");
         when(config.primaryWebhook()).thenReturn(PRIMARY_WEBHOOK_URL);
         when(config.maxRetries()).thenReturn(0);
         when(config.baseRetryDelay()).thenReturn(2000);