Skip to content

Commit 8e1ae7d

Browse files
committed
added help stats command
1 parent f49d430 commit 8e1ae7d

File tree

2 files changed

+158
-10
lines changed

2 files changed

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

0 commit comments

Comments
 (0)