Skip to content

Commit 20a80d7

Browse files
Merge pull request #266 from Java-Discord/dynxsty/experience_leaderboard
Experience Leaderboard
2 parents efa1cb7 + 93cc911 commit 20a80d7

File tree

8 files changed

+164
-21
lines changed

8 files changed

+164
-21
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import net.dv8tion.jda.api.hooks.ListenerAdapter;
88
import net.javadiscord.javabot.command.Responses;
99
import net.javadiscord.javabot.systems.help.HelpChannelInteractionManager;
10+
import net.javadiscord.javabot.systems.help.commands.subcommands.ExperienceLeaderboardSubcommand;
1011
import net.javadiscord.javabot.systems.moderation.ReportCommand;
1112
import net.javadiscord.javabot.systems.qotw.subcommands.questions_queue.AddQuestionSubcommand;
1213
import net.javadiscord.javabot.systems.qotw.submissions.SubmissionInteractionManager;
@@ -62,6 +63,7 @@ public void onButtonInteraction(ButtonInteractionEvent event) {
6263
if (event.getUser().isBot()) return;
6364
String[] id = event.getComponentId().split(":");
6465
switch (id[0]) {
66+
case "experience-leaderboard" -> ExperienceLeaderboardSubcommand.handleButtons(event, id);
6567
case "qotw-submission" -> SubmissionInteractionManager.handleButton(event, id);
6668
case "resolve-report" -> new ReportCommand().markAsResolved(event, id[1]);
6769
case "self-role" -> SelfRoleInteractionManager.handleButton(event, id);

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import lombok.RequiredArgsConstructor;
55
import lombok.extern.slf4j.Slf4j;
66
import net.dv8tion.jda.api.entities.Guild;
7+
import net.dv8tion.jda.api.entities.Role;
78
import net.javadiscord.javabot.Bot;
89
import net.javadiscord.javabot.systems.help.dao.HelpAccountRepository;
910
import net.javadiscord.javabot.systems.help.dao.HelpTransactionRepository;
1011
import net.javadiscord.javabot.systems.help.model.HelpAccount;
1112
import net.javadiscord.javabot.systems.help.model.HelpTransaction;
1213
import net.javadiscord.javabot.systems.help.model.HelpTransactionMessage;
14+
import net.javadiscord.javabot.util.Pair;
1315

1416
import java.sql.Connection;
1517
import java.sql.SQLException;
@@ -102,14 +104,14 @@ public HelpTransaction performTransaction(long recipient, double value, HelpTran
102104
}
103105

104106
private void checkExperienceRoles(Guild guild, HelpAccount account) {
105-
guild.retrieveMemberById(account.getUserId()).queue(member -> {
106-
Bot.config.get(guild).getHelp().getExperienceRoles().forEach((key, value) -> {
107-
if (key.equals(account.getCurrentExperienceGoal(guild).getKey())) {
108-
guild.addRoleToMember(member, guild.getRoleById(key)).queue();
109-
} else {
110-
guild.removeRoleFromMember(member, guild.getRoleById(key)).queue();
111-
}
112-
});
113-
}, e -> {});
107+
guild.retrieveMemberById(account.getUserId()).queue(member ->
108+
Bot.config.get(guild).getHelp().getExperienceRoles().forEach((key, value) -> {
109+
Pair<Role, Double> role = account.getCurrentExperienceGoal(guild);
110+
if (key.equals(role.first().getIdLong())) {
111+
guild.addRoleToMember(member, role.first()).queue();
112+
} else {
113+
guild.removeRoleFromMember(member, role.first()).queue();
114+
}
115+
}), e -> {});
114116
}
115117
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.javadiscord.javabot.systems.help.commands;
22

33
import net.javadiscord.javabot.command.DelegatingCommandHandler;
4+
import net.javadiscord.javabot.systems.help.commands.subcommands.ExperienceLeaderboardSubcommand;
45
import net.javadiscord.javabot.systems.help.commands.subcommands.HelpAccountSubcommand;
56
import net.javadiscord.javabot.systems.help.commands.subcommands.ThanksLeaderboardSubcommand;
67

@@ -15,5 +16,6 @@ public class HelpCommandHandler extends DelegatingCommandHandler {
1516
public HelpCommandHandler() {
1617
this.addSubcommand("account", new HelpAccountSubcommand());
1718
this.addSubcommand("thanks-leaderboard", new ThanksLeaderboardSubcommand());
19+
this.addSubcommand("experience-leaderboard", new ExperienceLeaderboardSubcommand());
1820
}
1921
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package net.javadiscord.javabot.systems.help.commands.subcommands;
2+
3+
import net.dv8tion.jda.api.EmbedBuilder;
4+
import net.dv8tion.jda.api.entities.*;
5+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
6+
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
7+
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
8+
import net.dv8tion.jda.api.interactions.components.ActionRow;
9+
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
10+
import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction;
11+
import net.dv8tion.jda.internal.interactions.component.ButtonImpl;
12+
import net.javadiscord.javabot.Bot;
13+
import net.javadiscord.javabot.command.ResponseException;
14+
import net.javadiscord.javabot.command.interfaces.SlashCommand;
15+
import net.javadiscord.javabot.data.h2db.DbHelper;
16+
import net.javadiscord.javabot.systems.help.dao.HelpAccountRepository;
17+
import net.javadiscord.javabot.systems.help.model.HelpAccount;
18+
import net.javadiscord.javabot.util.Pair;
19+
20+
import java.sql.SQLException;
21+
import java.util.List;
22+
23+
/**
24+
* Command that generates a leaderboard based on the help channel experience.
25+
*/
26+
public class ExperienceLeaderboardSubcommand implements SlashCommand {
27+
28+
private static final int PAGE_SIZE = 5;
29+
30+
@Override
31+
public ReplyCallbackAction handleSlashCommandInteraction(SlashCommandInteractionEvent event) throws ResponseException {
32+
int page = event.getOption("page", 1, OptionMapping::getAsInt);
33+
DbHelper.doDaoAction(HelpAccountRepository::new, dao ->
34+
event.getHook().sendMessageEmbeds(buildExperienceLeaderboard(event.getGuild(), dao, page))
35+
.addActionRows(buildPageControls(page))
36+
.queue());
37+
return event.deferReply(false);
38+
}
39+
40+
/**
41+
* Handles all Button Interactions that regard this command.
42+
*
43+
* @param event The {@link ButtonInteractionEvent} that was fired upon use.
44+
* @param id The component's id, split by ":".
45+
*/
46+
public static void handleButtons(ButtonInteractionEvent event, String[] id) {
47+
event.deferEdit().queue();
48+
DbHelper.doDaoAction(HelpAccountRepository::new, dao -> {
49+
int page = Integer.parseInt(id[2]);
50+
switch (id[1]) {
51+
case "left" -> page--;
52+
case "right" -> page++;
53+
}
54+
int maxPage = dao.getTotalAccounts() / PAGE_SIZE;
55+
if (page <= 0) page = maxPage;
56+
if (page > maxPage) page = 1;
57+
event.getHook().editOriginalEmbeds(buildExperienceLeaderboard(event.getGuild(), dao, page))
58+
.setActionRows(buildPageControls(page))
59+
.queue();
60+
});
61+
}
62+
63+
private static MessageEmbed buildExperienceLeaderboard(Guild guild, HelpAccountRepository dao, int page) throws SQLException {
64+
int maxPage = dao.getTotalAccounts() / PAGE_SIZE;
65+
List<HelpAccount> accounts = dao.getAccounts(Math.min(page, maxPage), PAGE_SIZE);
66+
EmbedBuilder builder = new EmbedBuilder()
67+
.setTitle("Experience Leaderboard")
68+
.setColor(Bot.config.get(guild).getSlashCommand().getDefaultColor())
69+
.setFooter(String.format("Page %s/%s", Math.min(page, maxPage), maxPage));
70+
accounts.forEach(account -> {
71+
Pair<Role, Double> currentRole = account.getCurrentExperienceGoal(guild);
72+
User user = guild.getJDA().getUserById(account.getUserId());
73+
builder.addField(
74+
String.format("**%s.** %s", (accounts.indexOf(account) + 1) + (page - 1) * PAGE_SIZE, user == null ? account.getUserId() : user.getAsTag()),
75+
String.format("%s`%.0f XP`\n", currentRole.first() != null ? currentRole.first().getAsMention() + ": " : "", account.getExperience()),
76+
false);
77+
});
78+
return builder.build();
79+
}
80+
81+
private static ActionRow buildPageControls(int currentPage) {
82+
return ActionRow.of(
83+
new ButtonImpl("experience-leaderboard:left:" + currentPage, "", ButtonStyle.SECONDARY, false, Emoji.fromUnicode("⬅")),
84+
new ButtonImpl("experience-leaderboard:right:" + currentPage, "", ButtonStyle.SECONDARY, false, Emoji.fromUnicode("➡"))
85+
);
86+
}
87+
}

src/main/java/net/javadiscord/javabot/systems/help/commands/subcommands/HelpAccountSubcommand.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import net.dv8tion.jda.api.EmbedBuilder;
44
import net.dv8tion.jda.api.entities.Guild;
55
import net.dv8tion.jda.api.entities.MessageEmbed;
6+
import net.dv8tion.jda.api.entities.Role;
67
import net.dv8tion.jda.api.entities.User;
78
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
89
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@@ -15,10 +16,10 @@
1516
import net.javadiscord.javabot.systems.help.HelpExperienceService;
1617
import net.javadiscord.javabot.systems.help.model.HelpAccount;
1718
import net.javadiscord.javabot.systems.help.model.HelpTransaction;
19+
import net.javadiscord.javabot.util.Pair;
1820
import net.javadiscord.javabot.util.StringUtils;
1921

2022
import java.sql.SQLException;
21-
import java.util.Map;
2223

2324
/**
2425
* Handles commands to show information about how a user has been thanked for
@@ -73,9 +74,9 @@ private String formatTransactionHistory(long userId) {
7374

7475
private String formatExperience(Guild guild, HelpAccount account) {
7576
double current = account.getExperience() - account.getLastExperienceGoal(guild);
76-
Map.Entry<Long, Double> role = account.getNextExperienceGoal(guild);
77-
double goal = role.getValue() - account.getLastExperienceGoal(guild);
78-
StringBuilder sb = new StringBuilder(String.format("<@&%s>: ", role.getKey()));
77+
Pair<Role, Double> role = account.getNextExperienceGoal(guild);
78+
double goal = role.second() - account.getLastExperienceGoal(guild);
79+
StringBuilder sb = new StringBuilder(String.format("%s: ", role.first().getAsMention()));
7980
if (goal > 0) {
8081
sb.append(String.format("%.2f XP / %.2f XP (%.2f%%)", current, goal, (current / goal) * 100))
8182
.append("\n")

src/main/java/net/javadiscord/javabot/systems/help/dao/HelpAccountRepository.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.sql.PreparedStatement;
99
import java.sql.ResultSet;
1010
import java.sql.SQLException;
11+
import java.util.ArrayList;
12+
import java.util.List;
1113
import java.util.Optional;
1214

1315
/**
@@ -66,6 +68,40 @@ public Optional<HelpAccount> getByUserId(long userId) throws SQLException {
6668
}
6769
}
6870

71+
/**
72+
* Gets a specified amount of {@link HelpAccount}s.
73+
*
74+
* @param page The page.
75+
* @param size The amount of {@link HelpAccount}s to return.
76+
* @return A {@link List} containing the specified amount of {@link HelpAccount}s.
77+
* @throws SQLException If an error occurs.
78+
*/
79+
public List<HelpAccount> getAccounts(int page, int size) throws SQLException {
80+
String sql = "SELECT * FROM help_account WHERE experience > 0 ORDER BY experience DESC LIMIT %d OFFSET %d";
81+
PreparedStatement stmt = con.prepareStatement(String.format(sql, size, (page * size) - size));
82+
ResultSet rs = stmt.executeQuery();
83+
List<HelpAccount> accounts = new ArrayList<>(size);
84+
while (rs.next()) {
85+
accounts.add(this.read(rs));
86+
}
87+
stmt.close();
88+
return accounts;
89+
}
90+
91+
/**
92+
* Gets the total amount of {@link HelpAccount}s stored in the database, that have more than 0 experience.
93+
*
94+
* @return The amount, as an {@link Integer}.
95+
* @throws SQLException If an error occurs.
96+
*/
97+
public int getTotalAccounts() throws SQLException {
98+
try (PreparedStatement s = con.prepareStatement("SELECT COUNT(*) FROM help_account WHERE experience > 0")) {
99+
ResultSet rs = s.executeQuery();
100+
if (rs.next()) return rs.getInt(1);
101+
return 0;
102+
}
103+
}
104+
69105
/**
70106
* Removes the specified amount of experience from all {@link HelpAccount}s.
71107
*

src/main/java/net/javadiscord/javabot/systems/help/model/HelpAccount.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import lombok.Data;
44
import net.dv8tion.jda.api.entities.Guild;
5+
import net.dv8tion.jda.api.entities.Role;
56
import net.javadiscord.javabot.Bot;
7+
import net.javadiscord.javabot.util.Pair;
68

79
import java.util.Comparator;
810
import java.util.Map;
@@ -24,17 +26,17 @@ public void updateExperience(double change) {
2426
* Tries to get the current experience role.
2527
*
2628
* @param guild The current {@link Guild}.
27-
* @return An {@link java.util.Map.Entry} that has the role's id as its key, and the experience needed as its value.
29+
* @return A {@link Pair} with both the Role, and the experience needed.
2830
*/
29-
public Map.Entry<Long, Double> getCurrentExperienceGoal(Guild guild) {
31+
public Pair<Role, Double> getCurrentExperienceGoal(Guild guild) {
3032
Map<Long, Double> experienceRoles = Bot.config.get(guild).getHelp().getExperienceRoles();
3133
Map.Entry<Long, Double> highestExperience = Map.entry(0L, 0.0);
3234
for (Map.Entry<Long, Double> entry : experienceRoles.entrySet()) {
3335
if (experience > entry.getValue() && entry.getValue() > highestExperience.getValue()) {
3436
highestExperience = entry;
3537
}
3638
}
37-
return highestExperience;
39+
return new Pair<>(guild.getRoleById(highestExperience.getKey()), highestExperience.getValue());
3840
}
3941

4042
/**
@@ -55,14 +57,16 @@ public double getLastExperienceGoal(Guild guild) {
5557
* Tries to get the next experience goal based on the current experience count.
5658
*
5759
* @param guild The current {@link Guild}.
58-
* @return An {@link java.util.Map.Entry} that has the role's id as its key, and the experience needed as its value.
60+
* @return A {@link Pair} with both the Role, and the experience needed.
5961
*/
60-
public Map.Entry<Long, Double> getNextExperienceGoal(Guild guild) {
62+
public Pair<Role, Double> getNextExperienceGoal(Guild guild) {
6163
Map<Long, Double> experienceRoles = Bot.config.get(guild).getHelp().getExperienceRoles();
62-
Optional<Map.Entry<Long, Double>> experienceOptional = experienceRoles.entrySet()
64+
Map.Entry<Long, Double> entry = experienceRoles.entrySet()
6365
.stream()
6466
.filter(r -> r.getValue() > experience)
65-
.findFirst();
66-
return experienceOptional.orElse(Map.entry(experienceRoles.keySet().stream().max(Comparator.naturalOrder()).orElse(0L), 0.0));
67+
.findFirst().orElseGet(() ->
68+
Map.entry(experienceRoles.keySet().stream().max(Comparator.naturalOrder()).orElse(0L), 0.0)
69+
);
70+
return new Pair<>(guild.getRoleById(entry.getKey()), entry.getValue());
6771
}
6872
}

src/main/resources/commands/slash/help.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
- name: thanks-leaderboard
2626
description: Show a leaderboard of the most thanked users.
2727

28+
# /help experience-leaderboard
29+
- name: experience-leaderboard
30+
description: Show a leaderboard of the users with the most help-experience.
31+
options:
32+
- name: page
33+
description: The page of results to show. By default it starts at 1.
34+
type: INTEGER
35+
required: false
36+
2837
handler: net.javadiscord.javabot.systems.help.commands.HelpCommandHandler
2938

3039

0 commit comments

Comments
 (0)