Skip to content

Commit c868df2

Browse files
Merge pull request #364 from Java-Discord/dynxsty/forum-help
2 parents f8a31f2 + 1e925c6 commit c868df2

File tree

11 files changed

+350
-29
lines changed

11 files changed

+350
-29
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import net.javadiscord.javabot.listener.*;
2828
import net.javadiscord.javabot.systems.help.HelpChannelInteractionManager;
2929
import net.javadiscord.javabot.systems.help.HelpChannelListener;
30+
import net.javadiscord.javabot.systems.help.forum.ForumHelpListener;
31+
import net.javadiscord.javabot.systems.help.forum.ForumHelpManager;
3032
import net.javadiscord.javabot.systems.moderation.AutoMod;
3133
import net.javadiscord.javabot.systems.moderation.report.ReportManager;
3234
import net.javadiscord.javabot.systems.moderation.server_lock.ServerLockManager;
@@ -192,6 +194,7 @@ private static void addEventListeners(@NotNull JDA jda, @NotNull DIH4JDA dih4jda
192194
new MetricsUpdater(),
193195
new SuggestionListener(),
194196
new StarboardManager(),
197+
new ForumHelpListener(),
195198
new HelpChannelListener(),
196199
new ShareKnowledgeVoteListener(),
197200
new JobChannelVoteListener(),
@@ -209,7 +212,8 @@ private static void addComponentHandler(@NotNull DIH4JDA dih4jda) {
209212
List.of("self-role"), new SelfRoleInteractionManager(),
210213
List.of("qotw-submission"), new SubmissionInteractionManager(),
211214
List.of("help-channel", "help-thank"), new HelpChannelInteractionManager(),
212-
List.of("qotw-list-questions"), new QOTWQuerySubcommand()
215+
List.of("qotw-list-questions"), new QOTWQuerySubcommand(),
216+
List.of(ForumHelpManager.HELP_THANKS_IDENTIFIER), new ForumHelpListener()
213217
));
214218
dih4jda.addModalHandlers(Map.of(
215219
List.of("qotw-add-question"), new AddQuestionSubcommand(),

src/main/java/net/javadiscord/javabot/data/config/GuildConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class GuildConfig {
3131
private transient Path file;
3232

3333
private HelpConfig helpConfig;
34+
private HelpForumConfig helpForumConfig;
3435
private ModerationConfig moderationConfig;
3536
private QOTWConfig qotwConfig;
3637
private MetricsConfig metricsConfig;
@@ -48,6 +49,7 @@ public GuildConfig(Guild guild, Path file) {
4849
this.file = file;
4950
// Initialize all config items.
5051
this.helpConfig = new HelpConfig();
52+
this.helpForumConfig = new HelpForumConfig();
5153
this.moderationConfig = new ModerationConfig();
5254
this.qotwConfig = new QOTWConfig();
5355
this.metricsConfig = new MetricsConfig();
@@ -95,6 +97,8 @@ private void setGuild(Guild guild) {
9597
this.guild = guild;
9698
if (this.helpConfig == null) this.helpConfig = new HelpConfig();
9799
this.helpConfig.setGuildConfig(this);
100+
if (this.helpForumConfig == null) this.helpForumConfig = new HelpForumConfig();
101+
this.helpForumConfig.setGuildConfig(this);
98102
if (this.moderationConfig == null) this.moderationConfig = new ModerationConfig();
99103
this.moderationConfig.setGuildConfig(this);
100104
if (this.qotwConfig == null) this.qotwConfig = new QOTWConfig();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package net.javadiscord.javabot.data.config.guild;
2+
3+
import lombok.Data;
4+
import lombok.EqualsAndHashCode;
5+
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
6+
import net.javadiscord.javabot.data.config.GuildConfigItem;
7+
8+
/**
9+
* Configuration for the guilds' forum help system, which aims to replace the
10+
* 'old' text channel-based one.
11+
*/
12+
@Data
13+
@EqualsAndHashCode(callSuper = true)
14+
public class HelpForumConfig extends GuildConfigItem {
15+
private long helpForumChannelId = 0;
16+
17+
public ForumChannel getHelpForumChannel() {
18+
return getGuild().getForumChannelById(helpForumChannelId);
19+
}
20+
}

src/main/java/net/javadiscord/javabot/listener/HugListener.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import net.dv8tion.jda.api.entities.*;
55
import net.dv8tion.jda.api.entities.channel.ChannelType;
66
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
7-
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
7+
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildChannel;
88
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
99
import net.dv8tion.jda.api.hooks.ListenerAdapter;
1010
import net.javadiscord.javabot.Bot;
@@ -40,7 +40,7 @@ public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
4040
tc = event.getChannel().asTextChannel();
4141
}
4242
if (event.isFromThread()) {
43-
GuildMessageChannel parentChannel = event.getChannel().asThreadChannel().getParentMessageChannel();
43+
StandardGuildChannel parentChannel = event.getChannel().asThreadChannel().getParentChannel().asStandardGuildChannel();
4444
if (parentChannel instanceof TextChannel textChannel) {
4545
tc = textChannel;
4646
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ public RestAction<?> unreserveChannel(TextChannel channel) {
332332
Optional<ChannelReservation> reservationOptional = this.getReservationForChannel(channel.getIdLong());
333333
if (reservationOptional.isPresent()) {
334334
ChannelReservation reservation = reservationOptional.get();
335-
Map<Long, Double> experience = this.calculateExperience(HelpChannelListener.reservationMessages.get(reservation.getId()), reservation.getUserId());
335+
Map<Long, Double> experience = calculateExperience(HelpChannelListener.reservationMessages.get(reservation.getId()), reservation.getUserId(), config);
336336
for (Long recipient : experience.keySet()) {
337337
service.performTransaction(recipient, experience.get(recipient), HelpTransactionMessage.HELPED, channel.getGuild());
338338
}
@@ -527,7 +527,15 @@ public Optional<Long> getReservationId(TextChannel channel) {
527527
}
528528
}
529529

530-
private Map<Long, Double> calculateExperience(List<Message> messages, long ownerId) {
530+
/**
531+
* Calculates the experience for each user, based on the messages they sent.
532+
*
533+
* @param messages The list of {@link Message}s.
534+
* @param ownerId The owner id.
535+
* @param config The {@link HelpConfig}, containing some static info for the calculation.
536+
* @return A {@link Map}, containing the users' id as the key, and the amount of xp as the value.
537+
*/
538+
public static Map<Long, Double> calculateExperience(List<Message> messages, long ownerId, HelpConfig config) {
531539
Map<Long, Double> experience = new HashMap<>();
532540
if (messages == null || messages.isEmpty()) return Map.of();
533541
for (User user : messages.stream().map(Message::getAuthor).collect(Collectors.toSet())) {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ public List<HelpTransaction> getRecentTransactions(long userId, int count) throw
8686
try (Connection con = this.dataSource.getConnection()) {
8787
con.setReadOnly(true);
8888
HelpTransactionRepository repo = new HelpTransactionRepository(con);
89-
List<HelpTransaction> transactions = repo.getTransactions(userId, count);
90-
return transactions;
89+
return repo.getTransactions(userId, count);
9190
}
9291
}
9392

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

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import net.dv8tion.jda.api.entities.User;
55
import net.dv8tion.jda.api.entities.channel.ChannelType;
66
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
7+
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
78
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
9+
import net.dv8tion.jda.api.interactions.commands.CommandInteraction;
810
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
911
import net.dv8tion.jda.api.interactions.commands.OptionType;
1012
import net.dv8tion.jda.api.interactions.commands.build.Commands;
1113
import net.javadiscord.javabot.Bot;
1214
import net.javadiscord.javabot.data.config.guild.HelpConfig;
1315
import net.javadiscord.javabot.systems.help.HelpChannelManager;
16+
import net.javadiscord.javabot.systems.help.forum.ForumHelpManager;
1417
import net.javadiscord.javabot.util.Responses;
1518
import org.jetbrains.annotations.NotNull;
1619

@@ -31,15 +34,40 @@ public UnreserveCommand() {
3134

3235
@Override
3336
public void execute(@NotNull SlashCommandInteractionEvent event) {
34-
if (event.getChannelType() != ChannelType.TEXT) {
35-
Responses.error(event, "This command cannot be used outside of help channels.").queue();
37+
// check whether the channel type is either text or thread (possible forum post?)
38+
if (event.getChannelType() != ChannelType.TEXT && event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) {
39+
replyInvalidChannel(event);
3640
return;
3741
}
38-
TextChannel channel = event.getChannel().asTextChannel();
42+
// handle forum-based help system
43+
if (event.getChannelType() == ChannelType.GUILD_PUBLIC_THREAD) {
44+
handleForumBasedHelp(event, event.getChannel().asThreadChannel());
45+
}
46+
// handle text-based help system
47+
if (event.getChannelType() == ChannelType.TEXT) {
48+
handleTextBasedHelp(event, event.getChannel().asTextChannel());
49+
}
50+
}
51+
52+
private void handleForumBasedHelp(SlashCommandInteractionEvent event, @NotNull ThreadChannel postThread) {
53+
if (postThread.getParentChannel().getType() != ChannelType.FORUM) {
54+
replyInvalidChannel(event);
55+
}
56+
ForumHelpManager manager = new ForumHelpManager(postThread);
57+
if (isForumEligibleToBeUnreserved(event, postThread)) {
58+
manager.close(event, event.getUser().getIdLong() == postThread.getOwnerIdLong(),
59+
event.getOption("reason", null, OptionMapping::getAsString)
60+
);
61+
} else {
62+
Responses.warning(event, "Could not close this post", "You're not allowed to close this post.").queue();
63+
}
64+
}
65+
66+
private void handleTextBasedHelp(@NotNull SlashCommandInteractionEvent event, TextChannel channel) {
3967
HelpConfig config = Bot.getConfig().get(event.getGuild()).getHelpConfig();
4068
HelpChannelManager channelManager = new HelpChannelManager(config);
4169
User owner = channelManager.getReservedChannelOwner(channel);
42-
if (isEligibleToBeUnreserved(event, channel, config, owner)) {
70+
if (isTextEligibleToBeUnreserved(event, channel, config, owner)) {
4371
String reason = event.getOption("reason", null, OptionMapping::getAsString);
4472
event.deferReply(true).queue();
4573
channelManager.unreserveChannelByOwner(channel, owner, reason, event);
@@ -48,25 +76,35 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
4876
}
4977
}
5078

51-
private boolean isEligibleToBeUnreserved(SlashCommandInteractionEvent event, TextChannel channel, HelpConfig config, User owner) {
79+
private void replyInvalidChannel(CommandInteraction interaction) {
80+
Responses.warning(interaction, "Invalid Channel",
81+
"This command may only be used in either the text-channel-based help system, or in our new forum help system.")
82+
.queue();
83+
}
84+
85+
private boolean isForumEligibleToBeUnreserved(@NotNull SlashCommandInteractionEvent event, @NotNull ThreadChannel postThread) {
86+
return event.getUser().getIdLong() == postThread.getOwnerIdLong() || memberHasHelperRole(event) || memberHasStaffRole(event);
87+
}
88+
89+
private boolean isTextEligibleToBeUnreserved(SlashCommandInteractionEvent event, TextChannel channel, HelpConfig config, User owner) {
5290
return channelIsInReservedCategory(channel, config) &&
5391
(isUserWhoReservedChannel(event, owner) || memberHasHelperRole(event) || memberHasStaffRole(event));
5492
}
5593

56-
private boolean channelIsInReservedCategory(TextChannel channel, HelpConfig config) {
94+
private boolean channelIsInReservedCategory(@NotNull TextChannel channel, @NotNull HelpConfig config) {
5795
return config.getReservedChannelCategory().equals(channel.getParentCategory());
5896
}
5997

6098
private boolean isUserWhoReservedChannel(SlashCommandInteractionEvent event, User owner) {
6199
return owner != null && event.getUser().equals(owner);
62100
}
63101

64-
private boolean memberHasStaffRole(SlashCommandInteractionEvent event) {
102+
private boolean memberHasStaffRole(@NotNull SlashCommandInteractionEvent event) {
65103
return event.getMember() != null &&
66104
event.getMember().getRoles().contains(Bot.getConfig().get(event.getGuild()).getModerationConfig().getStaffRole());
67105
}
68106

69-
private boolean memberHasHelperRole(SlashCommandInteractionEvent event) {
107+
private boolean memberHasHelperRole(@NotNull SlashCommandInteractionEvent event) {
70108
return event.getMember() != null &&
71109
event.getMember().getRoles().contains(Bot.getConfig().get(event.getGuild()).getHelpConfig().getHelperRole());
72110
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package net.javadiscord.javabot.systems.help.forum;
2+
3+
import com.dynxsty.dih4jda.interactions.ComponentIdBuilder;
4+
import com.dynxsty.dih4jda.interactions.components.ButtonHandler;
5+
import net.dv8tion.jda.api.entities.Message;
6+
import net.dv8tion.jda.api.entities.channel.ChannelType;
7+
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
8+
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
9+
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
10+
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
11+
import net.dv8tion.jda.api.hooks.ListenerAdapter;
12+
import net.dv8tion.jda.api.interactions.components.ActionComponent;
13+
import net.dv8tion.jda.api.interactions.components.buttons.Button;
14+
import net.javadiscord.javabot.Bot;
15+
import net.javadiscord.javabot.data.config.GuildConfig;
16+
import net.javadiscord.javabot.data.config.guild.HelpConfig;
17+
import net.javadiscord.javabot.systems.help.HelpChannelManager;
18+
import net.javadiscord.javabot.systems.help.HelpExperienceService;
19+
import net.javadiscord.javabot.systems.help.model.HelpTransactionMessage;
20+
import net.javadiscord.javabot.util.ExceptionLogger;
21+
import net.javadiscord.javabot.util.Responses;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
import java.sql.SQLException;
25+
import java.util.*;
26+
27+
/**
28+
* Listens for all events releated to the forum help channel system.
29+
*/
30+
public class ForumHelpListener extends ListenerAdapter implements ButtonHandler {
31+
32+
/**
33+
* A static Map that holds all messages that was sent in a specific reserved forum channel.
34+
*/
35+
public static final Map<Long, List<Message>> HELP_POST_MESSAGES = new HashMap<>();
36+
37+
@Override
38+
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
39+
if (event.getMessage().getAuthor().isSystem() || event.getMessage().getAuthor().isBot()) {
40+
return;
41+
}
42+
// check for guild & channel type
43+
if (!event.isFromGuild() || event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) {
44+
return;
45+
}
46+
// get post & check parent channel
47+
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()) {
55+
return;
56+
}
57+
// cache messages
58+
List<Message> messages = new ArrayList<>();
59+
messages.add(event.getMessage());
60+
if (HELP_POST_MESSAGES.containsKey(post.getIdLong())) {
61+
messages.addAll(HELP_POST_MESSAGES.get(post.getIdLong()));
62+
}
63+
HELP_POST_MESSAGES.put(post.getIdLong(), messages);
64+
}
65+
66+
@Override
67+
public void handleButton(@NotNull ButtonInteractionEvent event, @NotNull Button button) {
68+
String[] id = ComponentIdBuilder.split(event.getComponentId());
69+
if (event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD
70+
|| event.getChannel().asThreadChannel().getParentChannel().getType() != ChannelType.FORUM) {
71+
Responses.error(event, "This button may only be used inside help forum threads.").queue();
72+
return;
73+
}
74+
ForumHelpManager manager = new ForumHelpManager(event.getChannel().asThreadChannel());
75+
switch (id[0]) {
76+
case ForumHelpManager.HELP_THANKS_IDENTIFIER -> handleHelpThanksInteraction(event, manager, id);
77+
}
78+
}
79+
80+
private void handleHelpThanksInteraction(@NotNull ButtonInteractionEvent event, @NotNull ForumHelpManager manager, String @NotNull [] id) {
81+
ThreadChannel post = manager.postThread();
82+
HelpConfig config = Bot.getConfig().get(event.getGuild()).getHelpConfig();
83+
switch (id[2]) {
84+
case "done" -> {
85+
List<Button> buttons = event.getMessage().getButtons();
86+
// immediately delete the message
87+
event.getMessage().delete().queue(s -> {
88+
// close post
89+
manager.close(event, false, null);
90+
// add experience
91+
try {
92+
HelpExperienceService service = new HelpExperienceService(Bot.getDataSource());
93+
Map<Long, Double> experience = HelpChannelManager.calculateExperience(HELP_POST_MESSAGES.get(post.getIdLong()), post.getOwnerIdLong(), config);
94+
for (Map.Entry<Long, Double> entry : experience.entrySet()) {
95+
service.performTransaction(entry.getKey(), entry.getValue(), HelpTransactionMessage.HELPED, config.getGuild());
96+
}
97+
} catch (SQLException e) {
98+
ExceptionLogger.capture(e, getClass().getName());
99+
}
100+
// thank all helpers
101+
buttons.stream().filter(ActionComponent::isDisabled)
102+
.filter(b -> b.getId() != null)
103+
.forEach(b -> manager.thankHelper(event, post, Long.parseLong(ComponentIdBuilder.split(b.getId())[2])));
104+
});
105+
106+
}
107+
case "cancel" -> event.getMessage().delete().queue();
108+
default -> event.editButton(event.getButton().asDisabled()).queue();
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)