From a11088e7e5aa65b519edb3ee966d14164223935c Mon Sep 17 00:00:00 2001 From: Daniel Tischner Date: Mon, 25 Mar 2024 09:18:45 +0100 Subject: [PATCH] Removed now obsolete HelperPrune routine (config change) (#1076) * the original issue was fixed by discord, so the routine is not needed anymore * changes the config, removes entry "helperPruneConfig" --- application/config.json.template | 7 - .../org/togetherjava/tjbot/config/Config.java | 13 -- .../tjbot/config/HelperPruneConfig.java | 19 -- .../togetherjava/tjbot/features/Features.java | 2 - .../features/help/AutoPruneHelperRoutine.java | 191 ------------------ 5 files changed, 232 deletions(-) delete mode 100644 application/src/main/java/org/togetherjava/tjbot/config/HelperPruneConfig.java delete mode 100644 application/src/main/java/org/togetherjava/tjbot/features/help/AutoPruneHelperRoutine.java diff --git a/application/config.json.template b/application/config.json.template index 7958745d39..b584f10f6a 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -97,13 +97,6 @@ "rateLimitWindowSeconds": 10, "rateLimitRequestsInWindow": 3 }, - "helperPruneConfig": { - "roleFullLimit": 100, - "roleFullThreshold": 95, - "pruneMemberAmount": 7, - "inactivateAfterDays": 90, - "recentlyJoinedDays": 4 - }, "featureBlacklist": { "normal": [ ], 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 7d809b6c9a..d14c6279d0 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -41,7 +41,6 @@ public final class Config { private final String openaiApiKey; private final String sourceCodeBaseUrl; private final JShellConfig jshell; - private final HelperPruneConfig helperPruneConfig; private final FeatureBlacklistConfig featureBlacklistConfig; private final String selectRolesChannelPattern; private final String memberCountCategoryPattern; @@ -89,8 +88,6 @@ private Config(@JsonProperty(value = "token", required = true) String token, @JsonProperty(value = "jshell", required = true) JShellConfig jshell, @JsonProperty(value = "memberCountCategoryPattern", required = true) String memberCountCategoryPattern, - @JsonProperty(value = "helperPruneConfig", - required = true) HelperPruneConfig helperPruneConfig, @JsonProperty(value = "featureBlacklist", required = true) FeatureBlacklistConfig featureBlacklistConfig, @JsonProperty(value = "selectRolesChannelPattern", @@ -124,7 +121,6 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.openaiApiKey = Objects.requireNonNull(openaiApiKey); this.sourceCodeBaseUrl = Objects.requireNonNull(sourceCodeBaseUrl); this.jshell = Objects.requireNonNull(jshell); - this.helperPruneConfig = Objects.requireNonNull(helperPruneConfig); this.featureBlacklistConfig = Objects.requireNonNull(featureBlacklistConfig); this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern); } @@ -382,15 +378,6 @@ public JShellConfig getJshell() { return jshell; } - /** - * Gets the config for automatic pruning of helper roles. - * - * @return the configuration - */ - public HelperPruneConfig getHelperPruneConfig() { - return helperPruneConfig; - } - /** * The configuration of blacklisted features. * diff --git a/application/src/main/java/org/togetherjava/tjbot/config/HelperPruneConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/HelperPruneConfig.java deleted file mode 100644 index 6f451b491f..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/config/HelperPruneConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.togetherjava.tjbot.config; - - -/** - * Config for automatic pruning of helper roles, see - * {@link org.togetherjava.tjbot.features.help.AutoPruneHelperRoutine}. - * - * @param roleFullLimit if a helper role contains that many users, it is considered full and pruning - * must occur - * @param roleFullThreshold if a helper role contains that many users, pruning will start to occur - * to prevent reaching the limit - * @param pruneMemberAmount amount of users to remove from helper roles during a prune - * @param inactivateAfterDays after how many days of inactivity a user is eligible for pruning - * @param recentlyJoinedDays if a user is with the server for just this amount of days, they are - * protected from pruning - */ -public record HelperPruneConfig(int roleFullLimit, int roleFullThreshold, int pruneMemberAmount, - int inactivateAfterDays, int recentlyJoinedDays) { -} 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 386bad158b..ea6b908b90 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -106,8 +106,6 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new ScamHistoryPurgeRoutine(scamHistoryStore)); features.add(new HelpThreadMetadataPurger(database)); features.add(new HelpThreadActivityUpdater(helpSystemHelper)); - features - .add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database)); features.add(new HelpThreadAutoArchiver(helpSystemHelper)); features.add(new LeftoverBookmarksCleanupRoutine(bookmarksSystem)); features.add(new MemberCountDisplayRoutine(config)); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/AutoPruneHelperRoutine.java b/application/src/main/java/org/togetherjava/tjbot/features/help/AutoPruneHelperRoutine.java deleted file mode 100644 index e32547e4f8..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/AutoPruneHelperRoutine.java +++ /dev/null @@ -1,191 +0,0 @@ -package org.togetherjava.tjbot.features.help; - -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.togetherjava.tjbot.config.Config; -import org.togetherjava.tjbot.config.HelperPruneConfig; -import org.togetherjava.tjbot.db.Database; -import org.togetherjava.tjbot.features.Routine; -import org.togetherjava.tjbot.features.moderation.audit.ModAuditLogWriter; - -import javax.annotation.Nullable; - -import java.time.Duration; -import java.time.Instant; -import java.time.Period; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import static org.togetherjava.tjbot.db.generated.tables.HelpChannelMessages.HELP_CHANNEL_MESSAGES; - -/** - * Due to a technical limitation in Discord, roles with more than 100 users can not be ghost-pinged - * into helper threads. - *

- * This routine mitigates the problem by automatically pruning inactive users from helper roles - * approaching this limit. - */ -public final class AutoPruneHelperRoutine implements Routine { - private static final Logger logger = LoggerFactory.getLogger(AutoPruneHelperRoutine.class); - - private final int roleFullLimit; - private final int roleFullThreshold; - private final int pruneMemberAmount; - private final Period inactiveAfter; - private final int recentlyJoinedDays; - - private final HelpSystemHelper helper; - private final ModAuditLogWriter modAuditLogWriter; - private final Database database; - private final List allCategories; - private final Predicate selectYourRolesChannelNamePredicate; - - /** - * Creates a new instance. - * - * @param config to determine all helper categories - * @param helper the helper to use - * @param modAuditLogWriter to inform mods when manual pruning becomes necessary - * @param database to determine whether an user is inactive - */ - public AutoPruneHelperRoutine(Config config, HelpSystemHelper helper, - ModAuditLogWriter modAuditLogWriter, Database database) { - allCategories = config.getHelpSystem().getCategories(); - this.helper = helper; - this.modAuditLogWriter = modAuditLogWriter; - this.database = database; - - HelperPruneConfig helperPruneConfig = config.getHelperPruneConfig(); - roleFullLimit = helperPruneConfig.roleFullLimit(); - roleFullThreshold = helperPruneConfig.roleFullThreshold(); - pruneMemberAmount = helperPruneConfig.pruneMemberAmount(); - inactiveAfter = Period.ofDays(helperPruneConfig.inactivateAfterDays()); - recentlyJoinedDays = helperPruneConfig.recentlyJoinedDays(); - selectYourRolesChannelNamePredicate = - Pattern.compile(config.getSelectRolesChannelPattern()).asMatchPredicate(); - } - - @Override - public Schedule createSchedule() { - return new Schedule(ScheduleMode.FIXED_RATE, 0, 1, TimeUnit.HOURS); - } - - @Override - public void runRoutine(JDA jda) { - jda.getGuildCache().forEach(this::pruneForGuild); - } - - private void pruneForGuild(Guild guild) { - Instant now = Instant.now(); - TextChannel selectRoleChannel = getSelectRolesChannelOptional(guild.getJDA()).orElse(null); - - allCategories.stream() - .map(category -> helper.handleFindRoleForCategory(category, guild)) - .filter(Optional::isPresent) - .map(Optional::orElseThrow) - .forEach(role -> pruneRoleIfFull(role, selectRoleChannel, now)); - } - - private void pruneRoleIfFull(Role role, @Nullable TextChannel selectRoleChannel, Instant when) { - role.getGuild().findMembersWithRoles(role).onSuccess(members -> { - if (isRoleFull(members)) { - logger.debug("Helper role {} is full, starting to prune.", role.getName()); - pruneRole(role, members, selectRoleChannel, when); - } - }); - } - - private boolean isRoleFull(Collection members) { - return members.size() >= roleFullThreshold; - } - - private void pruneRole(Role role, List members, - @Nullable TextChannel selectRoleChannel, Instant when) { - List membersShuffled = new ArrayList<>(members); - Collections.shuffle(membersShuffled); - - List membersToPrune = membersShuffled.stream() - .filter(member -> isMemberInactive(member, when)) - .limit(pruneMemberAmount) - .toList(); - if (membersToPrune.size() < pruneMemberAmount) { - warnModsAbout( - "Attempting to prune helpers from role **%s** (%d members), but only found %d inactive users. That is less than expected, the category might eventually grow beyond the limit." - .formatted(role.getName(), members.size(), membersToPrune.size()), - role.getGuild()); - } - if (members.size() - membersToPrune.size() >= roleFullLimit) { - warnModsAbout( - "The helper role **%s** went beyond its member limit (%d), despite automatic pruning. It will not function correctly anymore. Please manually prune some users." - .formatted(role.getName(), roleFullLimit), - role.getGuild()); - } - - logger.info("Pruning {} users {} from role {}", membersToPrune.size(), membersToPrune, - role.getName()); - membersToPrune.forEach(member -> pruneMemberFromRole(member, role, selectRoleChannel)); - } - - private boolean isMemberInactive(Member member, Instant when) { - if (member.hasTimeJoined()) { - Instant memberJoined = member.getTimeJoined().toInstant(); - if (Duration.between(memberJoined, when).toDays() <= recentlyJoinedDays) { - // New users are protected from purging to not immediately kick them out of the role - // again - return false; - } - } - - Instant latestActiveMoment = when.minus(inactiveAfter); - - // Has no recent help message - return database.read(context -> context.fetchCount(HELP_CHANNEL_MESSAGES, - HELP_CHANNEL_MESSAGES.GUILD_ID.eq(member.getGuild().getIdLong()) - .and(HELP_CHANNEL_MESSAGES.AUTHOR_ID.eq(member.getIdLong())) - .and(HELP_CHANNEL_MESSAGES.SENT_AT.greaterThan(latestActiveMoment)))) == 0; - } - - private void pruneMemberFromRole(Member member, Role role, - @Nullable TextChannel selectRoleChannel) { - Guild guild = member.getGuild(); - - String channelMentionOrFallbackMessage = - selectRoleChannel == null ? "role selection channel" - : selectRoleChannel.getAsMention(); - - String dmMessage = - """ - You seem to have been inactive for some time in server **%s**, hence we removed you from the **%s** role. - If that was a mistake, just head back to %s and select the role again. - Sorry for any inconvenience caused by this 🙇""" - .formatted(guild.getName(), role.getName(), channelMentionOrFallbackMessage); - - guild.removeRoleFromMember(member, role) - .flatMap(any -> member.getUser().openPrivateChannel()) - .flatMap(channel -> channel.sendMessage(dmMessage)) - .queue(null, failure -> logger.debug( - "Failed sending a DM to user ({}) while pruning them from a helper role.", - member.getId())); - } - - private void warnModsAbout(String message, Guild guild) { - logger.warn(message); - - modAuditLogWriter.write("Auto-prune helpers", message, null, Instant.now(), guild); - } - - private Optional getSelectRolesChannelOptional(JDA jda) { - return jda.getTextChannels() - .stream() - .filter(textChannel -> selectYourRolesChannelNamePredicate.test(textChannel.getName())) - .findFirst(); - } -}