Skip to content

Commit 23b6d5f

Browse files
committed
/leaderboard command showing top helpers hall of fame
1 parent 9bb9883 commit 23b6d5f

1 file changed

Lines changed: 75 additions & 41 deletions

File tree

application/src/main/java/org/togetherjava/tjbot/features/leaderboard/LeaderboardCommand.java

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import net.dv8tion.jda.api.entities.User;
88
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
99
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
10+
import net.dv8tion.jda.api.interactions.InteractionHook;
1011
import org.slf4j.Logger;
1112
import org.slf4j.LoggerFactory;
1213

@@ -16,11 +17,13 @@
1617
import org.togetherjava.tjbot.features.tophelper.TopHelpersService;
1718
import org.togetherjava.tjbot.features.utils.Colors;
1819

20+
import java.time.OffsetDateTime;
1921
import java.util.Comparator;
2022
import java.util.HashMap;
2123
import java.util.List;
2224
import java.util.Map;
2325
import java.util.StringJoiner;
26+
import java.util.concurrent.ConcurrentHashMap;
2427
import java.util.regex.Pattern;
2528

2629
/**
@@ -41,6 +44,9 @@ public final class LeaderboardCommand extends SlashCommandAdapter {
4144

4245
private final Config config;
4346

47+
private final Map<Long, Map<Long, Integer>> winsByGuild = new ConcurrentHashMap<>();
48+
private final Map<Long, OffsetDateTime> lastFetchedPerGuild = new ConcurrentHashMap<>();
49+
4450
public LeaderboardCommand(Config config) {
4551
super(COMMAND_NAME, "Show the all-time top helpers leaderboard", CommandVisibility.GUILD);
4652
this.config = config;
@@ -71,58 +77,87 @@ public void onSlashCommand(SlashCommandInteractionEvent event) {
7177
return;
7278
}
7379

74-
hallOfFame.getIterableHistory().takeAsync(HISTORY_LIMIT).thenAccept(messages -> {
75-
Map<Long, Integer> winsByUser = countWins(messages);
80+
long guildId = guild.getIdLong();
81+
InteractionHook hook = event.getHook();
7682

77-
List<Map.Entry<Long, Integer>> sorted = winsByUser.entrySet()
78-
.stream()
79-
.sorted(Map.Entry.<Long, Integer>comparingByValue(Comparator.reverseOrder()))
80-
.limit(TOP_LIMIT)
81-
.toList();
83+
if (!winsByGuild.containsKey(guildId)) {
84+
hallOfFame.getIterableHistory().takeAsync(HISTORY_LIMIT).thenAccept(messages -> {
85+
Map<Long, Integer> wins = new HashMap<>();
86+
countWinsInto(messages, wins);
87+
winsByGuild.put(guildId, new ConcurrentHashMap<>(wins));
8288

83-
if (sorted.isEmpty()) {
84-
event.getHook().editOriginal("No top helper data found.").queue();
85-
return;
86-
}
89+
if (!messages.isEmpty()) {
90+
lastFetchedPerGuild.put(guildId, messages.getFirst().getTimeCreated());
91+
}
92+
93+
sendLeaderboard(guild, wins, hook);
94+
}).exceptionally(error -> {
95+
logger.error("Failed to read hall of fame channel", error);
96+
hook.editOriginal("Failed to read the hall of fame channel.").queue();
97+
return null;
98+
});
99+
} else {
100+
OffsetDateTime lastFetched = lastFetchedPerGuild.get(guildId);
101+
Map<Long, Integer> cachedWins = winsByGuild.get(guildId);
102+
103+
hallOfFame.getIterableHistory()
104+
.takeWhileAsync(HISTORY_LIMIT, msg -> msg.getTimeCreated().isAfter(lastFetched))
105+
.thenAccept(newMessages -> {
106+
if (!newMessages.isEmpty()) {
107+
countWinsInto(newMessages, cachedWins);
108+
lastFetchedPerGuild.put(guildId, newMessages.getFirst().getTimeCreated());
109+
}
110+
sendLeaderboard(guild, cachedWins, hook);
111+
})
112+
.exceptionally(error -> {
113+
logger.error("Failed to read hall of fame channel", error);
114+
hook.editOriginal("Failed to read the hall of fame channel.").queue();
115+
return null;
116+
});
117+
}
118+
}
87119

88-
List<Long> ids = sorted.stream().map(Map.Entry::getKey).toList();
120+
private void sendLeaderboard(Guild guild, Map<Long, Integer> wins, InteractionHook hook) {
121+
List<Map.Entry<Long, Integer>> sorted = wins.entrySet()
122+
.stream()
123+
.sorted(Map.Entry.<Long, Integer>comparingByValue(Comparator.reverseOrder()))
124+
.limit(TOP_LIMIT)
125+
.toList();
89126

90-
guild.retrieveMembersByIds(ids).onSuccess(members -> {
91-
Map<Long, Member> memberById = TopHelpersService.mapUserIdToMember(members);
127+
if (sorted.isEmpty()) {
128+
hook.editOriginal("No top helper data found.").queue();
129+
return;
130+
}
92131

93-
StringJoiner description = new StringJoiner("\n");
94-
for (int i = 0; i < sorted.size(); i++) {
95-
Map.Entry<Long, Integer> entry = sorted.get(i);
96-
Member member = memberById.get(entry.getKey());
97-
String name = TopHelpersService.getUsernameDisplay(member);
98-
int wins = entry.getValue();
99-
description.add("%s **%s** — %d month%s".formatted(rankPrefix(i), name, wins,
100-
wins == 1 ? "" : "s"));
101-
}
132+
List<Long> ids = sorted.stream().map(Map.Entry::getKey).toList();
102133

103-
EmbedBuilder embed = new EmbedBuilder().setTitle("🏆 Top Helpers — Hall of Fame")
104-
.setDescription(description.toString())
105-
.setColor(Colors.SUCCESS_COLOR)
106-
.setFooter("Times awarded Top Helper");
134+
guild.retrieveMembersByIds(ids).onSuccess(members -> {
135+
Map<Long, Member> memberById = TopHelpersService.mapUserIdToMember(members);
107136

108-
event.getHook().editOriginalEmbeds(embed.build()).queue();
137+
StringJoiner description = new StringJoiner("\n");
138+
for (int i = 0; i < sorted.size(); i++) {
139+
Map.Entry<Long, Integer> entry = sorted.get(i);
140+
Member member = memberById.get(entry.getKey());
141+
String name = TopHelpersService.getUsernameDisplay(member);
142+
int winCount = entry.getValue();
143+
description.add("%s **%s** — %d month%s".formatted(rankPrefix(i), name, winCount,
144+
winCount == 1 ? "" : "s"));
145+
}
109146

110-
}).onError(error -> {
111-
logger.error("Failed to retrieve members for leaderboard", error);
112-
event.getHook()
113-
.editOriginal("Failed to load member data, please try again.")
114-
.queue();
115-
});
147+
EmbedBuilder embed = new EmbedBuilder().setTitle("🏆 Top Helpers — Hall of Fame")
148+
.setDescription(description.toString())
149+
.setColor(Colors.SUCCESS_COLOR)
150+
.setFooter("Times awarded Top Helper");
151+
152+
hook.editOriginalEmbeds(embed.build()).queue();
116153

117-
}).exceptionally(error -> {
118-
logger.error("Failed to read hall of fame channel", error);
119-
event.getHook().editOriginal("Failed to read the hall of fame channel.").queue();
120-
return null;
154+
}).onError(error -> {
155+
logger.error("Failed to retrieve members for leaderboard", error);
156+
hook.editOriginal("Failed to load member data, please try again.").queue();
121157
});
122158
}
123159

124-
private static Map<Long, Integer> countWins(List<Message> messages) {
125-
Map<Long, Integer> wins = new HashMap<>();
160+
private static void countWinsInto(List<Message> messages, Map<Long, Integer> wins) {
126161
for (Message message : messages) {
127162
String content = message.getContentRaw();
128163
if (!content.toLowerCase().contains("top helper")) {
@@ -132,7 +167,6 @@ private static Map<Long, Integer> countWins(List<Message> messages) {
132167
wins.merge(user.getIdLong(), 1, Integer::sum);
133168
}
134169
}
135-
return wins;
136170
}
137171

138172
private static String rankPrefix(int zeroBasedIndex) {

0 commit comments

Comments
 (0)