From c9e3a09de7a2b1f4c3e84506a11abf9932249c23 Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Mon, 2 Mar 2026 02:10:24 +0800 Subject: [PATCH 1/9] feat(help): add manual reactivation button for inactive threads --- .../features/help/HelpThreadAutoArchiver.java | 54 +++++++++++-------- .../help/HelpThreadCreatedListener.java | 28 +++++++++- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 41792957ff..d7d2512484 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -8,6 +8,7 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.TimeUtil; import org.slf4j.Logger; @@ -128,41 +129,48 @@ private void handleArchiveFlow(ThreadChannel threadChannel, MessageEmbed embed) () -> triggerAuthorIdNotFoundArchiveFlow(threadChannel, embed)); } - private void triggerArchiveFlow(ThreadChannel threadChannel, long authorId, - MessageEmbed embed) { + private void triggerArchiveFlow(ThreadChannel threadChannel, long authorId, MessageEmbed embed) { + // --- UPDATED: Added ActionRow with custom namespace ID --- Function> sendEmbedWithMention = - member -> threadChannel.sendMessage(member.getAsMention()).addEmbeds(embed); + member -> threadChannel.sendMessage(member.getAsMention()) + .addEmbeds(embed) + .addActionRow(Button.primary("OTHER:thread-inactivity:mark-active", "Mark Active")); Supplier> sendEmbedWithoutMention = - () -> threadChannel.sendMessageEmbeds(embed); + () -> threadChannel.sendMessageEmbeds(embed) + .addActionRow(Button.primary("OTHER:thread-inactivity:mark-active", "Mark Active")); + // --------------------------------------------------------- threadChannel.getGuild() - .retrieveMemberById(authorId) - .mapToResult() - .flatMap(authorResults -> { - if (authorResults.isFailure()) { - logger.info( - "Trying to archive a thread ({}), but OP ({}) left the server, sending embed without mention", - threadChannel.getId(), authorId, authorResults.getFailure()); - - return sendEmbedWithoutMention.get(); - } - - return sendEmbedWithMention.apply(authorResults.get()); - }) - .flatMap(_ -> threadChannel.getManager().setArchived(true)) - .queue(); + .retrieveMemberById(authorId) + .mapToResult() + .flatMap(authorResults -> { + if (authorResults.isFailure()) { + logger.info( + "Trying to archive a thread ({}), but OP ({}) left the server, sending embed without mention", + threadChannel.getId(), authorId, authorResults.getFailure()); + + return sendEmbedWithoutMention.get(); + } + + return sendEmbedWithMention.apply(authorResults.get()); + }) + .flatMap(_ -> threadChannel.getManager().setArchived(true)) + .queue(); } - private void triggerAuthorIdNotFoundArchiveFlow(ThreadChannel threadChannel, - MessageEmbed embed) { + private void triggerAuthorIdNotFoundArchiveFlow(ThreadChannel threadChannel, MessageEmbed embed) { logger.info( "Was unable to find a matching thread for id: {} in DB, archiving thread without mentioning OP", threadChannel.getId()); + + // --- UPDATED: Added ActionRow with custom namespace ID --- threadChannel.sendMessageEmbeds(embed) - .flatMap(sentEmbed -> threadChannel.getManager().setArchived(true)) - .queue(); + .addActionRow(Button.primary("OTHER:thread-inactivity:mark-active", "Mark Active")) + .flatMap(sentEmbed -> threadChannel.getManager().setArchived(true)) + .queue(); + // --------------------------------------------- } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java index bbf8490a2c..e012626419 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java @@ -47,6 +47,10 @@ public final class HelpThreadCreatedListener extends ListenerAdapter private static final Logger log = LoggerFactory.getLogger(HelpThreadCreatedListener.class); private final HelpSystemHelper helper; + private static final String MARK_ACTIVE_ID = "mark-active"; + private final ComponentIdInteractor inactivityInteractor = + new ComponentIdInteractor(UserInteractionType.OTHER, "thread-inactivity"); + private final Cache threadIdToCreatedAtCache = Caffeine.newBuilder() .maximumSize(1_000) .expireAfterAccess(2, TimeUnit.of(ChronoUnit.MINUTES)) @@ -187,7 +191,16 @@ private Consumer handleParentMessageDeleted(Member user, ThreadChanne @Override public void onButtonClick(ButtonInteractionEvent event, List args) { - // This method handles chatgpt's automatic response "dismiss" button + // Check if the button belongs to the "thread-inactivity" namespace + if (inactivityInteractor.isMatch(event.getComponentId())) { + String subId = inactivityInteractor.getComponentId(event.getComponentId()); + + if (subId.equals("mark-active")) { + handleMarkActiveInteraction(event); + return; // EXIT: This logic is owned by handleMarkActiveInteraction + } + } + // Handle chatgpt's automatic response "dismiss" button event.deferEdit().queue(); ThreadChannel channel = event.getChannel().asThreadChannel(); @@ -248,4 +261,17 @@ private void registerThreadDataInDB(Message message, ThreadChannel threadChannel helper.writeHelpThreadToDatabase(authorId, threadChannel); } + + private void handleMarkActiveInteraction(ButtonInteractionEvent event) { + event.deferEdit().queue(); + + if (event.getChannel() instanceof ThreadChannel thread) { + // Reopen the thread so people can chat, then delete the bot's warning + thread.getManager().setArchived(false).queue(); + event.getMessage().delete().queue(); + + log.info("Thread {} was manually reactivated via button by {}", + thread.getId(), event.getUser().getName()); + } + } } From 18ef76ae3a8da37857f3dbe459491844b37fe667 Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Mon, 2 Mar 2026 02:15:12 +0800 Subject: [PATCH 2/9] feat(help): add manual reactivation button for inactive threads --- .../tjbot/features/help/HelpThreadAutoArchiver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index d7d2512484..74f657829f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -89,8 +89,8 @@ private void autoArchiveForThread(ThreadChannel threadChannel) { """ Your question has been closed due to inactivity. - If it was not resolved yet, feel free to just post a message below - to reopen it, or create a new thread. + If it was not resolved yet, **click the button below** to keep it + open, or feel free to create a new thread. Note that usually the reason for nobody calling back is that your question may have been not well asked and hence no one felt confident From edb7af3e309a0c40f33defde50542aa76398ce83 Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Mon, 2 Mar 2026 16:39:43 +0800 Subject: [PATCH 3/9] refactor(help): consolidate interactor and cleanup reactivation logic --- .../features/help/HelpThreadAutoArchiver.java | 10 ++---- .../help/HelpThreadCreatedListener.java | 35 ++++++++----------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 74f657829f..03f0130e34 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -131,16 +131,14 @@ private void handleArchiveFlow(ThreadChannel threadChannel, MessageEmbed embed) private void triggerArchiveFlow(ThreadChannel threadChannel, long authorId, MessageEmbed embed) { - // --- UPDATED: Added ActionRow with custom namespace ID --- Function> sendEmbedWithMention = member -> threadChannel.sendMessage(member.getAsMention()) .addEmbeds(embed) - .addActionRow(Button.primary("OTHER:thread-inactivity:mark-active", "Mark Active")); + .addActionRow(Button.primary("OTHER:chatpgt-answer:mark-active", "Mark Active")); Supplier> sendEmbedWithoutMention = () -> threadChannel.sendMessageEmbeds(embed) - .addActionRow(Button.primary("OTHER:thread-inactivity:mark-active", "Mark Active")); - // --------------------------------------------------------- + .addActionRow(Button.primary("OTHER:chatpgt-answer:mark-active", "Mark Active")); threadChannel.getGuild() .retrieveMemberById(authorId) @@ -166,11 +164,9 @@ private void triggerAuthorIdNotFoundArchiveFlow(ThreadChannel threadChannel, Mes "Was unable to find a matching thread for id: {} in DB, archiving thread without mentioning OP", threadChannel.getId()); - // --- UPDATED: Added ActionRow with custom namespace ID --- threadChannel.sendMessageEmbeds(embed) - .addActionRow(Button.primary("OTHER:thread-inactivity:mark-active", "Mark Active")) + .addActionRow(Button.primary("OTHER:chatpgt-answer:mark-active", "Mark Active")) .flatMap(sentEmbed -> threadChannel.getManager().setArchived(true)) .queue(); - // --------------------------------------------- } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java index e012626419..c822d6a46c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java @@ -47,10 +47,6 @@ public final class HelpThreadCreatedListener extends ListenerAdapter private static final Logger log = LoggerFactory.getLogger(HelpThreadCreatedListener.class); private final HelpSystemHelper helper; - private static final String MARK_ACTIVE_ID = "mark-active"; - private final ComponentIdInteractor inactivityInteractor = - new ComponentIdInteractor(UserInteractionType.OTHER, "thread-inactivity"); - private final Cache threadIdToCreatedAtCache = Caffeine.newBuilder() .maximumSize(1_000) .expireAfterAccess(2, TimeUnit.of(ChronoUnit.MINUTES)) @@ -191,16 +187,14 @@ private Consumer handleParentMessageDeleted(Member user, ThreadChanne @Override public void onButtonClick(ButtonInteractionEvent event, List args) { - // Check if the button belongs to the "thread-inactivity" namespace - if (inactivityInteractor.isMatch(event.getComponentId())) { - String subId = inactivityInteractor.getComponentId(event.getComponentId()); - - if (subId.equals("mark-active")) { - handleMarkActiveInteraction(event); - return; // EXIT: This logic is owned by handleMarkActiveInteraction - } + if (args.contains("mark-active")) { + onInactivityButton(event); + } else { + onAiHelpDismissButton(event, args); } - // Handle chatgpt's automatic response "dismiss" button + } + + private void onAiHelpDismissButton(ButtonInteractionEvent event, List args) { event.deferEdit().queue(); ThreadChannel channel = event.getChannel().asThreadChannel(); @@ -210,7 +204,6 @@ public void onButtonClick(ButtonInteractionEvent event, List args) { .queue(forumPostMessage -> handleDismiss(interactionUser, channel, forumPostMessage, event, args), handleParentMessageDeleted(interactionUser, channel, event, args)); - } private boolean isPostAuthor(Member interactionUser, Message message) { @@ -262,16 +255,18 @@ private void registerThreadDataInDB(Message message, ThreadChannel threadChannel helper.writeHelpThreadToDatabase(authorId, threadChannel); } - private void handleMarkActiveInteraction(ButtonInteractionEvent event) { + private void onInactivityButton(ButtonInteractionEvent event) { event.deferEdit().queue(); if (event.getChannel() instanceof ThreadChannel thread) { - // Reopen the thread so people can chat, then delete the bot's warning - thread.getManager().setArchived(false).queue(); - event.getMessage().delete().queue(); + Message botClosedThreadMessage = event.getMessage(); + + thread.getManager().setArchived(false) + .flatMap(v -> botClosedThreadMessage.delete()) + .queue(); - log.info("Thread {} was manually reactivated via button by {}", - thread.getId(), event.getUser().getName()); + log.debug("Thread {} was manually reactivated via button by {}", + thread.getId(), event.getUser().getId()); } } } From 9dfc140ccbb8809fffe3ce46572bb2c961a3211f Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Mon, 2 Mar 2026 17:08:08 +0800 Subject: [PATCH 4/9] refactor(help): share ComponentIdInteractor between listener and archiver --- .../togetherjava/tjbot/features/Features.java | 7 ++- .../tjbot/features/help/HelpSystemHelper.java | 11 ++++ .../features/help/HelpThreadAutoArchiver.java | 60 +++++++++++-------- .../help/HelpThreadCreatedListener.java | 21 +++++-- 4 files changed, 68 insertions(+), 31 deletions(-) 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 6febd433b6..37461b81cb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -144,7 +144,10 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new HelpThreadActivityUpdater(helpSystemHelper)); features .add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database)); - features.add(new HelpThreadAutoArchiver(helpSystemHelper)); + HelpThreadCreatedListener helpThreadCreatedListener = + new HelpThreadCreatedListener(helpSystemHelper); + features.add(new HelpThreadAutoArchiver(helpSystemHelper, + helpThreadCreatedListener.getComponentIdInteractor())); features.add(new LeftoverBookmarksCleanupRoutine(bookmarksSystem)); features.add(new MarkHelpThreadCloseInDBRoutine(database, helpThreadLifecycleListener)); features.add(new MemberCountDisplayRoutine(config)); @@ -173,7 +176,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new RejoinModerationRoleListener(actionsStore, config)); features.add(new GuildLeaveCloseThreadListener(config)); features.add(new LeftoverBookmarksListener(bookmarksSystem)); - features.add(new HelpThreadCreatedListener(helpSystemHelper)); + features.add(helpThreadCreatedListener); features.add(new HelpThreadLifecycleListener(helpSystemHelper, database)); features.add(new ProjectsThreadCreatedListener(config)); 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 edf217f1ea..516fd07945 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 @@ -419,6 +419,17 @@ Optional getAuthorByHelpThreadId(final long channelId) { } + /** + * Generates a component ID for the "Mark Active" button, derived from the given interactor's + * namespace. + * + * @param interactor the component ID interactor whose namespace should be used + * @return a valid component ID string for the mark-active button + */ + public String generateMarkActiveId(ComponentIdInteractor interactor) { + return interactor.generateComponentId("mark-active"); + } + /** * will be used to filter a tag based on categories config * diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 03f0130e34..6a3d0340a2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -15,6 +15,7 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.features.Routine; +import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor; import java.time.Duration; import java.time.Instant; @@ -32,16 +33,21 @@ public final class HelpThreadAutoArchiver implements Routine { private static final Logger logger = LoggerFactory.getLogger(HelpThreadAutoArchiver.class); private static final int SCHEDULE_MINUTES = 60; private static final Duration ARCHIVE_AFTER_INACTIVITY_OF = Duration.ofHours(12); + private static final String MARK_ACTIVE_LABEL = "Mark Active"; private final HelpSystemHelper helper; + private final ComponentIdInteractor componentIdInteractor; /** * Creates a new instance. * * @param helper the helper to use + * @param componentIdInteractor the interactor used to generate component IDs for buttons */ - public HelpThreadAutoArchiver(HelpSystemHelper helper) { + public HelpThreadAutoArchiver(HelpSystemHelper helper, + ComponentIdInteractor componentIdInteractor) { this.helper = helper; + this.componentIdInteractor = componentIdInteractor; } @Override @@ -129,44 +135,50 @@ private void handleArchiveFlow(ThreadChannel threadChannel, MessageEmbed embed) () -> triggerAuthorIdNotFoundArchiveFlow(threadChannel, embed)); } - private void triggerArchiveFlow(ThreadChannel threadChannel, long authorId, MessageEmbed embed) { + private void triggerArchiveFlow(ThreadChannel threadChannel, long authorId, + MessageEmbed embed) { + + String markActiveId = helper.generateMarkActiveId(componentIdInteractor); Function> sendEmbedWithMention = member -> threadChannel.sendMessage(member.getAsMention()) - .addEmbeds(embed) - .addActionRow(Button.primary("OTHER:chatpgt-answer:mark-active", "Mark Active")); + .addEmbeds(embed) + .addActionRow(Button.primary(markActiveId, MARK_ACTIVE_LABEL)); Supplier> sendEmbedWithoutMention = () -> threadChannel.sendMessageEmbeds(embed) - .addActionRow(Button.primary("OTHER:chatpgt-answer:mark-active", "Mark Active")); + .addActionRow(Button.primary(markActiveId, MARK_ACTIVE_LABEL)); threadChannel.getGuild() - .retrieveMemberById(authorId) - .mapToResult() - .flatMap(authorResults -> { - if (authorResults.isFailure()) { - logger.info( - "Trying to archive a thread ({}), but OP ({}) left the server, sending embed without mention", - threadChannel.getId(), authorId, authorResults.getFailure()); - - return sendEmbedWithoutMention.get(); - } - - return sendEmbedWithMention.apply(authorResults.get()); - }) - .flatMap(_ -> threadChannel.getManager().setArchived(true)) - .queue(); + .retrieveMemberById(authorId) + .mapToResult() + .flatMap(authorResults -> { + if (authorResults.isFailure()) { + logger.info( + "Trying to archive a thread ({}), but OP ({}) left the server, sending embed without mention", + threadChannel.getId(), authorId, authorResults.getFailure()); + + return sendEmbedWithoutMention.get(); + } + + return sendEmbedWithMention.apply(authorResults.get()); + }) + .flatMap(_ -> threadChannel.getManager().setArchived(true)) + .queue(); } - private void triggerAuthorIdNotFoundArchiveFlow(ThreadChannel threadChannel, MessageEmbed embed) { + private void triggerAuthorIdNotFoundArchiveFlow(ThreadChannel threadChannel, + MessageEmbed embed) { logger.info( "Was unable to find a matching thread for id: {} in DB, archiving thread without mentioning OP", threadChannel.getId()); + String markActiveId = helper.generateMarkActiveId(componentIdInteractor); + threadChannel.sendMessageEmbeds(embed) - .addActionRow(Button.primary("OTHER:chatpgt-answer:mark-active", "Mark Active")) - .flatMap(sentEmbed -> threadChannel.getManager().setArchived(true)) - .queue(); + .addActionRow(Button.primary(markActiveId, MARK_ACTIVE_LABEL)) + .flatMap(sentEmbed -> threadChannel.getManager().setArchived(true)) + .queue(); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java index c822d6a46c..41a71e557a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java @@ -166,6 +166,16 @@ public void acceptComponentIdGenerator(ComponentIdGenerator generator) { componentIdInteractor.acceptComponentIdGenerator(generator); } + /** + * Returns the component ID interactor used by this listener, so that other components (e.g. the + * archiver) can generate IDs within the same namespace. + * + * @return the component ID interactor + */ + public ComponentIdInteractor getComponentIdInteractor() { + return componentIdInteractor; + } + private Consumer handleParentMessageDeleted(Member user, ThreadChannel channel, ButtonInteractionEvent event, List args) { int noOfMessages = 1; // we only care about first message from channel history @@ -261,12 +271,13 @@ private void onInactivityButton(ButtonInteractionEvent event) { if (event.getChannel() instanceof ThreadChannel thread) { Message botClosedThreadMessage = event.getMessage(); - thread.getManager().setArchived(false) - .flatMap(v -> botClosedThreadMessage.delete()) - .queue(); + thread.getManager() + .setArchived(false) + .flatMap(v -> botClosedThreadMessage.delete()) + .queue(); - log.debug("Thread {} was manually reactivated via button by {}", - thread.getId(), event.getUser().getId()); + log.debug("Thread {} was manually reactivated via button by {}", thread.getId(), + event.getUser().getId()); } } } From 29b7a73ea074afc91209a3ffc7a951dc5a65b3e1 Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Mon, 2 Mar 2026 17:13:00 +0800 Subject: [PATCH 5/9] refactor(help):minor formatting --- .../tjbot/features/help/HelpThreadAutoArchiver.java | 2 +- .../tjbot/features/help/HelpThreadCreatedListener.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 6a3d0340a2..45ba635fa5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -178,7 +178,7 @@ private void triggerAuthorIdNotFoundArchiveFlow(ThreadChannel threadChannel, threadChannel.sendMessageEmbeds(embed) .addActionRow(Button.primary(markActiveId, MARK_ACTIVE_LABEL)) - .flatMap(sentEmbed -> threadChannel.getManager().setArchived(true)) + .flatMap(_ -> threadChannel.getManager().setArchived(true)) .queue(); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java index 41a71e557a..28f76e38a2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java @@ -273,10 +273,10 @@ private void onInactivityButton(ButtonInteractionEvent event) { thread.getManager() .setArchived(false) - .flatMap(v -> botClosedThreadMessage.delete()) + .flatMap(_ -> botClosedThreadMessage.delete()) .queue(); - log.debug("Thread {} was manually reactivated via button by {}", thread.getId(), + log.debug("Thread {} was manually reactivated via button by user {}", thread.getId(), event.getUser().getId()); } } From e92c09ddfe4a1820a80b8495fe05b8aeebba508b Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Mon, 2 Mar 2026 18:13:07 +0800 Subject: [PATCH 6/9] refactor: move thread reactivation logic to HelpThreadAutoArchiver - Make HelpThreadAutoArchiver implement UserInteractor (name: thread-inactivity) - Own ComponentIdInteractor for mark-active button ID generation - Move onButtonClick/onInactivityButton from listener to archiver - Remove mark-active routing and getComponentIdInteractor from listener - Remove unused generateMarkActiveId from HelpSystemHelper - Extract mark-active literal to constant (SonarLint S1192) --- .../togetherjava/tjbot/features/Features.java | 3 +- .../tjbot/features/help/HelpSystemHelper.java | 11 ---- .../features/help/HelpThreadAutoArchiver.java | 57 ++++++++++++++++--- .../help/HelpThreadCreatedListener.java | 32 +---------- 4 files changed, 51 insertions(+), 52 deletions(-) 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 37461b81cb..513f75af1a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -146,8 +146,7 @@ public static Collection createFeatures(JDA jda, Database database, Con .add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database)); HelpThreadCreatedListener helpThreadCreatedListener = new HelpThreadCreatedListener(helpSystemHelper); - features.add(new HelpThreadAutoArchiver(helpSystemHelper, - helpThreadCreatedListener.getComponentIdInteractor())); + features.add(new HelpThreadAutoArchiver(helpSystemHelper)); features.add(new LeftoverBookmarksCleanupRoutine(bookmarksSystem)); features.add(new MarkHelpThreadCloseInDBRoutine(database, helpThreadLifecycleListener)); features.add(new MemberCountDisplayRoutine(config)); 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 516fd07945..edf217f1ea 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 @@ -419,17 +419,6 @@ Optional getAuthorByHelpThreadId(final long channelId) { } - /** - * Generates a component ID for the "Mark Active" button, derived from the given interactor's - * namespace. - * - * @param interactor the component ID interactor whose namespace should be used - * @return a valid component ID string for the mark-active button - */ - public String generateMarkActiveId(ComponentIdInteractor interactor) { - return interactor.generateComponentId("mark-active"); - } - /** * will be used to filter a tag based on categories config * diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 45ba635fa5..79b27bc381 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -8,6 +8,7 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.TimeUtil; @@ -15,6 +16,9 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.features.Routine; +import org.togetherjava.tjbot.features.UserInteractionType; +import org.togetherjava.tjbot.features.UserInteractor; +import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator; import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor; import java.time.Duration; @@ -29,25 +33,62 @@ * Routine, which periodically checks all help threads and archives them if there has not been any * recent activity. */ -public final class HelpThreadAutoArchiver implements Routine { +public final class HelpThreadAutoArchiver implements Routine, UserInteractor { private static final Logger logger = LoggerFactory.getLogger(HelpThreadAutoArchiver.class); private static final int SCHEDULE_MINUTES = 60; private static final Duration ARCHIVE_AFTER_INACTIVITY_OF = Duration.ofHours(12); private static final String MARK_ACTIVE_LABEL = "Mark Active"; + private static final String MARK_ACTIVE_ID = "mark-active"; private final HelpSystemHelper helper; - private final ComponentIdInteractor componentIdInteractor; + private final ComponentIdInteractor inactivityInteractor = + new ComponentIdInteractor(getInteractionType(), getName()); /** * Creates a new instance. * * @param helper the helper to use - * @param componentIdInteractor the interactor used to generate component IDs for buttons */ - public HelpThreadAutoArchiver(HelpSystemHelper helper, - ComponentIdInteractor componentIdInteractor) { + public HelpThreadAutoArchiver(HelpSystemHelper helper) { this.helper = helper; - this.componentIdInteractor = componentIdInteractor; + } + + @Override + public String getName() { + return "thread-inactivity"; + } + + @Override + public UserInteractionType getInteractionType() { + return UserInteractionType.OTHER; + } + + @Override + public void acceptComponentIdGenerator(ComponentIdGenerator generator) { + inactivityInteractor.acceptComponentIdGenerator(generator); + } + + @Override + public void onButtonClick(ButtonInteractionEvent event, List args) { + if (args.contains(MARK_ACTIVE_ID)) { + onInactivityButton(event); + } + } + + private void onInactivityButton(ButtonInteractionEvent event) { + event.deferEdit().queue(); + + if (event.getChannel() instanceof ThreadChannel thread) { + Message botClosedThreadMessage = event.getMessage(); + + thread.getManager() + .setArchived(false) + .flatMap(_ -> botClosedThreadMessage.delete()) + .queue(); + + logger.debug("Thread {} was manually reactivated via button by user {}", thread.getId(), + event.getUser().getId()); + } } @Override @@ -138,7 +179,7 @@ private void handleArchiveFlow(ThreadChannel threadChannel, MessageEmbed embed) private void triggerArchiveFlow(ThreadChannel threadChannel, long authorId, MessageEmbed embed) { - String markActiveId = helper.generateMarkActiveId(componentIdInteractor); + String markActiveId = inactivityInteractor.generateComponentId(MARK_ACTIVE_ID); Function> sendEmbedWithMention = member -> threadChannel.sendMessage(member.getAsMention()) @@ -174,7 +215,7 @@ private void triggerAuthorIdNotFoundArchiveFlow(ThreadChannel threadChannel, "Was unable to find a matching thread for id: {} in DB, archiving thread without mentioning OP", threadChannel.getId()); - String markActiveId = helper.generateMarkActiveId(componentIdInteractor); + String markActiveId = inactivityInteractor.generateComponentId(MARK_ACTIVE_ID); threadChannel.sendMessageEmbeds(embed) .addActionRow(Button.primary(markActiveId, MARK_ACTIVE_LABEL)) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java index 28f76e38a2..246af1ffcf 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java @@ -166,16 +166,6 @@ public void acceptComponentIdGenerator(ComponentIdGenerator generator) { componentIdInteractor.acceptComponentIdGenerator(generator); } - /** - * Returns the component ID interactor used by this listener, so that other components (e.g. the - * archiver) can generate IDs within the same namespace. - * - * @return the component ID interactor - */ - public ComponentIdInteractor getComponentIdInteractor() { - return componentIdInteractor; - } - private Consumer handleParentMessageDeleted(Member user, ThreadChannel channel, ButtonInteractionEvent event, List args) { int noOfMessages = 1; // we only care about first message from channel history @@ -197,11 +187,7 @@ private Consumer handleParentMessageDeleted(Member user, ThreadChanne @Override public void onButtonClick(ButtonInteractionEvent event, List args) { - if (args.contains("mark-active")) { - onInactivityButton(event); - } else { - onAiHelpDismissButton(event, args); - } + onAiHelpDismissButton(event, args); } private void onAiHelpDismissButton(ButtonInteractionEvent event, List args) { @@ -264,20 +250,4 @@ private void registerThreadDataInDB(Message message, ThreadChannel threadChannel helper.writeHelpThreadToDatabase(authorId, threadChannel); } - - private void onInactivityButton(ButtonInteractionEvent event) { - event.deferEdit().queue(); - - if (event.getChannel() instanceof ThreadChannel thread) { - Message botClosedThreadMessage = event.getMessage(); - - thread.getManager() - .setArchived(false) - .flatMap(_ -> botClosedThreadMessage.delete()) - .queue(); - - log.debug("Thread {} was manually reactivated via button by user {}", thread.getId(), - event.getUser().getId()); - } - } } From faadc3a01b193bd62437f85ea6964bc78b316912 Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Mon, 2 Mar 2026 19:03:30 +0800 Subject: [PATCH 7/9] refactor: minor cleanups as requested --- .../togetherjava/tjbot/features/Features.java | 2 +- .../features/help/HelpThreadAutoArchiver.java | 25 ++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) 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 513f75af1a..6790b06554 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -144,9 +144,9 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new HelpThreadActivityUpdater(helpSystemHelper)); features .add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database)); + features.add(new HelpThreadAutoArchiver(helpSystemHelper)); HelpThreadCreatedListener helpThreadCreatedListener = new HelpThreadCreatedListener(helpSystemHelper); - features.add(new HelpThreadAutoArchiver(helpSystemHelper)); features.add(new LeftoverBookmarksCleanupRoutine(bookmarksSystem)); features.add(new MarkHelpThreadCloseInDBRoutine(database, helpThreadLifecycleListener)); features.add(new MemberCountDisplayRoutine(config)); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 79b27bc381..96b8128cb1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -55,7 +55,7 @@ public HelpThreadAutoArchiver(HelpSystemHelper helper) { @Override public String getName() { - return "thread-inactivity"; + return "help-thread-auto-archiver"; } @Override @@ -70,25 +70,22 @@ public void acceptComponentIdGenerator(ComponentIdGenerator generator) { @Override public void onButtonClick(ButtonInteractionEvent event, List args) { - if (args.contains(MARK_ACTIVE_ID)) { - onInactivityButton(event); - } + onMarkActiveButton(event); } - private void onInactivityButton(ButtonInteractionEvent event) { + private void onMarkActiveButton(ButtonInteractionEvent event) { event.deferEdit().queue(); - if (event.getChannel() instanceof ThreadChannel thread) { - Message botClosedThreadMessage = event.getMessage(); + ThreadChannel thread = event.getChannel().asThreadChannel(); + Message botClosedThreadMessage = event.getMessage(); - thread.getManager() - .setArchived(false) - .flatMap(_ -> botClosedThreadMessage.delete()) - .queue(); + thread.getManager() + .setArchived(false) + .flatMap(_ -> botClosedThreadMessage.delete()) + .queue(); - logger.debug("Thread {} was manually reactivated via button by user {}", thread.getId(), - event.getUser().getId()); - } + logger.debug("Thread {} was manually reactivated via button by user {}", thread.getId(), + event.getUser().getId()); } @Override From a3bc76a3ca1cc6a85cb4d85c3fd16f1829a3fce8 Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Tue, 3 Mar 2026 15:51:25 +0800 Subject: [PATCH 8/9] refactor: formatting as requested by christolis. --- .../main/java/org/togetherjava/tjbot/features/Features.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 6790b06554..65a9996842 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -125,6 +125,8 @@ public static Collection createFeatures(JDA jda, Database database, Con HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database, chatGptService); HelpThreadLifecycleListener helpThreadLifecycleListener = new HelpThreadLifecycleListener(helpSystemHelper, database); + HelpThreadCreatedListener helpThreadCreatedListener = + new HelpThreadCreatedListener(helpSystemHelper); TopHelpersService topHelpersService = new TopHelpersService(database); TopHelpersAssignmentRoutine topHelpersAssignmentRoutine = new TopHelpersAssignmentRoutine(config, topHelpersService); @@ -145,8 +147,6 @@ public static Collection createFeatures(JDA jda, Database database, Con features .add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database)); features.add(new HelpThreadAutoArchiver(helpSystemHelper)); - HelpThreadCreatedListener helpThreadCreatedListener = - new HelpThreadCreatedListener(helpSystemHelper); features.add(new LeftoverBookmarksCleanupRoutine(bookmarksSystem)); features.add(new MarkHelpThreadCloseInDBRoutine(database, helpThreadLifecycleListener)); features.add(new MemberCountDisplayRoutine(config)); From 2d26d42c7ee05cef769c22223b0520bb45f51d2d Mon Sep 17 00:00:00 2001 From: SleepyStack Date: Tue, 3 Mar 2026 16:07:21 +0800 Subject: [PATCH 9/9] add Ephermeral message on thread re-Activation --- .../tjbot/features/help/HelpThreadAutoArchiver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java index 96b8128cb1..d4537cdab5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadAutoArchiver.java @@ -74,7 +74,7 @@ public void onButtonClick(ButtonInteractionEvent event, List args) { } private void onMarkActiveButton(ButtonInteractionEvent event) { - event.deferEdit().queue(); + event.reply("You have marked the thread as active.").setEphemeral(true).queue(); ThreadChannel thread = event.getChannel().asThreadChannel(); Message botClosedThreadMessage = event.getMessage();