From 576f7b156de9a45f39726b76b0d65168aff1a75a Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 27 Jun 2024 14:29:48 +0100 Subject: [PATCH] add afk feature --- .../cc/fascinated/bat/common/MemberUtils.java | 40 ++++++++ .../cc/fascinated/bat/common/TimeUtils.java | 1 - .../fascinated/bat/event/EventListener.java | 9 ++ .../bat/features/afk/AfkFeature.java | 20 ++++ .../bat/features/afk/AfkMentionListener.java | 35 +++++++ .../bat/features/afk/AfkReturnListener.java | 25 +++++ .../bat/features/afk/command/AfkCommand.java | 42 ++++++++ .../bat/features/afk/profile/AfkProfile.java | 99 +++++++++++++++++++ .../fascinated/bat/service/EventService.java | 20 ++++ 9 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 src/main/java/cc/fascinated/bat/common/MemberUtils.java create mode 100644 src/main/java/cc/fascinated/bat/features/afk/AfkFeature.java create mode 100644 src/main/java/cc/fascinated/bat/features/afk/AfkMentionListener.java create mode 100644 src/main/java/cc/fascinated/bat/features/afk/AfkReturnListener.java create mode 100644 src/main/java/cc/fascinated/bat/features/afk/command/AfkCommand.java create mode 100644 src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java diff --git a/src/main/java/cc/fascinated/bat/common/MemberUtils.java b/src/main/java/cc/fascinated/bat/common/MemberUtils.java new file mode 100644 index 0000000..85c96dc --- /dev/null +++ b/src/main/java/cc/fascinated/bat/common/MemberUtils.java @@ -0,0 +1,40 @@ +package cc.fascinated.bat.common; + +import cc.fascinated.bat.model.BatGuild; +import cc.fascinated.bat.model.BatUser; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; + +import java.util.Comparator; + +/** + * @author Fascinated (fascinated7) + */ +public class MemberUtils { + /** + * Checks if a user has permission to edit another user + * + * @param guild the guild to check + * @param user the user to check + * @return if the user has permission to edit another user + */ + public static boolean hasPermissionToEdit(BatGuild guild, BatUser user) { + Member botUser = guild.getDiscordGuild().getSelfMember(); + Member member = guild.getDiscordGuild().getMemberById(user.getId()); + if (member == null) { + return false; + } + + return botUser.canInteract(member) && getHighestRole(botUser).getPosition() > getHighestRole(member).getPosition(); + } + + /** + * Gets the highest role of a member + * + * @param member the member to get the highest role of + * @return the highest role of the member + */ + public static Role getHighestRole(Member member) { + return member.getRoles().stream().max(Comparator.comparingInt(Role::getPosition)).orElse(null); + } +} diff --git a/src/main/java/cc/fascinated/bat/common/TimeUtils.java b/src/main/java/cc/fascinated/bat/common/TimeUtils.java index 603d739..f3b902e 100644 --- a/src/main/java/cc/fascinated/bat/common/TimeUtils.java +++ b/src/main/java/cc/fascinated/bat/common/TimeUtils.java @@ -4,7 +4,6 @@ import lombok.*; import lombok.experimental.UtilityClass; import org.jetbrains.annotations.Nullable; -import java.text.SimpleDateFormat; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/cc/fascinated/bat/event/EventListener.java b/src/main/java/cc/fascinated/bat/event/EventListener.java index ee0a2a6..2befe95 100644 --- a/src/main/java/cc/fascinated/bat/event/EventListener.java +++ b/src/main/java/cc/fascinated/bat/event/EventListener.java @@ -8,6 +8,7 @@ import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken; import lombok.NonNull; import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; /** * @author Fascinated (fascinated7) @@ -38,4 +39,12 @@ public interface EventListener { * @param user the user that left the guild */ default void onGuildMemberLeave(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberRemoveEvent event) {} + + /** + * Called when a user types a message + * + * @param guild the guild that the message was sent in + * @param user the user that sent the message + */ + default void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {} } diff --git a/src/main/java/cc/fascinated/bat/features/afk/AfkFeature.java b/src/main/java/cc/fascinated/bat/features/afk/AfkFeature.java new file mode 100644 index 0000000..dd80f05 --- /dev/null +++ b/src/main/java/cc/fascinated/bat/features/afk/AfkFeature.java @@ -0,0 +1,20 @@ +package cc.fascinated.bat.features.afk; + +import cc.fascinated.bat.features.Feature; +import cc.fascinated.bat.features.afk.command.AfkCommand; +import cc.fascinated.bat.service.CommandService; +import lombok.NonNull; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +/** + * @author Fascinated (fascinated7) + */ +@Component +public class AfkFeature extends Feature { + public AfkFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) { + super("AFK", Category.GENERAL); + + commandService.registerCommand(context.getBean(AfkCommand.class)); + } +} diff --git a/src/main/java/cc/fascinated/bat/features/afk/AfkMentionListener.java b/src/main/java/cc/fascinated/bat/features/afk/AfkMentionListener.java new file mode 100644 index 0000000..9716696 --- /dev/null +++ b/src/main/java/cc/fascinated/bat/features/afk/AfkMentionListener.java @@ -0,0 +1,35 @@ +package cc.fascinated.bat.features.afk; + +import cc.fascinated.bat.event.EventListener; +import cc.fascinated.bat.features.afk.profile.AfkProfile; +import cc.fascinated.bat.model.BatGuild; +import cc.fascinated.bat.model.BatUser; +import lombok.NonNull; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @author Fascinated (fascinated7) + */ +@Component +public class AfkMentionListener implements EventListener { + @Override + public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) { + Message message = event.getMessage(); + List mentionedUsers = message.getMentions().getUsers(); + if (mentionedUsers.isEmpty()) { + return; + } + User mentionedUser = mentionedUsers.get(0); + AfkProfile profile = guild.getProfile(AfkProfile.class); + if (!profile.isAfk(mentionedUser.getId())) { + return; + } + + event.getMessage().reply("%s is currently AFK: %s".formatted(mentionedUser.getAsMention(), profile.getAfkReason(mentionedUser.getId()))).queue(); + } +} diff --git a/src/main/java/cc/fascinated/bat/features/afk/AfkReturnListener.java b/src/main/java/cc/fascinated/bat/features/afk/AfkReturnListener.java new file mode 100644 index 0000000..c23a3c7 --- /dev/null +++ b/src/main/java/cc/fascinated/bat/features/afk/AfkReturnListener.java @@ -0,0 +1,25 @@ +package cc.fascinated.bat.features.afk; + +import cc.fascinated.bat.event.EventListener; +import cc.fascinated.bat.features.afk.profile.AfkProfile; +import cc.fascinated.bat.model.BatGuild; +import cc.fascinated.bat.model.BatUser; +import lombok.NonNull; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.springframework.stereotype.Component; + +/** + * @author Fascinated (fascinated7) + */ +@Component +public class AfkReturnListener implements EventListener { + @Override + public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) { + AfkProfile profile = guild.getProfile(AfkProfile.class); + if (!profile.isAfk(user.getId())) { + return; + } + profile.removeAfkUser(guild, user.getId()); + event.getMessage().reply("Welcome back, %s! You are no longer AFK.".formatted(user.getDiscordUser().getAsMention())).queue(); + } +} diff --git a/src/main/java/cc/fascinated/bat/features/afk/command/AfkCommand.java b/src/main/java/cc/fascinated/bat/features/afk/command/AfkCommand.java new file mode 100644 index 0000000..ebe7315 --- /dev/null +++ b/src/main/java/cc/fascinated/bat/features/afk/command/AfkCommand.java @@ -0,0 +1,42 @@ +package cc.fascinated.bat.features.afk.command; + +import cc.fascinated.bat.command.BatCommand; +import cc.fascinated.bat.common.MemberUtils; +import cc.fascinated.bat.features.afk.profile.AfkProfile; +import cc.fascinated.bat.model.BatGuild; +import cc.fascinated.bat.model.BatUser; +import lombok.NonNull; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; +import org.springframework.stereotype.Component; + +/** + * @author Fascinated (fascinated7) + */ +@Component +public class AfkCommand extends BatCommand { + public AfkCommand() { + super("afk", "Sets your AFK status"); + super.addOption(OptionType.STRING, "reason", "The reason for being AFK", false); + } + + @Override + public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { + AfkProfile profile = guild.getProfile(AfkProfile.class); + String reason = null; + OptionMapping reasonOption = interaction.getOption("reason"); + if (reasonOption != null) { + reason = reasonOption.getAsString(); + } + + profile.addAfkUser(guild, member.getId(), reason); + interaction.reply("You are now AFK: %s%s".formatted( + profile.getAfkReason(member.getId()), + MemberUtils.hasPermissionToEdit(guild, user) ? "" : + "\n\n*I do not have enough permissions to edit your user, and therefore cannot update your nickname*" + )).queue(); + } +} diff --git a/src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java b/src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java new file mode 100644 index 0000000..02ce819 --- /dev/null +++ b/src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java @@ -0,0 +1,99 @@ +package cc.fascinated.bat.features.afk.profile; + +import cc.fascinated.bat.common.Profile; +import cc.fascinated.bat.model.BatGuild; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Fascinated (fascinated7) + */ +@Component +public class AfkProfile extends Profile { + private static final String DEFAULT_REASON = "Away"; + + /** + * The AFK users in this guild + */ + private Map afkUsers; + + /** + * Adds a user to the AFK list + * + * @param guild the guild enable afk mode for + * @param userId the user ID to add + * @param reason the reason for being AFK + */ + public void addAfkUser(BatGuild guild, String userId, String reason) { + if (afkUsers == null) { + afkUsers = new HashMap<>(); + } + afkUsers.put(userId, reason == null ? DEFAULT_REASON : reason); + + Guild discordGuild = guild.getDiscordGuild(); + Member member = discordGuild.getMemberById(userId); + if (member == null) { + return; + } + try { + member.modifyNickname("[AFK] " + member.getEffectiveName()).queue(); + } catch (Exception ignored) {} + } + + /** + * Removes a user from the AFK list + * + * @param guild the guild to remove the user from + * @param userId the user ID to remove + */ + public void removeAfkUser(BatGuild guild, String userId) { + if (afkUsers == null) { + afkUsers = new HashMap<>(); + } + afkUsers.remove(userId); + + Guild discordGuild = guild.getDiscordGuild(); + Member member = discordGuild.getMemberById(userId); + if (member == null) { + return; + } + try { + member.modifyNickname(member.getEffectiveName().replace("[AFK] ", "")).queue(); + } catch (Exception ignored) {} + } + + /** + * Gets the reason for being AFK + * + * @param userId the user ID to get the reason for + * @return the reason for being AFK + */ + public String getAfkReason(String userId) { + if (afkUsers == null) { + afkUsers = new HashMap<>(); + } + return afkUsers.get(userId); + } + + /** + * Checks if a user is AFK + * + * @param userId the user ID to check + * @return if the user is AFK + */ + public boolean isAfk(String userId) { + if (afkUsers == null) { + afkUsers = new HashMap<>(); + } + return afkUsers.containsKey(userId); + } + + @Override + public void reset() { + afkUsers = new HashMap<>(); + } +} diff --git a/src/main/java/cc/fascinated/bat/service/EventService.java b/src/main/java/cc/fascinated/bat/service/EventService.java index c6b9fcd..4810e00 100644 --- a/src/main/java/cc/fascinated/bat/service/EventService.java +++ b/src/main/java/cc/fascinated/bat/service/EventService.java @@ -7,6 +7,7 @@ import lombok.NonNull; import lombok.extern.log4j.Log4j2; import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; @@ -52,6 +53,9 @@ public class EventService extends ListenerAdapter { @Override public void onGuildMemberJoin(@NotNull GuildMemberJoinEvent event) { + if (event.getUser().isBot()) { + return; + } BatGuild guild = guildService.getGuild(event.getGuild().getId()); BatUser user = userService.getUser(event.getUser().getId()); @@ -62,6 +66,9 @@ public class EventService extends ListenerAdapter { @Override public void onGuildMemberRemove(@NonNull GuildMemberRemoveEvent event) { + if (event.getUser().isBot()) { + return; + } BatGuild guild = guildService.getGuild(event.getGuild().getId()); BatUser user = userService.getUser(event.getUser().getId()); @@ -69,4 +76,17 @@ public class EventService extends ListenerAdapter { listener.onGuildMemberLeave(guild, user, event); } } + + @Override + public void onMessageReceived(MessageReceivedEvent event) { + if (event.getAuthor().isBot()) { + return; + } + BatGuild guild = guildService.getGuild(event.getGuild().getId()); + BatUser user = userService.getUser(event.getAuthor().getId()); + + for (EventListener listener : LISTENERS) { + listener.onGuildMessageReceive(guild, user, event); + } + } }