diff --git a/application/config.json.template b/application/config.json.template index f8a14abc2a..5884522c60 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -10,8 +10,8 @@ "mutedRolePattern": "Muted", "heavyModerationRolePattern": "Moderator", "softModerationRolePattern": "Moderator|Community Ambassador", - "tagManageRolePattern": "Moderator|Community Ambassador|Top Helpers .+", - "excludeCodeAutoDetectionRolePattern": "Top Helpers .+|Moderator|Community Ambassador|Expert", + "tagManageRolePattern": "Moderator|Community Ambassador|Top Helper.*", + "excludeCodeAutoDetectionRolePattern": "Top Helper.*|Moderator|Community Ambassador|Expert", "suggestions": { "channelPattern": "tj-suggestions", "upVoteEmoteName": "peepo_yes", @@ -22,7 +22,7 @@ "mode": "AUTO_DELETE_BUT_APPROVE_QUARANTINE", "reportChannelPattern": "commands", "botTrapChannelPattern": "bot-trap", - "trustedUserRolePattern": "Top Helpers .+|Moderator|Community Ambassador|Expert", + "trustedUserRolePattern": "Top Helper.*|Moderator|Community Ambassador|Expert", "suspiciousKeywords": [ "nitro", "boob", @@ -189,5 +189,10 @@ "fallbackChannelPattern": "java-news-and-changes", "pollIntervalInMinutes": 10 }, - "memberCountCategoryPattern": "Info" + "memberCountCategoryPattern": "Info", + "topHelpers": { + "rolePattern": "Top Helper.*", + "assignmentChannelPattern": "community-commands", + "announcementChannelPattern": "hall-of-fame" + } } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index 79c04e6cad..60e6622cbc 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -48,6 +48,7 @@ public final class Config { private final RSSFeedsConfig rssFeedsConfig; private final String selectRolesChannelPattern; private final String memberCountCategoryPattern; + private final TopHelpersConfig topHelpers; @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) @@ -100,7 +101,8 @@ private Config(@JsonProperty(value = "token", required = true) String token, required = true) FeatureBlacklistConfig featureBlacklistConfig, @JsonProperty(value = "rssConfig", required = true) RSSFeedsConfig rssFeedsConfig, @JsonProperty(value = "selectRolesChannelPattern", - required = true) String selectRolesChannelPattern) { + required = true) String selectRolesChannelPattern, + @JsonProperty(value = "topHelpers", required = true) TopHelpersConfig topHelpers) { this.token = Objects.requireNonNull(token); this.githubApiKey = Objects.requireNonNull(githubApiKey); this.databasePath = Objects.requireNonNull(databasePath); @@ -135,6 +137,7 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.featureBlacklistConfig = Objects.requireNonNull(featureBlacklistConfig); this.rssFeedsConfig = Objects.requireNonNull(rssFeedsConfig); this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern); + this.topHelpers = Objects.requireNonNull(topHelpers); } /** @@ -445,4 +448,13 @@ public String getMemberCountCategoryPattern() { public RSSFeedsConfig getRSSFeedsConfig() { return rssFeedsConfig; } + + /** + * Gets the config for the Top Helpers system. + * + * @return the configuration + */ + public TopHelpersConfig getTopHelpers() { + return topHelpers; + } } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/TopHelpersConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/TopHelpersConfig.java new file mode 100644 index 0000000000..6f4b20d9db --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/config/TopHelpersConfig.java @@ -0,0 +1,58 @@ +package org.togetherjava.tjbot.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import java.util.Objects; + +/** + * Configuration for the top helper system, see + * {@link org.togetherjava.tjbot.features.tophelper.TopHelpersCommand}. + */ +@JsonRootName("topHelpers") +public final class TopHelpersConfig { + private final String rolePattern; + private final String assignmentChannelPattern; + private final String announcementChannelPattern; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private TopHelpersConfig( + @JsonProperty(value = "rolePattern", required = true) String rolePattern, + @JsonProperty(value = "assignmentChannelPattern", + required = true) String assignmentChannelPattern, + @JsonProperty(value = "announcementChannelPattern", + required = true) String announcementChannelPattern) { + this.rolePattern = Objects.requireNonNull(rolePattern); + this.assignmentChannelPattern = Objects.requireNonNull(assignmentChannelPattern); + this.announcementChannelPattern = Objects.requireNonNull(announcementChannelPattern); + } + + /** + * Gets the REGEX pattern matching the role used to represent Top Helpers. + * + * @return the role name pattern + */ + public String getRolePattern() { + return rolePattern; + } + + /** + * Gets the REGEX pattern used to identify the channel where Top Helper assignments are + * automatically executed. + * + * @return the channel name pattern + */ + public String getAssignmentChannelPattern() { + return assignmentChannelPattern; + } + + /** + * Gets the REGEX pattern used to identify the channel where Top Helper announcements are send. + * + * @return the channel name pattern + */ + public String getAnnouncementChannelPattern() { + return announcementChannelPattern; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index 6746c7f8a2..463c3b5248 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -72,9 +72,11 @@ import org.togetherjava.tjbot.features.tags.TagManageCommand; import org.togetherjava.tjbot.features.tags.TagSystem; import org.togetherjava.tjbot.features.tags.TagsCommand; +import org.togetherjava.tjbot.features.tophelper.TopHelpersAssignmentRoutine; import org.togetherjava.tjbot.features.tophelper.TopHelpersCommand; import org.togetherjava.tjbot.features.tophelper.TopHelpersMessageListener; import org.togetherjava.tjbot.features.tophelper.TopHelpersPurgeMessagesRoutine; +import org.togetherjava.tjbot.features.tophelper.TopHelpersService; import java.util.ArrayList; import java.util.Collection; @@ -119,6 +121,9 @@ public static Collection createFeatures(JDA jda, Database database, Con HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database, chatGptService); HelpThreadLifecycleListener helpThreadLifecycleListener = new HelpThreadLifecycleListener(helpSystemHelper, database); + TopHelpersService topHelpersService = new TopHelpersService(database); + TopHelpersAssignmentRoutine topHelpersAssignmentRoutine = + new TopHelpersAssignmentRoutine(config, topHelpersService); // NOTE The system can add special system relevant commands also by itself, // hence this list may not necessarily represent the full list of all commands actually @@ -140,6 +145,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new MarkHelpThreadCloseInDBRoutine(database, helpThreadLifecycleListener)); features.add(new MemberCountDisplayRoutine(config)); features.add(new RSSHandlerRoutine(config, database)); + features.add(topHelpersAssignmentRoutine); // Message receivers features.add(new TopHelpersMessageListener(database, config)); @@ -182,7 +188,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new AuditCommand(actionsStore)); features.add(new MuteCommand(actionsStore, config)); features.add(new UnmuteCommand(actionsStore, config)); - features.add(new TopHelpersCommand(database)); + features.add(new TopHelpersCommand(topHelpersService, topHelpersAssignmentRoutine)); features.add(new RoleSelectCommand()); features.add(new NoteCommand(actionsStore)); features.add(new ReminderCommand(database)); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Routine.java b/application/src/main/java/org/togetherjava/tjbot/features/Routine.java index b5d2f614af..806618649d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Routine.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Routine.java @@ -2,7 +2,19 @@ import net.dv8tion.jda.api.JDA; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.stream.Collectors; /** * Routines are executed on a reoccurring schedule by the core system. @@ -45,6 +57,97 @@ public interface Routine extends Feature { * seconds */ record Schedule(ScheduleMode mode, long initialDuration, long duration, TimeUnit unit) { + + private static final int HOURS_OF_DAY = 24; + + /** + * Creates a schedule for execution at a fixed hour of the day. The initial first execution + * will be delayed to the next fixed time that matches the given hour of the day, + * effectively making execution stable at that fixed hour - regardless of when this method + * was originally triggered. + *

+ * For example, if the given hour is 12 o'clock, this leads to the fixed execution times of + * only 12:00 each day. The first execution is then delayed to the closest time in that + * schedule. For example, if triggered at 7:00, execution will happen at 12:00 and then + * follow the schedule. + *

+ * Execution will also correctly roll over to the next day, for example if the method is + * triggered at 21:30, the next execution will be at 12:00 the following day. + * + * @param hourOfDay the hour of the day that marks the start of this period + * @return the according schedule representing the planned execution + */ + public static Schedule atFixedHour(int hourOfDay) { + return atFixedRateFromNextFixedTime(hourOfDay, HOURS_OF_DAY); + } + + /** + * Creates a schedule for execution at a fixed rate (see + * {@link ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, TimeUnit)}). + * The initial first execution will be delayed to the next fixed time that matches the given + * period, effectively making execution stable at fixed times of a day - regardless of when + * this method was originally triggered. + *

+ * For example, if the given period is 8 hours with a start hour of 4 o'clock, this leads to + * the fixed execution times of 4:00, 12:00 and 20:00 each day. The first execution is then + * delayed to the closest time in that schedule. For example, if triggered at 7:00, + * execution will happen at 12:00 and then follow the schedule. + *

+ * Execution will also correctly roll over to the next day, for example if the method is + * triggered at 21:30, the next execution will be at 4:00 the following day. + * + * @param periodStartHour the hour of the day that marks the start of this period + * @param periodHours the scheduling period in hours + * @return the according schedule representing the planned execution + */ + public static Schedule atFixedRateFromNextFixedTime(int periodStartHour, int periodHours) { + // NOTE This scheduler could be improved, for example supporting arbitrary periods (not + // just hour-based). Also, it probably does not correctly handle all date/time-quirks, + // for example if a schedule would hit a time that does not exist for a specific date + // due to DST or similar issues. Those are minor though and can be ignored for now. + if (periodStartHour < 0 || periodStartHour >= HOURS_OF_DAY) { + throw new IllegalArgumentException( + "Schedule period start hour must be a valid hour of a day (0-23)"); + } + if (periodHours <= 0 || periodHours > HOURS_OF_DAY) { + throw new IllegalArgumentException( + "Schedule period must not be zero and must fit into a single day (0-24)"); + } + + // Compute fixed schedule hours + List fixedScheduleHours = new ArrayList<>(); + + for (int hour = periodStartHour; hour < HOURS_OF_DAY; hour += periodHours) { + fixedScheduleHours.add(hour); + } + + Instant now = Instant.now(); + Instant nextFixedTime = + computeClosestNextScheduleDate(now, fixedScheduleHours, periodHours); + return new Schedule(ScheduleMode.FIXED_RATE, + ChronoUnit.SECONDS.between(now, nextFixedTime), + TimeUnit.HOURS.toSeconds(periodHours), TimeUnit.SECONDS); + } + + private static Instant computeClosestNextScheduleDate(Instant instant, + List scheduleHours, int periodHours) { + OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC); + BiFunction dateAtTime = + (date, hour) -> date.with(LocalTime.of(hour, 0)).toInstant(); + + // The instant is either before the given hours, in between, or after. + // For latter, we roll the schedule over once to the next day + List scheduleDates = scheduleHours.stream() + .map(hour -> dateAtTime.apply(offsetDateTime, hour)) + .collect(Collectors.toCollection(ArrayList::new)); + int rolloverHour = (scheduleHours.getLast() + periodHours) % HOURS_OF_DAY; + scheduleDates.add(dateAtTime.apply(offsetDateTime.plusDays(1), rolloverHour)); + + return scheduleDates.stream() + .filter(instant::isBefore) + .min(Comparator.comparing(scheduleDate -> Duration.between(instant, scheduleDate))) + .orElseThrow(); + } } /** diff --git a/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java index b330960475..c040eaf065 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java @@ -2,7 +2,6 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; @@ -21,6 +20,7 @@ import org.togetherjava.tjbot.features.UserInteractor; import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator; import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor; +import org.togetherjava.tjbot.features.utils.Guilds; import java.io.IOException; import java.io.InputStream; @@ -99,8 +99,7 @@ public void onMessageReceived(MessageReceivedEvent event) { public void onButtonClick(ButtonInteractionEvent event, List args) { Member interactionUser = event.getMember(); String gistAuthorId = args.getFirst(); - boolean hasSoftModPermissions = - interactionUser.getRoles().stream().map(Role::getName).anyMatch(isSoftModRole); + boolean hasSoftModPermissions = Guilds.hasMemberRole(interactionUser, isSoftModRole); if (!gistAuthorId.equals(interactionUser.getId()) && !hasSoftModPermissions) { event.reply("You do not have permission for this action.").setEphemeral(true).queue(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java index 222a8bfb66..dbb6ed55e2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpSystemHelper.java @@ -27,6 +27,7 @@ import org.togetherjava.tjbot.features.chatgpt.ChatGptCommand; import org.togetherjava.tjbot.features.chatgpt.ChatGptService; import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor; +import org.togetherjava.tjbot.features.utils.Guilds; import java.awt.Color; import java.time.Instant; @@ -57,7 +58,7 @@ public final class HelpSystemHelper { static final Color AMBIENT_COLOR = new Color(255, 255, 165); - private final Predicate hasTagManageRole; + private final Predicate isTagManageRole; private final Predicate isHelpForumName; private final String helpForumPattern; /** @@ -88,7 +89,7 @@ public HelpSystemHelper(Config config, Database database, ChatGptService chatGpt this.database = database; this.chatGptService = chatGptService; - hasTagManageRole = Pattern.compile(config.getTagManageRolePattern()).asMatchPredicate(); + isTagManageRole = Pattern.compile(config.getTagManageRolePattern()).asMatchPredicate(); helpForumPattern = helpConfig.getHelpForumPattern(); isHelpForumName = Pattern.compile(helpForumPattern).asMatchPredicate(); @@ -344,7 +345,7 @@ private static ForumTag requireTag(String tagName, ForumChannel forumChannel) { } boolean hasTagManageRole(Member member) { - return member.getRoles().stream().map(Role::getName).anyMatch(hasTagManageRole); + return Guilds.hasMemberRole(member, isTagManageRole); } boolean isHelpForumName(String channelName) { @@ -360,11 +361,7 @@ Optional handleRequireHelpForum(Guild guild, Predicate isChannelName = this::isHelpForumName; String channelPattern = getHelpForumPattern(); - Optional maybeChannel = guild.getForumChannelCache() - .stream() - .filter(channel -> isChannelName.test(channel.getName())) - .findAny(); - + Optional maybeChannel = Guilds.findForumChannel(guild, isChannelName); if (maybeChannel.isEmpty()) { consumeChannelPatternIfNotFound.accept(channelPattern); } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java index d6aba6dc68..5378b1a606 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/ModerationUtils.java @@ -18,6 +18,7 @@ import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.features.moderation.modmail.ModMailCommand; +import org.togetherjava.tjbot.features.utils.Guilds; import org.togetherjava.tjbot.features.utils.MessageUtils; import javax.annotation.Nullable; @@ -321,7 +322,7 @@ public static Predicate getIsMutedRolePredicate(Config config) { */ public static Optional getMutedRole(Guild guild, Config config) { Predicate isMutedRole = getIsMutedRolePredicate(config); - return guild.getRoles().stream().filter(role -> isMutedRole.test(role.getName())).findAny(); + return Guilds.findRole(guild, isMutedRole); } /** @@ -343,10 +344,7 @@ public static Predicate getIsQuarantinedRolePredicate(Config config) { */ public static Optional getQuarantinedRole(Guild guild, Config config) { Predicate isQuarantinedRole = getIsQuarantinedRolePredicate(config); - return guild.getRoles() - .stream() - .filter(role -> isQuarantinedRole.test(role.getName())) - .findAny(); + return Guilds.findRole(guild, isQuarantinedRole); } /** diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java index 6af6fa2248..603c1de578 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogRoutine.java @@ -28,24 +28,14 @@ import javax.annotation.Nullable; import java.awt.Color; -import java.time.Duration; import java.time.Instant; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAccessor; -import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; -import java.util.stream.Collectors; /** @@ -60,7 +50,6 @@ public final class ModAuditLogRoutine implements Routine { private static final Logger logger = LoggerFactory.getLogger(ModAuditLogRoutine.class); private static final int CHECK_AUDIT_LOG_START_HOUR = 4; private static final int CHECK_AUDIT_LOG_EVERY_HOURS = 8; - private static final int HOURS_OF_DAY = 24; private static final Color AMBIENT_COLOR = Color.decode("#4FC3F7"); private final Database database; @@ -95,74 +84,6 @@ private static boolean isSnowflakeAfter(ISnowflake snowflake, Instant timestamp) return TimeUtil.getTimeCreated(snowflake.getIdLong()).toInstant().isAfter(timestamp); } - /** - * Creates a schedule for execution at a fixed rate (see - * {@link ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, TimeUnit)}). The - * initial first execution will be delayed to the next fixed time that matches the given period, - * effectively making execution stable at fixed times of a day - regardless of when this method - * was originally triggered. - *

- * For example, if the given period is 8 hours with a start hour of 4 o'clock, this leads to the - * fixed execution times of 4:00, 12:00 and 20:00 each day. The first execution is then delayed - * to the closest time in that schedule. For example, if triggered at 7:00, execution will - * happen at 12:00 and then follow the schedule. - *

- * Execution will also correctly roll over to the next day, for example if the method is - * triggered at 21:30, the next execution will be at 4:00 the following day. - * - * @param periodStartHour the hour of the day that marks the start of this period - * @param periodHours the scheduling period in hours - * @return the according schedule representing the planned execution - */ - private static Schedule scheduleAtFixedRateFromNextFixedTime(int periodStartHour, - int periodHours) { - // NOTE This scheduler could be improved, for example supporting arbitrary periods (not just - // hour-based). Also, it probably does not correctly handle all date/time-quirks, for - // example if a schedule would hit a time that does not exist for a specific date due to DST - // or similar issues. Those are minor though and can be ignored for now. - if (periodHours <= 0 || periodHours >= HOURS_OF_DAY) { - throw new IllegalArgumentException( - "Schedule period must not be zero and must fit into a single day"); - } - if (periodStartHour <= 0 || periodStartHour >= HOURS_OF_DAY) { - throw new IllegalArgumentException( - "Schedule period start hour must be a valid hour of a day (0-23)"); - } - - // Compute fixed schedule hours - List fixedScheduleHours = new ArrayList<>(); - - for (int hour = periodStartHour; hour < HOURS_OF_DAY; hour += periodHours) { - fixedScheduleHours.add(hour); - } - - Instant now = Instant.now(); - Instant nextFixedTime = - computeClosestNextScheduleDate(now, fixedScheduleHours, periodHours); - return new Schedule(ScheduleMode.FIXED_RATE, ChronoUnit.SECONDS.between(now, nextFixedTime), - TimeUnit.HOURS.toSeconds(periodHours), TimeUnit.SECONDS); - } - - private static Instant computeClosestNextScheduleDate(Instant instant, - List scheduleHours, int periodHours) { - OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC); - BiFunction dateAtTime = - (date, hour) -> date.with(LocalTime.of(hour, 0)).toInstant(); - - // The instant is either before the given hours, in between, or after. - // For latter, we roll the schedule over once to the next day - List scheduleDates = scheduleHours.stream() - .map(hour -> dateAtTime.apply(offsetDateTime, hour)) - .collect(Collectors.toCollection(ArrayList::new)); - int rolloverHour = (scheduleHours.getLast() + periodHours) % HOURS_OF_DAY; - scheduleDates.add(dateAtTime.apply(offsetDateTime.plusDays(1), rolloverHour)); - - return scheduleDates.stream() - .filter(instant::isBefore) - .min(Comparator.comparing(scheduleDate -> Duration.between(instant, scheduleDate))) - .orElseThrow(); - } - private static Optional> handleBanEntry(AuditLogEntry entry) { // NOTE Temporary bans are realized as permanent bans with automated unban, // hence we can not differentiate a permanent or a temporary ban here @@ -205,7 +126,7 @@ public void runRoutine(JDA jda) { @Override public Schedule createSchedule() { - Schedule schedule = scheduleAtFixedRateFromNextFixedTime(CHECK_AUDIT_LOG_START_HOUR, + Schedule schedule = Schedule.atFixedRateFromNextFixedTime(CHECK_AUDIT_LOG_START_HOUR, CHECK_AUDIT_LOG_EVERY_HOURS); logger.info("Checking audit logs is scheduled for {}.", Instant.now().plus(schedule.initialDuration(), schedule.unit().toChronoUnit())); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogWriter.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogWriter.java index b458017732..b0aebee61b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogWriter.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/audit/ModAuditLogWriter.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.Config; +import org.togetherjava.tjbot.features.utils.Guilds; import javax.annotation.Nullable; @@ -34,7 +35,7 @@ public final class ModAuditLogWriter { private final Config config; - private final Predicate auditLogChannelNamePredicate; + private final Predicate isAuditLogChannelName; /** * Creates a new instance. @@ -43,7 +44,7 @@ public final class ModAuditLogWriter { */ public ModAuditLogWriter(Config config) { this.config = config; - auditLogChannelNamePredicate = + isAuditLogChannelName = Pattern.compile(config.getModAuditLogChannelPattern()).asMatchPredicate(); } @@ -90,11 +91,8 @@ public void write(String title, String description, @Nullable User author, * @return the channel used for moderation audit logs, if present */ public Optional getAndHandleModAuditLogChannel(Guild guild) { - Optional auditLogChannel = guild.getTextChannelCache() - .stream() - .filter(channel -> auditLogChannelNamePredicate.test(channel.getName())) - .findAny(); - + Optional auditLogChannel = + Guilds.findTextChannel(guild, isAuditLogChannelName); if (auditLogChannel.isEmpty()) { logger.warn( "Unable to log moderation events, did not find a mod audit log channel matching the configured pattern '{}' for guild '{}'", diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java index 730d7eef14..1c37fe8f3f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.SelfUser; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel; @@ -34,6 +33,7 @@ import org.togetherjava.tjbot.features.moderation.ModerationActionsStore; import org.togetherjava.tjbot.features.moderation.ModerationUtils; import org.togetherjava.tjbot.features.moderation.modmail.ModMailCommand; +import org.togetherjava.tjbot.features.utils.Guilds; import org.togetherjava.tjbot.features.utils.MessageUtils; import org.togetherjava.tjbot.logging.LogMarkers; @@ -41,6 +41,7 @@ import java.util.Collection; import java.util.EnumSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; @@ -65,13 +66,13 @@ public final class ScamBlocker extends MessageReceiverAdapter implements UserInt private final ScamBlockerConfig.Mode mode; private final String reportChannelPattern; private final String botTrapChannelPattern; - private final Predicate isReportChannel; + private final Predicate isReportChannelName; private final Predicate isBotTrapChannel; private final ScamDetector scamDetector; private final Config config; private final ModerationActionsStore actionsStore; private final ScamHistoryStore scamHistoryStore; - private final Predicate hasRequiredRole; + private final Predicate isRequiredRole; private final ComponentIdInteractor componentIdInteractor; @@ -91,16 +92,14 @@ public ScamBlocker(ModerationActionsStore actionsStore, ScamHistoryStore scamHis scamDetector = new ScamDetector(config); reportChannelPattern = config.getScamBlocker().getReportChannelPattern(); - Predicate isReportChannelName = - Pattern.compile(reportChannelPattern).asMatchPredicate(); - isReportChannel = channel -> isReportChannelName.test(channel.getName()); + isReportChannelName = Pattern.compile(reportChannelPattern).asMatchPredicate(); botTrapChannelPattern = config.getScamBlocker().getBotTrapChannelPattern(); Predicate isBotTrapChannelName = Pattern.compile(botTrapChannelPattern).asMatchPredicate(); isBotTrapChannel = channel -> isBotTrapChannelName.test(channel.getName()); - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); + isRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); componentIdInteractor = new ComponentIdInteractor(getInteractionType(), getName()); } @@ -296,7 +295,7 @@ If you think this was a mistake (for example, your account was hacked, but you g } private Optional getReportChannel(Guild guild) { - return guild.getTextChannelCache().stream().filter(isReportChannel).findAny(); + return Guilds.findTextChannel(guild, isReportChannelName); } private List