add afk feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s

This commit is contained in:
Lee 2024-06-27 14:29:48 +01:00
parent 0fc5e6514a
commit 576f7b156d
9 changed files with 290 additions and 1 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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) {}
}

View File

@ -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));
}
}

View File

@ -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<User> 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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<String, String> 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<>();
}
}

View File

@ -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);
}
}
}