Skip to content

Commit 2c82dd6

Browse files
authored
Merge pull request #275 from Java-Discord/moon/leaderboard
Moved leaderboards to single command
2 parents 944f063 + 4126ce8 commit 2c82dd6

File tree

10 files changed

+305
-284
lines changed

10 files changed

+305
-284
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import net.dv8tion.jda.api.hooks.ListenerAdapter;
77
import net.javadiscord.javabot.command.Responses;
88
import net.javadiscord.javabot.systems.help.HelpChannelInteractionManager;
9-
import net.javadiscord.javabot.systems.help.commands.subcommands.ExperienceLeaderboardSubcommand;
9+
import net.javadiscord.javabot.systems.commands.subcommands.leaderboard.ExperienceLeaderboardSubcommand;
1010
import net.javadiscord.javabot.systems.moderation.ReportCommand;
1111
import net.javadiscord.javabot.systems.qotw.subcommands.questions_queue.AddQuestionSubcommand;
1212
import net.javadiscord.javabot.systems.qotw.submissions.SubmissionInteractionManager;
Lines changed: 12 additions & 254 deletions
Original file line numberDiff line numberDiff line change
@@ -1,262 +1,20 @@
11
package net.javadiscord.javabot.systems.commands;
22

3-
import net.dv8tion.jda.api.EmbedBuilder;
4-
import net.dv8tion.jda.api.entities.Guild;
5-
import net.dv8tion.jda.api.entities.Member;
6-
import net.dv8tion.jda.api.entities.MessageEmbed;
7-
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
8-
import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction;
9-
import net.javadiscord.javabot.Bot;
10-
import net.javadiscord.javabot.command.interfaces.SlashCommand;
11-
import net.javadiscord.javabot.systems.qotw.dao.QuestionPointsRepository;
12-
import net.javadiscord.javabot.systems.qotw.model.QOTWAccount;
13-
import net.javadiscord.javabot.util.ImageGenerationUtils;
14-
15-
import javax.imageio.ImageIO;
16-
import java.awt.*;
17-
import java.awt.image.BufferedImage;
18-
import java.io.ByteArrayInputStream;
19-
import java.io.ByteArrayOutputStream;
20-
import java.io.IOException;
21-
import java.sql.SQLException;
22-
import java.time.Instant;
23-
import java.util.List;
24-
import java.util.Objects;
25-
26-
import static net.javadiscord.javabot.Bot.imageCache;
3+
import net.javadiscord.javabot.command.DelegatingCommandHandler;
4+
import net.javadiscord.javabot.systems.commands.subcommands.leaderboard.ExperienceLeaderboardSubcommand;
5+
import net.javadiscord.javabot.systems.commands.subcommands.leaderboard.ThanksLeaderboardSubcommand;
6+
import net.javadiscord.javabot.systems.commands.subcommands.leaderboard.QOTWLeaderboardSubcommand;
277

288
/**
29-
* Command that generates a leaderboard based on QOTW-Points.
9+
* Single command housing all leaderboards.
3010
*/
31-
public class LeaderboardCommand extends ImageGenerationUtils implements SlashCommand {
32-
33-
private final Color BACKGROUND_COLOR = Color.decode("#011E2F");
34-
private final Color PRIMARY_COLOR = Color.WHITE;
35-
private final Color SECONDARY_COLOR = Color.decode("#414A52");
36-
37-
private final int DISPLAY_COUNT = 10;
38-
39-
private final int MARGIN = 40;
40-
private final int WIDTH = 3000;
41-
42-
@Override
43-
public ReplyCallbackAction handleSlashCommandInteraction(SlashCommandInteractionEvent event) {
44-
Bot.asyncPool.submit(() -> {
45-
try {
46-
var action = event.getHook().sendMessageEmbeds(buildLeaderboardRankEmbed(event.getMember()));
47-
byte[] array;
48-
if (imageCache.isCached(getCacheName())) {
49-
array = getOutputStreamFromImage(imageCache.getCachedImage(getCacheName())).toByteArray();
50-
} else {
51-
array = generateLeaderboard(event.getGuild()).toByteArray();
52-
}
53-
action.addFile(new ByteArrayInputStream(array), Instant.now().getEpochSecond() + ".png").queue();
54-
} catch (IOException e) {
55-
e.printStackTrace();
56-
}
57-
});
58-
return event.deferReply();
59-
}
60-
61-
/**
62-
* Gets the given user's QOTW-Rank.
63-
*
64-
* @param member The member whose rank should be returned.
65-
* @param guild The current guild.
66-
* @return The QOTW-Rank as an integer.
67-
*/
68-
public static int getQOTWRank(Member member, Guild guild) {
69-
try (var con = Bot.dataSource.getConnection()) {
70-
var repo = new QuestionPointsRepository(con);
71-
var accounts = repo.getAllAccountsSortedByPoints();
72-
return accounts.stream()
73-
.map(QOTWAccount::getUserId)
74-
.map(guild::getMemberById)
75-
.filter(Objects::nonNull)
76-
.toList().indexOf(member) + 1;
77-
} catch (SQLException e) {
78-
e.printStackTrace();
79-
return 0;
80-
}
81-
}
82-
83-
/**
84-
* Gets the top N members based on their QOTW-Points.
85-
*
86-
* @param n The amount of members to get.
87-
* @param guild The current guild.
88-
* @return A {@link List} with the top member ids.
89-
*/
90-
private List<Member> getTopNMembers(int n, Guild guild) {
91-
try (var con = Bot.dataSource.getConnection()) {
92-
var repo = new QuestionPointsRepository(con);
93-
var accounts = repo.getAllAccountsSortedByPoints();
94-
return accounts.stream()
95-
.map(QOTWAccount::getUserId)
96-
.map(guild::getMemberById)
97-
.filter(Objects::nonNull)
98-
.limit(n)
99-
.toList();
100-
} catch (SQLException e) {
101-
e.printStackTrace();
102-
return List.of();
103-
}
104-
}
105-
106-
/**
107-
* Gets the given user's QOTW-Points.
108-
*
109-
* @param userId The id of the user.
110-
* @return The user's total QOTW-Points
111-
*/
112-
private long getPoints(long userId) {
113-
try (var con = Bot.dataSource.getConnection()) {
114-
var repo = new QuestionPointsRepository(con);
115-
return repo.getAccountByUserId(userId).getPoints();
116-
} catch (SQLException e) {
117-
e.printStackTrace();
118-
return 0;
119-
}
120-
}
121-
122-
/**
123-
* Builds the Leaderboard Rank {@link MessageEmbed}.
124-
*
125-
* @param member The member which executed the command.
126-
* @return A {@link MessageEmbed} object.
127-
*/
128-
private MessageEmbed buildLeaderboardRankEmbed(Member member) {
129-
var rank = getQOTWRank(member, member.getGuild());
130-
var rankSuffix = switch (rank % 10) {
131-
case 1 -> "st";
132-
case 2 -> "nd";
133-
case 3 -> "rd";
134-
default -> "th";
135-
};
136-
var points = getPoints(member.getIdLong());
137-
var pointsText = points == 1 ? "point" : "points";
138-
return new EmbedBuilder()
139-
.setAuthor(member.getUser().getAsTag(), null, member.getEffectiveAvatarUrl())
140-
.setTitle("Question of the Week Leaderboard")
141-
.setDescription(String.format("You're currently in `%s` place with `%s` %s.",
142-
rank + rankSuffix, points, pointsText))
143-
.setTimestamp(Instant.now())
144-
.build();
145-
}
146-
147-
/**
148-
* Draws a single "user card" at the given coordinates.
149-
*
150-
* @param g2d Graphics object.
151-
* @param guild The current Guild.
152-
* @param member The member.
153-
* @param y The y-position.
154-
* @param left Whether the card should be drawn left or right.
155-
* @throws IOException If an error occurs.
156-
*/
157-
private void drawUserCard(Graphics2D g2d, Guild guild, Member member, int y, boolean left) throws IOException {
158-
var card = getResourceImage("images/leaderboard/LBCard.png");
159-
int x;
160-
if (left) {
161-
x = MARGIN * 5;
162-
} else {
163-
x = WIDTH - (MARGIN * 5) - card.getWidth();
164-
}
165-
166-
167-
g2d.drawImage(getImageFromUrl(member.getUser().getEffectiveAvatarUrl() + "?size=4096"), x + 185, y + 43, 200, 200, null);
168-
var displayName = member.getUser().getAsTag();
169-
g2d.drawImage(card, x, y, null);
170-
g2d.setColor(PRIMARY_COLOR);
171-
g2d.setFont(getResourceFont("fonts/Uni-Sans-Heavy.ttf", 65).orElseThrow());
172-
173-
int stringWidth = g2d.getFontMetrics().stringWidth(displayName);
174-
while (stringWidth > 750) {
175-
var currentFont = g2d.getFont();
176-
var newFont = currentFont.deriveFont(currentFont.getSize() - 1F);
177-
g2d.setFont(newFont);
178-
stringWidth = g2d.getFontMetrics().stringWidth(displayName);
179-
}
180-
g2d.drawString(displayName, x + 430, y + 130);
181-
g2d.setColor(SECONDARY_COLOR);
182-
g2d.setFont(getResourceFont("fonts/Uni-Sans-Heavy.ttf", 72).orElseThrow());
183-
184-
var points = getPoints(member.getIdLong());
185-
String text = points + (points > 1 ? " points" : " point");
186-
String rank = "#" + getQOTWRank(member, member.getGuild());
187-
g2d.drawString(text, x + 430, y + 210);
188-
int stringLength = (int) g2d.getFontMetrics().getStringBounds(rank, g2d).getWidth();
189-
int start = 185 / 2 - stringLength / 2;
190-
g2d.drawString(rank, x + start, y + 173);
191-
}
192-
193-
/**
194-
* Draws and constructs the leaderboard image.
195-
*
196-
* @param guild The current guild.
197-
* @return The finished image as a {@link ByteArrayInputStream}.
198-
* @throws IOException If an error occurs.
199-
*/
200-
private ByteArrayOutputStream generateLeaderboard(Guild guild) throws IOException {
201-
var logo = getResourceImage("images/leaderboard/Logo.png");
202-
var card = getResourceImage("images/leaderboard/LBCard.png");
203-
204-
var topMembers = getTopNMembers(DISPLAY_COUNT, guild);
205-
int height = (logo.getHeight() + MARGIN * 3) +
206-
(getResourceImage("images/leaderboard/LBCard.png").getHeight() + MARGIN) * (Math.min(DISPLAY_COUNT, topMembers.size()) / 2) + MARGIN;
207-
var image = new BufferedImage(WIDTH, height, BufferedImage.TYPE_INT_RGB);
208-
Graphics2D g2d = image.createGraphics();
209-
210-
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
211-
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
212-
g2d.setPaint(BACKGROUND_COLOR);
213-
g2d.fillRect(0, 0, WIDTH, height);
214-
g2d.drawImage(logo, WIDTH / 2 - logo.getWidth() / 2, MARGIN, null);
215-
216-
boolean left = true;
217-
int y = logo.getHeight() + 3 * MARGIN;
218-
for (var m : topMembers) {
219-
drawUserCard(g2d, guild, m, y, left);
220-
left = !left;
221-
if (left) y = y + card.getHeight() + MARGIN;
222-
}
223-
g2d.dispose();
224-
imageCache.removeCachedImagesByKeyword("qotw_leaderboard");
225-
imageCache.cacheImage(getCacheName(), image);
226-
return getOutputStreamFromImage(image);
227-
}
228-
229-
/**
230-
* Builds the cached image's name.
231-
*
232-
* @return The image's cache name.
233-
*/
234-
private String getCacheName() {
235-
try (var con = Bot.dataSource.getConnection()) {
236-
var repo = new QuestionPointsRepository(con);
237-
var accounts = repo.getAllAccountsSortedByPoints().stream().limit(DISPLAY_COUNT).toList();
238-
StringBuilder sb = new StringBuilder("qotw_leaderboard_");
239-
for (var a : accounts) {
240-
sb.append(":").append(a.getUserId())
241-
.append(":").append(a.getPoints());
242-
}
243-
return sb.toString();
244-
} catch (SQLException e) {
245-
e.printStackTrace();
246-
return "";
247-
}
248-
}
249-
11+
public class LeaderboardCommand extends DelegatingCommandHandler {
25012
/**
251-
* Retrieves the image's {@link ByteArrayOutputStream}.
252-
*
253-
* @param image The image.
254-
* @return The image's {@link ByteArrayOutputStream}.
255-
* @throws IOException If an error occurs.
13+
* Leaderboard command handler.
25614
*/
257-
private ByteArrayOutputStream getOutputStreamFromImage(BufferedImage image) throws IOException {
258-
var outputStream = new ByteArrayOutputStream();
259-
ImageIO.write(image, "png", outputStream);
260-
return outputStream;
15+
public LeaderboardCommand() {
16+
this.addSubcommand("qotw", new QOTWLeaderboardSubcommand());
17+
this.addSubcommand("thanks", new ThanksLeaderboardSubcommand());
18+
this.addSubcommand("help-xp", new ExperienceLeaderboardSubcommand());
26119
}
262-
}
20+
}

src/main/java/net/javadiscord/javabot/systems/commands/ProfileCommand.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import net.javadiscord.javabot.systems.help.HelpExperienceService;
1414
import net.javadiscord.javabot.systems.moderation.ModerationService;
1515
import net.javadiscord.javabot.systems.moderation.warn.model.Warn;
16+
import net.javadiscord.javabot.systems.commands.subcommands.leaderboard.QOTWLeaderboardSubcommand;
1617
import net.javadiscord.javabot.systems.qotw.dao.QuestionPointsRepository;
1718

1819
import java.sql.Connection;
@@ -63,7 +64,7 @@ private MessageEmbed buildProfileEmbed(Member member, Connection con) throws SQL
6364
.addField("QOTW-Points", String.format("`%s point%s (#%s)`",
6465
points,
6566
points == 1 ? "" : "s",
66-
LeaderboardCommand.getQOTWRank(member, member.getGuild())), true)
67+
QOTWLeaderboardSubcommand.getQOTWRank(member, member.getGuild())), true)
6768
.addField("Total Help XP", String.format("`%.2f XP`", helpXP), true)
6869
.addField("Server joined", String.format("<t:%s:R>", member.getTimeJoined().toEpochSecond()), true)
6970
.addField("Account created", String.format("<t:%s:R>", member.getUser().getTimeCreated().toEpochSecond()), true);

src/main/java/net/javadiscord/javabot/systems/help/commands/subcommands/ExperienceLeaderboardSubcommand.java renamed to src/main/java/net/javadiscord/javabot/systems/commands/subcommands/leaderboard/ExperienceLeaderboardSubcommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package net.javadiscord.javabot.systems.help.commands.subcommands;
1+
package net.javadiscord.javabot.systems.commands.subcommands.leaderboard;
22

33
import net.dv8tion.jda.api.EmbedBuilder;
44
import net.dv8tion.jda.api.entities.*;

0 commit comments

Comments
 (0)