Skip to content

Commit 775457f

Browse files
Merge pull request #365 from Java-Discord/dynxsty/post_forum_cleanup
Post Help Forum Cleanup
2 parents c868df2 + 29894b3 commit 775457f

File tree

11 files changed

+193
-36
lines changed

11 files changed

+193
-36
lines changed

build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ repositories {
2525
dependencies {
2626
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.0")
2727
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.0")
28+
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
29+
compileOnly("org.jetbrains:annotations:23.0.0")
2830

2931
// DIH4JDA (Interaction Framework) & JDA
3032
implementation("com.github.DynxstyGIT:DIH4JDA:c8f7928efc")
31-
implementation("net.dv8tion:JDA:5.0.0-alpha.20") {
33+
implementation("com.github.DV8FromTheWorld:JDA:86af73d377") {
3234
exclude(module = "opus-java")
3335
}
3436

src/main/java/net/javadiscord/javabot/Bot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ private static void addComponentHandler(@NotNull DIH4JDA dih4jda) {
213213
List.of("qotw-submission"), new SubmissionInteractionManager(),
214214
List.of("help-channel", "help-thank"), new HelpChannelInteractionManager(),
215215
List.of("qotw-list-questions"), new QOTWQuerySubcommand(),
216-
List.of(ForumHelpManager.HELP_THANKS_IDENTIFIER), new ForumHelpListener()
216+
List.of(ForumHelpManager.HELP_THANKS_IDENTIFIER, ForumHelpManager.HELP_CLOSE_IDENTIFIER, ForumHelpManager.HELP_GUIDELINES_IDENTIFIER), new ForumHelpListener()
217217
));
218218
dih4jda.addModalHandlers(Map.of(
219219
List.of("qotw-add-question"), new AddQuestionSubcommand(),

src/main/java/net/javadiscord/javabot/data/config/guild/HelpForumConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
@EqualsAndHashCode(callSuper = true)
1414
public class HelpForumConfig extends GuildConfigItem {
1515
private long helpForumChannelId = 0;
16+
private String closeReminderText = "Hey, %s!\nPlease remember to `/close` this post once your question has been answered!";
17+
private String helpThanksText = "Before your post will be closed, would you like to express your gratitude to any of the people who helped you? When you're done, click **I'm done here. Close this post!**.";
1618

1719
public ForumChannel getHelpForumChannel() {
1820
return getGuild().getForumChannelById(helpForumChannelId);

src/main/java/net/javadiscord/javabot/systems/help/HelpChannelManager.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,6 @@ public void reserve(TextChannel channel, User reservingUser, Message message) th
153153
TextChannel targetChannel = dormantChannels.get(0);
154154
targetChannel.getManager().setParent(targetCategory).sync(targetCategory).queue();
155155
targetChannel.sendMessage(config.getReopenedChannelMessage()).queue();
156-
} else {
157-
logChannel.sendMessage("Warning: No dormant channels were available to replenish an open channel that was just reserved.").queue();
158156
}
159157
} else {
160158
this.openNew();

src/main/java/net/javadiscord/javabot/systems/help/HelpChannelUpdater.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import net.dv8tion.jda.api.JDA;
66
import net.dv8tion.jda.api.entities.*;
77
import net.dv8tion.jda.api.entities.channel.concrete.Category;
8+
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
89
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
910
import net.dv8tion.jda.api.entities.emoji.Emoji;
11+
import net.dv8tion.jda.api.interactions.components.ActionRow;
1012
import net.dv8tion.jda.api.interactions.components.buttons.Button;
1113
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
1214
import net.dv8tion.jda.api.requests.RestAction;
@@ -15,6 +17,7 @@
1517
import net.dv8tion.jda.internal.requests.CompletedRestAction;
1618
import net.javadiscord.javabot.Bot;
1719
import net.javadiscord.javabot.data.config.guild.HelpConfig;
20+
import net.javadiscord.javabot.data.config.guild.HelpForumConfig;
1821
import net.javadiscord.javabot.systems.help.model.ChannelReservation;
1922
import net.javadiscord.javabot.util.ExceptionLogger;
2023
import net.javadiscord.javabot.util.Responses;
@@ -372,6 +375,7 @@ private RestAction<?> semanticMessageCheck(TextChannel channel, User owner, List
372375
}
373376

374377
private void updateHelpOverview() {
378+
HelpForumConfig forumConfig = Bot.getConfig().get(config.getGuild()).getHelpForumConfig();
375379
config.getHelpOverviewMessageIds().forEach((channelId, messageId) -> {
376380
TextChannel channel = config.getGuild().getTextChannelById(channelId);
377381
if (channel == null) {
@@ -381,11 +385,17 @@ private void updateHelpOverview() {
381385
List<TextChannel> availableChannels = config.getOpenChannelCategory().getTextChannels();
382386
List<Button> buttons = new ArrayList<>(2);
383387
if (!availableChannels.isEmpty()) {
384-
buttons.add(Button.link(availableChannels.get(0).getJumpUrl(), "Show me an available Help Channel!"));
388+
buttons.add(Button.link(availableChannels.get(0).getJumpUrl(), "Show me an Available Help Channel!"));
385389
}
386390
buttons.add(Button.link(StringResourceCache.load("/help_overview/overview_image_url.txt"), "How does this work?"));
387391
channel.retrieveMessageById(messageId).queue(
388-
m -> m.editMessageEmbeds(buildHelpOverviewEmbed()).setActionRow(buttons).queue(),
392+
m -> m.editMessageEmbeds(buildHelpOverviewEmbed()).setComponents(
393+
ActionRow.of(
394+
// Temporary Forum Channel Upsell
395+
Button.link(forumConfig.getHelpForumChannel().getJumpUrl(), "Try our new Help Forum!"),
396+
Button.link("https://discord.com/blog/forum-channels-space-for-organized-conversation", "What are Forums?")
397+
),
398+
ActionRow.of(buttons)).queue(),
389399
err -> channel.sendMessageEmbeds(buildHelpOverviewEmbed()).queue(m -> {
390400
config.getHelpOverviewMessageIds().put(channelId, m.getIdLong());
391401
Bot.getConfig().flush();
@@ -419,10 +429,14 @@ private void updateHelpOverview() {
419429
e -> ExceptionLogger.capture(e, getClass().getSimpleName())
420430
);
421431
}
432+
ForumChannel forum = Bot.getConfig().get(config.getGuild()).getHelpForumConfig().getHelpForumChannel();
422433
EmbedBuilder builder = new EmbedBuilder()
423434
.setTitle("Help Overview")
424435
.setColor(Responses.Type.DEFAULT.getColor())
425-
.setDescription(availableHelpChannels + " are __**available**__ to claim!")
436+
.setDescription(availableHelpChannels.isEmpty() ?
437+
String.format("There are no help channels available to claim.%nHow about using our new **[Help Forum](%s)** (%s) then?",
438+
forum.getJumpUrl(), forum.getAsMention()) :
439+
availableHelpChannels + " are __**available**__ to claim!")
426440
.setFooter("Last refreshed: ")
427441
.setTimestamp(Instant.now());
428442
if (!reservedHelpChannels.isEmpty()) {

src/main/java/net/javadiscord/javabot/systems/help/commands/HelpPingSubcommand.java

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.dynxsty.dih4jda.interactions.commands.SlashCommand;
44
import net.dv8tion.jda.api.EmbedBuilder;
55
import net.dv8tion.jda.api.entities.*;
6+
import net.dv8tion.jda.api.entities.channel.ChannelType;
7+
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
68
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
79
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
810
import net.javadiscord.javabot.Bot;
@@ -23,7 +25,7 @@
2325
* helpers.
2426
*/
2527
public class HelpPingSubcommand extends SlashCommand.Subcommand {
26-
private static final String WRONG_CHANNEL_MSG = "This command can only be used in **reserved help channels**.";
28+
private static final String WRONG_CHANNEL_MSG = "This command can only be used in **reserved help channels** OR **help forum posts.**.";
2729
private static final long CACHE_CLEANUP_DELAY = 60L;
2830

2931
private final Map<Long, Pair<Long, Guild>> lastPingTimes;
@@ -45,6 +47,43 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
4547
return;
4648
}
4749
GuildConfig config = Bot.getConfig().get(guild);
50+
if (event.getChannelType() == ChannelType.TEXT) {
51+
handleTextBasedHelpPing(event, config);
52+
} else if (event.getChannelType() == ChannelType.GUILD_PUBLIC_THREAD) {
53+
handleForumBasedHelpPing(event, config, event.getChannel().asThreadChannel());
54+
}
55+
}
56+
57+
private void handleForumBasedHelpPing(@NotNull SlashCommandInteractionEvent event, @NotNull GuildConfig config, @NotNull ThreadChannel post) {
58+
if (post.getParentChannel().getType() != ChannelType.FORUM ||
59+
post.getParentChannel().getIdLong() != config.getHelpForumConfig().getHelpForumChannelId()
60+
) {
61+
Responses.error(event, WRONG_CHANNEL_MSG).queue();
62+
return;
63+
}
64+
Member member = event.getMember();
65+
if (member == null) {
66+
Responses.warning(event, "No member information was available for this event.").queue();
67+
return;
68+
}
69+
if (isHelpPingForbiddenForMember(post.getOwnerIdLong(), member, config)) {
70+
Responses.warning(event, "Sorry, but only the person who reserved this channel, or staff and helpers, may use this command.").queue();
71+
return;
72+
}
73+
if (isHelpPingTimeoutElapsed(member.getIdLong(), config)) {
74+
lastPingTimes.put(event.getMember().getIdLong(), new Pair<>(System.currentTimeMillis(), config.getGuild()));
75+
Role role = config.getHelpConfig().getHelpPingRole();
76+
event.getChannel().sendMessage(role.getAsMention())
77+
.setAllowedMentions(EnumSet.of(Message.MentionType.ROLE))
78+
.setEmbeds(buildAuthorEmbed(event.getUser()))
79+
.queue();
80+
event.replyFormat("Successfully pinged " + role.getAsMention()).setEphemeral(true).queue();
81+
} else {
82+
Responses.warning(event, "Sorry, but you can only use this command occasionally. Please try again later.").queue();
83+
}
84+
}
85+
86+
private void handleTextBasedHelpPing(@NotNull SlashCommandInteractionEvent event, @NotNull GuildConfig config) {
4887
HelpChannelManager channelManager = new HelpChannelManager(config.getHelpConfig());
4988
if (channelManager.isReserved(event.getChannel().asTextChannel())) {
5089
Optional<ChannelReservation> optionalReservation = channelManager.getReservationForChannel(event.getChannel().getIdLong());
@@ -63,7 +102,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
63102
return;
64103
}
65104
if (isHelpPingTimeoutElapsed(member.getIdLong(), config)) {
66-
lastPingTimes.put(event.getMember().getIdLong(), new Pair<>(System.currentTimeMillis(), guild));
105+
lastPingTimes.put(event.getMember().getIdLong(), new Pair<>(System.currentTimeMillis(), config.getGuild()));
67106
Role role = channelManager.getConfig().getHelpPingRole();
68107
event.getChannel().sendMessage(role.getAsMention())
69108
.setAllowedMentions(EnumSet.of(Message.MentionType.ROLE))
@@ -103,6 +142,24 @@ private boolean isHelpPingForbiddenForMember(@NotNull ChannelReservation reserva
103142
);
104143
}
105144

145+
/**
146+
* Determines if a user is forbidden from sending a help-ping command due
147+
* to their status in the server.
148+
*
149+
* @param postOwnerId The posts' owner id.
150+
* @param member The member.
151+
* @param config The guild config.
152+
* @return True if the user is forbidden from sending the command.
153+
*/
154+
private boolean isHelpPingForbiddenForMember(long postOwnerId, @NotNull Member member, @NotNull GuildConfig config) {
155+
Set<Role> allowedRoles = Set.of(config.getModerationConfig().getStaffRole(), config.getHelpConfig().getHelperRole());
156+
return !(
157+
postOwnerId == member.getUser().getIdLong() ||
158+
member.getRoles().stream().anyMatch(allowedRoles::contains) ||
159+
member.isOwner()
160+
);
161+
}
162+
106163
/**
107164
* Determines if the user's timeout has elapsed (or doesn't exist), which
108165
* implies that it's fine for the user to send the command.

src/main/java/net/javadiscord/javabot/systems/help/commands/UnreserveCommand.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private void handleForumBasedHelp(SlashCommandInteractionEvent event, @NotNull T
5454
replyInvalidChannel(event);
5555
}
5656
ForumHelpManager manager = new ForumHelpManager(postThread);
57-
if (isForumEligibleToBeUnreserved(event, postThread)) {
57+
if (manager.isForumEligibleToBeUnreserved(event)) {
5858
manager.close(event, event.getUser().getIdLong() == postThread.getOwnerIdLong(),
5959
event.getOption("reason", null, OptionMapping::getAsString)
6060
);
@@ -82,10 +82,6 @@ private void replyInvalidChannel(CommandInteraction interaction) {
8282
.queue();
8383
}
8484

85-
private boolean isForumEligibleToBeUnreserved(@NotNull SlashCommandInteractionEvent event, @NotNull ThreadChannel postThread) {
86-
return event.getUser().getIdLong() == postThread.getOwnerIdLong() || memberHasHelperRole(event) || memberHasStaffRole(event);
87-
}
88-
8985
private boolean isTextEligibleToBeUnreserved(SlashCommandInteractionEvent event, TextChannel channel, HelpConfig config, User owner) {
9086
return channelIsInReservedCategory(channel, config) &&
9187
(isUserWhoReservedChannel(event, owner) || memberHasHelperRole(event) || memberHasStaffRole(event));

src/main/java/net/javadiscord/javabot/systems/help/forum/ForumHelpListener.java

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,30 @@
22

33
import com.dynxsty.dih4jda.interactions.ComponentIdBuilder;
44
import com.dynxsty.dih4jda.interactions.components.ButtonHandler;
5+
import net.dv8tion.jda.api.EmbedBuilder;
56
import net.dv8tion.jda.api.entities.Message;
7+
import net.dv8tion.jda.api.entities.UserSnowflake;
8+
import net.dv8tion.jda.api.entities.channel.Channel;
69
import net.dv8tion.jda.api.entities.channel.ChannelType;
710
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
811
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
12+
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
913
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
1014
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
1115
import net.dv8tion.jda.api.hooks.ListenerAdapter;
16+
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
1217
import net.dv8tion.jda.api.interactions.components.ActionComponent;
18+
import net.dv8tion.jda.api.interactions.components.ActionRow;
1319
import net.dv8tion.jda.api.interactions.components.buttons.Button;
1420
import net.javadiscord.javabot.Bot;
15-
import net.javadiscord.javabot.data.config.GuildConfig;
1621
import net.javadiscord.javabot.data.config.guild.HelpConfig;
22+
import net.javadiscord.javabot.data.config.guild.HelpForumConfig;
1723
import net.javadiscord.javabot.systems.help.HelpChannelManager;
1824
import net.javadiscord.javabot.systems.help.HelpExperienceService;
1925
import net.javadiscord.javabot.systems.help.model.HelpTransactionMessage;
26+
import net.javadiscord.javabot.systems.user_preferences.UserPreferenceService;
27+
import net.javadiscord.javabot.systems.user_preferences.model.Preference;
28+
import net.javadiscord.javabot.systems.user_preferences.model.UserPreference;
2029
import net.javadiscord.javabot.util.ExceptionLogger;
2130
import net.javadiscord.javabot.util.Responses;
2231
import org.jetbrains.annotations.NotNull;
@@ -39,19 +48,12 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
3948
if (event.getMessage().getAuthor().isSystem() || event.getMessage().getAuthor().isBot()) {
4049
return;
4150
}
42-
// check for guild & channel type
43-
if (!event.isFromGuild() || event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) {
51+
// check for forum post
52+
if (isInvalidForumPost(event.getChannel())) {
4453
return;
4554
}
46-
// get post & check parent channel
4755
ThreadChannel post = event.getChannel().asThreadChannel();
48-
if (post.getParentChannel().getType() != ChannelType.FORUM) {
49-
return;
50-
}
51-
ForumChannel forum = post.getParentChannel().asForumChannel();
52-
GuildConfig config = Bot.getConfig().get(event.getGuild());
53-
// check for channel id
54-
if (forum.getIdLong() != config.getHelpForumConfig().getHelpForumChannelId()) {
56+
if (isInvalidHelpForumChannel(post.getParentChannel().asForumChannel())) {
5557
return;
5658
}
5759
// cache messages
@@ -63,20 +65,58 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
6365
HELP_POST_MESSAGES.put(post.getIdLong(), messages);
6466
}
6567

68+
@Override
69+
public void onChannelCreate(@NotNull ChannelCreateEvent event) {
70+
if (event.getGuild() == null || isInvalidForumPost(event.getChannel())) {
71+
return;
72+
}
73+
HelpForumConfig config = Bot.getConfig().get(event.getGuild()).getHelpForumConfig();
74+
ThreadChannel post = event.getChannel().asThreadChannel();
75+
if (isInvalidHelpForumChannel(post.getParentChannel().asForumChannel())) {
76+
return;
77+
}
78+
// send post buttons
79+
post.sendMessageComponents(ActionRow.of(
80+
Button.primary(ComponentIdBuilder.build(ForumHelpManager.HELP_CLOSE_IDENTIFIER, post.getIdLong()), "Close Post"),
81+
Button.secondary(ComponentIdBuilder.build(ForumHelpManager.HELP_GUIDELINES_IDENTIFIER), "View Help Guidelines")
82+
)).queue(success -> {
83+
// send /close reminder (if enabled)
84+
UserPreferenceService service = new UserPreferenceService(Bot.getDataSource());
85+
UserPreference preference = service.getOrCreate(post.getOwnerIdLong(), Preference.FORUM_CLOSE_REMINDER);
86+
if (Boolean.parseBoolean(preference.getState())) {
87+
post.sendMessageFormat(config.getCloseReminderText(), UserSnowflake.fromId(post.getOwnerIdLong()).getAsMention()).queue();
88+
}
89+
});
90+
}
91+
6692
@Override
6793
public void handleButton(@NotNull ButtonInteractionEvent event, @NotNull Button button) {
6894
String[] id = ComponentIdBuilder.split(event.getComponentId());
69-
if (event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD
70-
|| event.getChannel().asThreadChannel().getParentChannel().getType() != ChannelType.FORUM) {
95+
if (isInvalidForumPost(event.getChannel()) ||
96+
isInvalidHelpForumChannel(event.getChannel().asThreadChannel().getParentChannel().asForumChannel())
97+
) {
7198
Responses.error(event, "This button may only be used inside help forum threads.").queue();
7299
return;
73100
}
74-
ForumHelpManager manager = new ForumHelpManager(event.getChannel().asThreadChannel());
101+
ThreadChannel post = event.getChannel().asThreadChannel();
102+
ForumHelpManager manager = new ForumHelpManager(post);
75103
switch (id[0]) {
76104
case ForumHelpManager.HELP_THANKS_IDENTIFIER -> handleHelpThanksInteraction(event, manager, id);
105+
case ForumHelpManager.HELP_GUIDELINES_IDENTIFIER -> handleReplyGuidelines(event, post.getParentChannel().asForumChannel());
106+
case ForumHelpManager.HELP_CLOSE_IDENTIFIER -> handlePostClose(event, manager);
77107
}
78108
}
79109

110+
private boolean isInvalidForumPost(@NotNull Channel channel) {
111+
return channel.getType() != ChannelType.GUILD_PUBLIC_THREAD ||
112+
((ThreadChannel) channel).getParentChannel().getType() != ChannelType.FORUM;
113+
}
114+
115+
private boolean isInvalidHelpForumChannel(@NotNull ForumChannel forum) {
116+
HelpForumConfig config = Bot.getConfig().get(forum.getGuild()).getHelpForumConfig();
117+
return config.getHelpForumChannelId() != forum.getIdLong();
118+
}
119+
80120
private void handleHelpThanksInteraction(@NotNull ButtonInteractionEvent event, @NotNull ForumHelpManager manager, String @NotNull [] id) {
81121
ThreadChannel post = manager.postThread();
82122
HelpConfig config = Bot.getConfig().get(event.getGuild()).getHelpConfig();
@@ -108,4 +148,21 @@ private void handleHelpThanksInteraction(@NotNull ButtonInteractionEvent event,
108148
default -> event.editButton(event.getButton().asDisabled()).queue();
109149
}
110150
}
151+
152+
private void handleReplyGuidelines(@NotNull IReplyCallback callback, @NotNull ForumChannel channel) {
153+
callback.replyEmbeds(new EmbedBuilder()
154+
.setTitle("Help Guidelines")
155+
.setDescription(channel.getTopic())
156+
.build()
157+
).setEphemeral(true)
158+
.queue();
159+
}
160+
161+
private void handlePostClose(ButtonInteractionEvent event, @NotNull ForumHelpManager manager) {
162+
if (manager.isForumEligibleToBeUnreserved(event)) {
163+
manager.close(event, event.getUser().getIdLong() == manager.postThread().getOwnerIdLong(), null);
164+
} else {
165+
Responses.warning(event, "Could not close this post", "You're not allowed to close this post.").queue();
166+
}
167+
}
111168
}

0 commit comments

Comments
 (0)