Skip to content

Commit 9dfda29

Browse files
committed
added help stats command
1 parent f49d430 commit 9dfda29

File tree

2 files changed

+156
-10
lines changed

2 files changed

+156
-10
lines changed

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

+2-10
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,7 @@
2323
import org.togetherjava.tjbot.features.filesharing.FileSharingMessageListener;
2424
import org.togetherjava.tjbot.features.github.GitHubCommand;
2525
import org.togetherjava.tjbot.features.github.GitHubReference;
26-
import org.togetherjava.tjbot.features.help.GuildLeaveCloseThreadListener;
27-
import org.togetherjava.tjbot.features.help.HelpSystemHelper;
28-
import org.togetherjava.tjbot.features.help.HelpThreadActivityUpdater;
29-
import org.togetherjava.tjbot.features.help.HelpThreadAutoArchiver;
30-
import org.togetherjava.tjbot.features.help.HelpThreadCommand;
31-
import org.togetherjava.tjbot.features.help.HelpThreadCreatedListener;
32-
import org.togetherjava.tjbot.features.help.HelpThreadLifecycleListener;
33-
import org.togetherjava.tjbot.features.help.HelpThreadMetadataPurger;
34-
import org.togetherjava.tjbot.features.help.MarkHelpThreadCloseInDBRoutine;
35-
import org.togetherjava.tjbot.features.help.PinnedNotificationRemover;
26+
import org.togetherjava.tjbot.features.help.*;
3627
import org.togetherjava.tjbot.features.javamail.RSSHandlerRoutine;
3728
import org.togetherjava.tjbot.features.jshell.JShellCommand;
3829
import org.togetherjava.tjbot.features.jshell.JShellEval;
@@ -192,6 +183,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
192183
features.add(new BookmarksCommand(bookmarksSystem));
193184
features.add(new ChatGptCommand(chatGptService, helpSystemHelper));
194185
features.add(new JShellCommand(jshellEval));
186+
features.add(new HelpThreadStatsCommand());
195187

196188
FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
197189
return blacklist.filterStream(features.stream(), Object::getClass).toList();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package org.togetherjava.tjbot.features.help;
2+
3+
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
4+
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
5+
import net.dv8tion.jda.api.entities.channel.forums.ForumTag;
6+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
7+
import net.dv8tion.jda.api.interactions.commands.OptionType;
8+
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
9+
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
10+
import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
11+
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction;
12+
13+
import org.togetherjava.tjbot.features.CommandVisibility;
14+
import org.togetherjava.tjbot.features.SlashCommandAdapter;
15+
16+
import java.time.OffsetDateTime;
17+
import java.util.*;
18+
import java.util.concurrent.TimeUnit;
19+
import java.util.function.Function;
20+
import java.util.stream.Collectors;
21+
import java.util.stream.Stream;
22+
23+
import static java.util.stream.Collectors.averagingDouble;
24+
import static java.util.stream.Collectors.toMap;
25+
26+
public class HelpThreadStatsCommand extends SlashCommandAdapter {
27+
28+
public static final String COMMAND_NAME = "help-thread-stats";
29+
public static final String PERIOD_OPTION = "period";
30+
public static final String DURATION_SUBCOMMAND = "duration";
31+
public static final String OPTIONAL_SUBCOMMAND_GROUP = "optional";
32+
private final Map<String, Subcommand> nameToSubcommand;
33+
34+
public HelpThreadStatsCommand() {
35+
super(COMMAND_NAME, "Display Help Thread Statistics", CommandVisibility.GUILD);
36+
OptionData durationOption =
37+
new OptionData(OptionType.STRING, PERIOD_OPTION, "period", false).setMinLength(1);
38+
SubcommandData duration = Subcommand.DURATION.toSubcommandData().addOptions(durationOption);
39+
SubcommandGroupData optionalCommands =
40+
new SubcommandGroupData(OPTIONAL_SUBCOMMAND_GROUP, "optional commands")
41+
.addSubcommands(duration);
42+
getData().addSubcommandGroups(optionalCommands);
43+
nameToSubcommand = streamSubcommands()
44+
.collect(Collectors.toMap(Subcommand::getCommandName, Function.identity()));
45+
}
46+
47+
@Override
48+
public void onSlashCommand(SlashCommandInteractionEvent event) {
49+
List<ForumChannel> forumChannels =
50+
Objects.requireNonNull(event.getGuild()).getForumChannels();
51+
Subcommand invokedSubcommand = nameToSubcommand.get(event.getSubcommandName());
52+
OffsetDateTime startDate = OffsetDateTime.MIN;
53+
if (Objects.nonNull(invokedSubcommand) && invokedSubcommand.equals(Subcommand.DURATION)
54+
&& Objects.nonNull(event.getOption(PERIOD_OPTION))) {
55+
startDate = OffsetDateTime.now().minusDays(event.getOption(PERIOD_OPTION).getAsLong());
56+
}
57+
ForumTag mostPopularTag = getMostPopularForumTag(forumChannels, startDate);
58+
Double averageNumberOfParticipants =
59+
getAverageNumberOfParticipantsPerThread(forumChannels, startDate);
60+
Integer totalNumberOfThreads =
61+
getThreadChannelsStream(forumChannels, startDate).toList().size();
62+
Long emptyThreads = getThreadsWithNoParticipants(forumChannels, startDate);
63+
Integer totalMessages = getTotalNumberOfMessages(forumChannels, startDate);
64+
Double averageNumberOfMessages = Double.valueOf(totalMessages) / totalNumberOfThreads;
65+
Double averageThreadLifecycle = getAverageThreadLifecycle(forumChannels, startDate);
66+
String statistics =
67+
"Most Popular Tag: %s%nAverage Number Of Participants: %.2f%nEmpty Threads: %s%nAverage Number Of Messages: %.2f%nAverage Thread Lifecycle: %.2f"
68+
.formatted(mostPopularTag.getName(), averageNumberOfParticipants, emptyThreads,
69+
averageNumberOfMessages, averageThreadLifecycle);
70+
event.reply(statistics).delay(2, TimeUnit.SECONDS).queue();
71+
}
72+
73+
private ForumTag getMostPopularForumTag(List<ForumChannel> forumChannels,
74+
OffsetDateTime startDate) {
75+
Map<ForumTag, Integer> tagCount = getThreadChannelsStream(forumChannels, startDate)
76+
.flatMap((threadChannel -> threadChannel.getAppliedTags().stream()))
77+
.collect(toMap(Function.identity(), tag -> 1, Integer::sum));
78+
return Collections.max(tagCount.entrySet(), Map.Entry.comparingByValue()).getKey();
79+
}
80+
81+
private Double getAverageNumberOfParticipantsPerThread(List<ForumChannel> forumChannels,
82+
OffsetDateTime startDate) {
83+
return getThreadChannelsStream(forumChannels, startDate)
84+
.collect(averagingDouble((ThreadChannel::getMemberCount)));
85+
}
86+
87+
private Long getThreadsWithNoParticipants(List<ForumChannel> forumChannels,
88+
OffsetDateTime startDate) {
89+
return getThreadChannelsStream(forumChannels, startDate)
90+
.filter(threadChannel -> threadChannel.getMemberCount() > 1)
91+
.count();
92+
}
93+
94+
private Integer getTotalNumberOfMessages(List<ForumChannel> forumChannels,
95+
OffsetDateTime startDate) {
96+
return getThreadChannelsStream(forumChannels, startDate)
97+
.mapToInt(ThreadChannel::getMessageCount)
98+
.sum();
99+
}
100+
101+
private Double getAverageThreadLifecycle(List<ForumChannel> forumChannels,
102+
OffsetDateTime startDate) {
103+
return getThreadChannelsStream(forumChannels, startDate).filter(ThreadChannel::isArchived)
104+
.mapToDouble(threadChannel -> calculateDurationInDays(
105+
threadChannel.getTimeArchiveInfoLastModified(), threadChannel.getTimeCreated()))
106+
.average()
107+
.orElse(0);
108+
}
109+
110+
private Double calculateDurationInDays(OffsetDateTime t1, OffsetDateTime t2) {
111+
long time1 = t1.toEpochSecond();
112+
long time2 = t2.toEpochSecond();
113+
return (time1 - time2) / 86400.0;
114+
}
115+
116+
private Stream<ThreadChannel> getThreadChannelsStream(List<ForumChannel> forumChannels,
117+
OffsetDateTime startDate) {
118+
return forumChannels.stream()
119+
.flatMap(forumChannel -> getAllThreadChannels(forumChannel).stream())
120+
.filter(threadChannel -> threadChannel.getTimeCreated().isAfter(startDate));
121+
}
122+
123+
private Set<ThreadChannel> getAllThreadChannels(ForumChannel forumChannel) {
124+
Set<ThreadChannel> threadChannels = new HashSet<>(forumChannel.getThreadChannels());
125+
Optional<ThreadChannelPaginationAction> publicThreadChannels =
126+
Optional.of(forumChannel.retrieveArchivedPublicThreadChannels());
127+
publicThreadChannels.ifPresent(threads -> threads.forEach(threadChannels::add));
128+
return threadChannels;
129+
}
130+
131+
private static Stream<Subcommand> streamSubcommands() {
132+
return Arrays.stream(Subcommand.values());
133+
}
134+
135+
enum Subcommand {
136+
DURATION(DURATION_SUBCOMMAND, "Set the duration");
137+
138+
private final String commandName;
139+
private final String description;
140+
141+
Subcommand(String commandName, String description) {
142+
this.commandName = commandName;
143+
this.description = description;
144+
}
145+
146+
String getCommandName() {
147+
return commandName;
148+
}
149+
150+
SubcommandData toSubcommandData() {
151+
return new SubcommandData(commandName, description);
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)