Skip to content

Commit 9817bac

Browse files
committed
added help stats command
1 parent f49d430 commit 9817bac

File tree

2 files changed

+155
-10
lines changed

2 files changed

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

0 commit comments

Comments
 (0)