commit 94b35942862a04d01c8e7176ccbda017b9f3d5da
parent a56b6f2d64000c6c60c53fa0ff77ebcaf6a1e9ff
Author: Wim Dupont <wim@wimdupont.com>
Date:   Sat,  1 Nov 2025 07:02:48 +0100
pvm autoadd
Diffstat:
9 files changed, 239 insertions(+), 3 deletions(-)
diff --git a/pom.xml b/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>3.5.0</version>
+        <version>3.5.6</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.wimdupont</groupId>
@@ -16,7 +16,7 @@
 
     <properties>
         <java.version>21</java.version>
-        <dv8ation.jda.version>5.5.1</dv8ation.jda.version>
+        <dv8ation.jda.version>5.6.1</dv8ation.jda.version>
     </properties>
 
     <dependencies>
diff --git a/src/main/java/com/wimdupont/sxcybot/client/WiseOldManClient.java b/src/main/java/com/wimdupont/sxcybot/client/WiseOldManClient.java
@@ -0,0 +1,37 @@
+package com.wimdupont.sxcybot.client;
+
+import com.wimdupont.sxcybot.exceptions.EntityNotFoundException;
+import com.wimdupont.sxcybot.model.wiseoldman.GroupDto;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.Duration;
+
+@Component
+public class WiseOldManClient {
+
+    private static final String URL = "https://api.wiseoldman.net/v2/groups/%s";
+    private static final String GROUP = "6516"; //"Best guild of Gielinor" - Wise Old Man
+    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
+
+    public GroupDto getGroup(MessageChannel channel) throws EntityNotFoundException {
+        RestTemplate restTemplate = new RestTemplateBuilder()
+                .errorHandler(new ClientErrorHandler(channel))
+                .readTimeout(Duration.ofSeconds(30))
+                .build();
+        try {
+            GroupDto result = restTemplate.getForObject(String.format(URL, GROUP), GroupDto.class);
+            if (result == null) {
+                throw new Exception("Wise Old Man Groupo is null");
+            }
+            return result;
+        } catch (Exception e) {
+            LOGGER.error(e.getMessage(), e);
+            throw new EntityNotFoundException(String.format("No WoM group available for %s.", GROUP));
+        }
+    }
+}
diff --git a/src/main/java/com/wimdupont/sxcybot/enums/Command.java b/src/main/java/com/wimdupont/sxcybot/enums/Command.java
@@ -31,7 +31,8 @@ public enum Command {
         ROLE("Add/remove a role to/from a member."),
         EDITROLE("Add/Delete record of roles."),
         EDITPVM("Add/Edit/Delete record of the PvM Role competition"),
-        EDITBOSS("Update multiplier for a OSRS Boss of the PvM Role competition");
+        EDITBOSS("Update multiplier for a OSRS Boss of the PvM Role competition"),
+        UPDATEPVM("Attempts to add Pvm Role Competitors using Wise Old Man data");
 
         public final String description;
 
diff --git a/src/main/java/com/wimdupont/sxcybot/listeners/AdminCommandListener.java b/src/main/java/com/wimdupont/sxcybot/listeners/AdminCommandListener.java
@@ -9,6 +9,7 @@ import com.wimdupont.sxcybot.listeners.admin.EditPvmListener;
 import com.wimdupont.sxcybot.listeners.admin.EditRoleListener;
 import com.wimdupont.sxcybot.listeners.admin.EditRuleListener;
 import com.wimdupont.sxcybot.listeners.admin.RoleAssignListener;
+import com.wimdupont.sxcybot.listeners.admin.pvmrole.AutoUpdatePvmListener;
 import com.wimdupont.sxcybot.services.AccessService;
 import jakarta.annotation.Nonnull;
 import net.dv8tion.jda.api.entities.Role;
@@ -34,6 +35,7 @@ public class AdminCommandListener extends ListenerAdapter {
     private final BanlistListener banlistListener;
     private final EditRoleListener editRoleListener;
     private final EditPvmListener editPvmListener;
+    private final AutoUpdatePvmListener autoUpdatePvmListener;
     private final BossUpdateMultiplierListener bossUpdateMultiplierListener;
     private final AccessService accessService;
 
@@ -43,6 +45,7 @@ public class AdminCommandListener extends ListenerAdapter {
                                 BanlistListener banlistListener,
                                 EditRoleListener editRoleListener,
                                 EditPvmListener editPvmListener,
+                                AutoUpdatePvmListener autoUpdatePvmListener,
                                 BossUpdateMultiplierListener bossUpdateMultiplierListener,
                                 AccessService accessService) {
         this.editRuleListener = editRuleListener;
@@ -51,6 +54,7 @@ public class AdminCommandListener extends ListenerAdapter {
         this.banlistListener = banlistListener;
         this.editRoleListener = editRoleListener;
         this.editPvmListener = editPvmListener;
+        this.autoUpdatePvmListener = autoUpdatePvmListener;
         this.bossUpdateMultiplierListener = bossUpdateMultiplierListener;
         this.accessService = accessService;
     }
@@ -88,6 +92,10 @@ public class AdminCommandListener extends ListenerAdapter {
                         accessService.isAllowed(roleStream, event, GENERAL);
                         bossUpdateMultiplierListener.process(event);
                     }
+                    case UPDATEPVM -> {
+                        accessService.isAllowed(roleStream, event, ADMIN_ROLE);
+                        autoUpdatePvmListener.process(event);
+                    }
                 }
             } catch (InsufficientPrivilegesException e) {
                 event.getChannel().sendMessage(e.getMessage()).queue();
diff --git a/src/main/java/com/wimdupont/sxcybot/listeners/admin/pvmrole/AutoUpdatePvmListener.java b/src/main/java/com/wimdupont/sxcybot/listeners/admin/pvmrole/AutoUpdatePvmListener.java
@@ -0,0 +1,30 @@
+package com.wimdupont.sxcybot.listeners.admin.pvmrole;
+
+import com.wimdupont.sxcybot.exceptions.EntityNotFoundException;
+import com.wimdupont.sxcybot.listeners.Listener;
+import com.wimdupont.sxcybot.services.guild.pvmrole.PvmRoleUserAutoAddService;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AutoUpdatePvmListener implements Listener {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+    private final PvmRoleUserAutoAddService pvmRoleUserAutoAddService;
+
+    public AutoUpdatePvmListener(PvmRoleUserAutoAddService pvmRoleUserAutoAddService) {
+        this.pvmRoleUserAutoAddService = pvmRoleUserAutoAddService;
+    }
+
+    @Override
+    public void process(MessageReceivedEvent event) {
+        try {
+            pvmRoleUserAutoAddService.autoAdd(event.getChannel().asTextChannel());
+        } catch (EntityNotFoundException e) {
+            logger.error(e.getMessage(), e);
+            event.getMessage().getChannel().sendMessage(String.format("Error during Auto adding: %s.", e.getMessage())).queue();
+        }
+    }
+}
diff --git a/src/main/java/com/wimdupont/sxcybot/model/wiseoldman/GroupDto.java b/src/main/java/com/wimdupont/sxcybot/model/wiseoldman/GroupDto.java
@@ -0,0 +1,13 @@
+package com.wimdupont.sxcybot.model.wiseoldman;
+
+import java.util.List;
+
+public record GroupDto(
+        int id,
+        String name,
+        String description,
+        int homeworld,
+        List<MembershipDto> memberships
+) {
+
+}
diff --git a/src/main/java/com/wimdupont/sxcybot/model/wiseoldman/MembershipDto.java b/src/main/java/com/wimdupont/sxcybot/model/wiseoldman/MembershipDto.java
@@ -0,0 +1,13 @@
+package com.wimdupont.sxcybot.model.wiseoldman;
+
+import java.time.LocalDateTime;
+
+public record MembershipDto(
+        int playerId,
+        int groupId,
+        String role,
+        LocalDateTime createdAt,
+        LocalDateTime updatedAt,
+        PlayerDto player
+) {
+}
diff --git a/src/main/java/com/wimdupont/sxcybot/model/wiseoldman/PlayerDto.java b/src/main/java/com/wimdupont/sxcybot/model/wiseoldman/PlayerDto.java
@@ -0,0 +1,17 @@
+package com.wimdupont.sxcybot.model.wiseoldman;
+
+import java.time.LocalDateTime;
+
+public record PlayerDto(
+        int id,
+        String username,
+        String displayName,
+        String type,
+        String build,
+        String status,
+        LocalDateTime registeredAt,
+        LocalDateTime updatedAt,
+        LocalDateTime lastChangedAt,
+        LocalDateTime lastImportedAt
+) {
+}
diff --git a/src/main/java/com/wimdupont/sxcybot/services/guild/pvmrole/PvmRoleUserAutoAddService.java b/src/main/java/com/wimdupont/sxcybot/services/guild/pvmrole/PvmRoleUserAutoAddService.java
@@ -0,0 +1,117 @@
+package com.wimdupont.sxcybot.services.guild.pvmrole;
+
+import com.wimdupont.sxcybot.client.WiseOldManClient;
+import com.wimdupont.sxcybot.exceptions.EntityNotFoundException;
+import com.wimdupont.sxcybot.model.wiseoldman.MembershipDto;
+import com.wimdupont.sxcybot.repository.guild.pvmrole.dao.PvmRoleUser;
+import com.wimdupont.sxcybot.services.guild.ChannelDetailService;
+import com.wimdupont.sxcybot.util.JdaUtil;
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.entities.MessageEmbed.Field;
+import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+@Service
+public class PvmRoleUserAutoAddService {
+
+    private final WiseOldManClient wiseOldManClient;
+    private final ChannelDetailService channelDetailService;
+    private final PvmRoleUserService pvmRoleUserService;
+    private final PvmRoleSnapshotComparatorService pvmRoleSnapshotComparatorService;
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    public PvmRoleUserAutoAddService(WiseOldManClient wiseOldManClient,
+                                     ChannelDetailService channelDetailService,
+                                     PvmRoleUserService pvmRoleUserService,
+                                     PvmRoleSnapshotComparatorService pvmRoleSnapshotComparatorService) {
+        this.wiseOldManClient = wiseOldManClient;
+        this.channelDetailService = channelDetailService;
+        this.pvmRoleUserService = pvmRoleUserService;
+        this.pvmRoleSnapshotComparatorService = pvmRoleSnapshotComparatorService;
+    }
+
+    public void autoAdd(TextChannel channel) throws EntityNotFoundException {
+        var group = wiseOldManClient.getGroup(channel);
+        var obscureList = new HashMap<String, List<String>>();
+        var unknownList = new ArrayList<String>();
+        channelDetailService.getJda().getGuilds().forEach(guild -> {
+            for (MembershipDto membership : group.memberships()) {
+                try {
+                    pvmRoleUserService.findByRsn(membership.player().username());
+                } catch (EntityNotFoundException e) {
+                    var discordUsers = guild.getMembersByNickname(membership.player().username(), true);
+                    if (discordUsers.size() > 1) {
+                        obscureList.put(membership.player().username(),
+                                discordUsers.stream()
+                                        .map(f -> f.getUser().getEffectiveName())
+                                        .toList());
+                        logger.warn("Multiple users found for rsn {}", membership.player().username());
+                    }
+                    if (!discordUsers.isEmpty()) {
+                        var pvmRoleUser = pvmRoleUserService.save(PvmRoleUser.Builder.newBuilder()
+                                .discordId(discordUsers.getFirst().getId())
+                                .rsn(membership.player().username())
+                                .build());
+                        try {
+                            pvmRoleSnapshotComparatorService.takeSnapshot(channel, pvmRoleUser, true, true);
+                        } catch (EntityNotFoundException ex) {
+                            logger.error("No snapshot taken for {} : {}", pvmRoleUser, ex.getMessage());
+                            channel.sendMessage(String.format("Error whilst taking snapshot for %s.", pvmRoleUser.getRsn())).queue();
+                        }
+                    } else {
+                        unknownList.add(membership.player().username());
+                        logger.warn("No users found for rsn {}", membership.player().username());
+                    }
+                }
+            }
+            if (!obscureList.isEmpty()) {
+                postObscures(channel, obscureList);
+            }
+            if (!unknownList.isEmpty()) {
+                postUnknowns(channel, unknownList);
+            }
+        });
+    }
+
+    private void postObscures(TextChannel channel, HashMap<String, List<String>> obscureList) {
+        EmbedBuilder embedBuilder = new EmbedBuilder();
+        embedBuilder.setColor(Color.red);
+        embedBuilder.setTitle("Obscure RSN's");
+        int i = 1;
+        for (Entry<String, List<String>> obscureRsn : obscureList.entrySet()) {
+            embedBuilder.addField(new Field(obscureRsn.getKey(), String.join(", ", obscureRsn.getValue()), true));
+            if (JdaUtil.requiresBuild(embedBuilder, obscureList.size(), i)) {
+                channel.sendMessageEmbeds(embedBuilder.build()).queue();
+                embedBuilder.clearFields();
+            }
+            i++;
+        }
+        if (!embedBuilder.getFields().isEmpty())
+            channel.sendMessageEmbeds(embedBuilder.build()).queue();
+    }
+
+    private void postUnknowns(TextChannel channel, List<String> unknownList) {
+        EmbedBuilder embedBuilder = new EmbedBuilder();
+        embedBuilder.setColor(Color.red);
+        embedBuilder.setTitle("Unknown RSN's");
+        int i = 1;
+        for (String unknownRsn : unknownList) {
+            embedBuilder.addField(new Field(unknownRsn, "", true));
+            if (JdaUtil.requiresBuild(embedBuilder, unknownList.size(), i)) {
+                channel.sendMessageEmbeds(embedBuilder.build()).queue();
+                embedBuilder.clearFields();
+            }
+            i++;
+        }
+        if (!embedBuilder.getFields().isEmpty())
+            channel.sendMessageEmbeds(embedBuilder.build()).queue();
+    }
+}