Skip to content

Commit b5b7c59

Browse files
authored
Adding help thread auto archiver (#540)
* added "handleRequireOverviewChannel" helper to reduce code duplication
1 parent 667b3ea commit b5b7c59

File tree

5 files changed

+135
-59
lines changed

5 files changed

+135
-59
lines changed

application/src/main/java/org/togetherjava/tjbot/commands/Features.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ private Features() {
8383
features.add(new HelpThreadActivityUpdater(helpSystemHelper));
8484
features
8585
.add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database));
86+
features.add(new HelpThreadAutoArchiver(helpSystemHelper));
8687

8788
// Message receivers
8889
features.add(new TopHelpersMessageListener(database, config));

application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.Locale;
2222
import java.util.Optional;
23+
import java.util.function.Consumer;
2324
import java.util.function.Predicate;
2425
import java.util.regex.Matcher;
2526
import java.util.regex.Pattern;
@@ -214,8 +215,8 @@ static boolean isTitleValid(@NotNull CharSequence title) {
214215
}
215216

216217
@NotNull
217-
Optional<TextChannel> handleRequireOverviewChannelForAsk(@NotNull Guild guild,
218-
@NotNull MessageChannel respondTo) {
218+
Optional<TextChannel> handleRequireOverviewChannel(@NotNull Guild guild,
219+
@NotNull Consumer<? super String> consumeChannelPatternIfNotFound) {
219220
Predicate<String> isChannelName = this::isOverviewChannelName;
220221
String channelPattern = getOverviewChannelPattern();
221222

@@ -225,17 +226,32 @@ Optional<TextChannel> handleRequireOverviewChannelForAsk(@NotNull Guild guild,
225226
.findAny();
226227

227228
if (maybeChannel.isEmpty()) {
229+
consumeChannelPatternIfNotFound.accept(channelPattern);
230+
}
231+
232+
return maybeChannel;
233+
}
234+
235+
@NotNull
236+
Optional<TextChannel> handleRequireOverviewChannelForAsk(@NotNull Guild guild,
237+
@NotNull MessageChannel respondTo) {
238+
return handleRequireOverviewChannel(guild, channelPattern -> {
228239
logger.warn(
229240
"Attempted to create a help thread, did not find the overview channel matching the configured pattern '{}' for guild '{}'",
230241
channelPattern, guild.getName());
231242

232243
respondTo.sendMessage(
233244
"Sorry, I was unable to locate the overview channel. The server seems wrongly configured, please contact a moderator.")
234245
.queue();
235-
return Optional.empty();
236-
}
246+
});
247+
}
237248

238-
return maybeChannel;
249+
@NotNull
250+
List<ThreadChannel> getActiveThreadsIn(@NotNull TextChannel channel) {
251+
return channel.getThreadChannels()
252+
.stream()
253+
.filter(Predicate.not(ThreadChannel::isArchived))
254+
.toList();
239255
}
240256

241257
record HelpThreadName(@Nullable ThreadActivity activity, @Nullable String category,

application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,44 +48,24 @@ public void runRoutine(@NotNull JDA jda) {
4848
}
4949

5050
private void updateActivityForGuild(@NotNull Guild guild) {
51-
Optional<TextChannel> maybeOverviewChannel = handleRequireOverviewChannel(guild);
51+
Optional<TextChannel> maybeOverviewChannel = helper
52+
.handleRequireOverviewChannel(guild, channelPattern -> logger.warn(
53+
"Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'",
54+
channelPattern, guild.getName()));
5255

5356
if (maybeOverviewChannel.isEmpty()) {
5457
return;
5558
}
5659

5760
logger.debug("Updating activities of active questions");
5861

59-
List<ThreadChannel> activeThreads = maybeOverviewChannel.orElseThrow()
60-
.getThreadChannels()
61-
.stream()
62-
.filter(Predicate.not(ThreadChannel::isArchived))
63-
.toList();
64-
62+
List<ThreadChannel> activeThreads =
63+
helper.getActiveThreadsIn(maybeOverviewChannel.orElseThrow());
6564
logger.debug("Found {} active questions", activeThreads.size());
6665

6766
activeThreads.forEach(this::updateActivityForThread);
6867
}
6968

70-
private @NotNull Optional<TextChannel> handleRequireOverviewChannel(@NotNull Guild guild) {
71-
Predicate<String> isChannelName = helper::isOverviewChannelName;
72-
String channelPattern = helper.getOverviewChannelPattern();
73-
74-
Optional<TextChannel> maybeChannel = guild.getTextChannelCache()
75-
.stream()
76-
.filter(channel -> isChannelName.test(channel.getName()))
77-
.findAny();
78-
79-
if (maybeChannel.isEmpty()) {
80-
logger.warn(
81-
"Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'",
82-
channelPattern, guild.getName());
83-
return Optional.empty();
84-
}
85-
86-
return maybeChannel;
87-
}
88-
8969
private void updateActivityForThread(@NotNull ThreadChannel threadChannel) {
9070
determineActivity(threadChannel)
9171
.flatMap(
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.togetherjava.tjbot.commands.help;
2+
3+
import net.dv8tion.jda.api.EmbedBuilder;
4+
import net.dv8tion.jda.api.JDA;
5+
import net.dv8tion.jda.api.entities.*;
6+
import net.dv8tion.jda.api.utils.TimeUtil;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
import org.togetherjava.tjbot.commands.Routine;
11+
12+
import java.time.Duration;
13+
import java.time.Instant;
14+
import java.util.List;
15+
import java.util.Optional;
16+
import java.util.concurrent.TimeUnit;
17+
18+
/**
19+
* Routine, which periodically checks all help threads and archives them if there has not been any
20+
* recent activity.
21+
*/
22+
public final class HelpThreadAutoArchiver implements Routine {
23+
private static final Logger logger = LoggerFactory.getLogger(HelpThreadAutoArchiver.class);
24+
private static final int SCHEDULE_MINUTES = 60;
25+
private static final Duration ARCHIVE_AFTER_INACTIVITY_OF = Duration.ofHours(24);
26+
27+
private final HelpSystemHelper helper;
28+
29+
/**
30+
* Creates a new instance.
31+
*
32+
* @param helper the helper to use
33+
*/
34+
public HelpThreadAutoArchiver(@NotNull HelpSystemHelper helper) {
35+
this.helper = helper;
36+
}
37+
38+
@Override
39+
public @NotNull Schedule createSchedule() {
40+
return new Schedule(ScheduleMode.FIXED_RATE, 0, SCHEDULE_MINUTES, TimeUnit.MINUTES);
41+
}
42+
43+
@Override
44+
public void runRoutine(@NotNull JDA jda) {
45+
jda.getGuildCache().forEach(this::autoArchiveForGuild);
46+
}
47+
48+
private void autoArchiveForGuild(@NotNull Guild guild) {
49+
Optional<TextChannel> maybeOverviewChannel = helper
50+
.handleRequireOverviewChannel(guild, channelPattern -> logger.warn(
51+
"Unable to auto archive help threads, did not find an overview channel matching the configured pattern '{}' for guild '{}'",
52+
channelPattern, guild.getName()));
53+
54+
if (maybeOverviewChannel.isEmpty()) {
55+
return;
56+
}
57+
58+
logger.debug("Auto archiving of help threads");
59+
60+
List<ThreadChannel> activeThreads =
61+
helper.getActiveThreadsIn(maybeOverviewChannel.orElseThrow());
62+
logger.debug("Found {} active questions", activeThreads.size());
63+
64+
Instant archiveAfterMoment = computeArchiveAfterMoment();
65+
activeThreads
66+
.forEach(activeThread -> autoArchiveForThread(activeThread, archiveAfterMoment));
67+
}
68+
69+
private @NotNull Instant computeArchiveAfterMoment() {
70+
return Instant.now().minus(ARCHIVE_AFTER_INACTIVITY_OF);
71+
}
72+
73+
private void autoArchiveForThread(@NotNull ThreadChannel threadChannel,
74+
@NotNull Instant archiveAfterMoment) {
75+
if (shouldBeArchived(threadChannel, archiveAfterMoment)) {
76+
logger.debug("Auto archiving help thread {}", threadChannel.getId());
77+
78+
MessageEmbed embed = new EmbedBuilder().setDescription(
79+
"""
80+
Closed the thread due to inactivity.
81+
82+
If your question was not resolved yet, feel free to create a new thread. \
83+
But try to improve the quality of your question to make it easier to help you 👍""")
84+
.setColor(HelpSystemHelper.AMBIENT_COLOR)
85+
.build();
86+
87+
threadChannel.sendMessageEmbeds(embed)
88+
.flatMap(any -> threadChannel.getManager().setArchived(true))
89+
.queue();
90+
}
91+
}
92+
93+
private static boolean shouldBeArchived(MessageChannel channel,
94+
@NotNull Instant archiveAfterMoment) {
95+
Instant lastActivity =
96+
TimeUtil.getTimeCreated(channel.getLatestMessageIdLong()).toInstant();
97+
98+
return lastActivity.isBefore(archiveAfterMoment);
99+
}
100+
}

application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,15 @@
1919
import java.util.concurrent.ScheduledExecutorService;
2020
import java.util.concurrent.TimeUnit;
2121
import java.util.concurrent.atomic.AtomicInteger;
22-
import java.util.function.Predicate;
2322
import java.util.regex.Pattern;
2423
import java.util.stream.Collectors;
2524

2625
/**
2726
* Provides and updates an overview of all active questions in an overview channel.
28-
*
27+
* <p>
2928
* The process runs on a schedule, but is also triggered whenever a new question has been asked in
3029
* the staging channel.
31-
*
30+
* <p>
3231
* Active questions are automatically picked up and grouped by categories.
3332
*/
3433
public final class HelpThreadOverviewUpdater extends MessageReceiverAdapter implements Routine {
@@ -96,7 +95,10 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
9695
}
9796

9897
private void updateOverviewForGuild(@NotNull Guild guild) {
99-
Optional<TextChannel> maybeOverviewChannel = handleRequireOverviewChannel(guild);
98+
Optional<TextChannel> maybeOverviewChannel = helper
99+
.handleRequireOverviewChannel(guild, channelPattern -> logger.warn(
100+
"Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'",
101+
channelPattern, guild.getName()));
100102

101103
if (maybeOverviewChannel.isEmpty()) {
102104
return;
@@ -105,33 +107,10 @@ private void updateOverviewForGuild(@NotNull Guild guild) {
105107
updateOverview(maybeOverviewChannel.orElseThrow());
106108
}
107109

108-
private @NotNull Optional<TextChannel> handleRequireOverviewChannel(@NotNull Guild guild) {
109-
Predicate<String> isChannelName = helper::isOverviewChannelName;
110-
String channelPattern = helper.getOverviewChannelPattern();
111-
112-
Optional<TextChannel> maybeChannel = guild.getTextChannelCache()
113-
.stream()
114-
.filter(channel -> isChannelName.test(channel.getName()))
115-
.findAny();
116-
117-
if (maybeChannel.isEmpty()) {
118-
logger.warn(
119-
"Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'",
120-
channelPattern, guild.getName());
121-
return Optional.empty();
122-
}
123-
124-
return maybeChannel;
125-
}
126-
127110
private void updateOverview(@NotNull TextChannel overviewChannel) {
128111
logger.debug("Updating overview of active questions");
129112

130-
List<ThreadChannel> activeThreads = overviewChannel.getThreadChannels()
131-
.stream()
132-
.filter(Predicate.not(ThreadChannel::isArchived))
133-
.toList();
134-
113+
List<ThreadChannel> activeThreads = helper.getActiveThreadsIn(overviewChannel);
135114
logger.debug("Found {} active questions", activeThreads.size());
136115

137116
Message message = new MessageBuilder()

0 commit comments

Comments
 (0)