11package 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+ }
0 commit comments