66import xyz .dynxsty .dih4jda .interactions .components .ButtonHandler ;
77import net .dv8tion .jda .api .EmbedBuilder ;
88import net .dv8tion .jda .api .entities .Guild ;
9+ import net .dv8tion .jda .api .entities .Member ;
910import net .dv8tion .jda .api .entities .MessageEmbed ;
10- import net .dv8tion .jda .api .entities .MessageEmbed .Field ;
1111import net .dv8tion .jda .api .entities .Role ;
12- import net .dv8tion .jda .api .entities .User ;
1312import net .dv8tion .jda .api .events .interaction .command .SlashCommandInteractionEvent ;
1413import net .dv8tion .jda .api .events .interaction .component .ButtonInteractionEvent ;
1514import net .dv8tion .jda .api .interactions .commands .OptionMapping ;
1817import net .dv8tion .jda .api .interactions .commands .build .SubcommandData ;
1918import net .dv8tion .jda .api .interactions .components .ActionRow ;
2019import net .dv8tion .jda .api .interactions .components .buttons .Button ;
20+ import net .dv8tion .jda .api .utils .FileUpload ;
21+ import net .dv8tion .jda .api .utils .messages .MessageEditBuilder ;
2122import net .javadiscord .javabot .annotations .AutoDetectableComponentHandler ;
2223import net .javadiscord .javabot .systems .help .dao .HelpAccountRepository ;
2324import net .javadiscord .javabot .systems .help .dao .HelpTransactionRepository ;
2829import org .jetbrains .annotations .NotNull ;
2930import org .springframework .dao .DataAccessException ;
3031
32+ import java .io .IOException ;
3133import java .util .List ;
34+ import java .util .Objects ;
3235import java .util .concurrent .ExecutorService ;
3336import java .util .function .BiFunction ;
3437
3740 */
3841@ AutoDetectableComponentHandler ("experience-leaderboard" )
3942public class ExperienceLeaderboardSubcommand extends SlashCommand .Subcommand implements ButtonHandler {
40- private static final int PAGE_SIZE = 5 ;
43+ /**
44+ * prefix contained in the image cache.
45+ */
46+ public static final String CACHE_PREFIX = "xp_leaderboard" ;
47+ private static final int PAGE_SIZE = 10 ;
48+
4149 private final ExecutorService asyncPool ;
4250 private final HelpAccountRepository helpAccountRepository ;
4351 private final HelpTransactionRepository helpTransactionRepository ;
@@ -90,58 +98,74 @@ public void handleButton(@NotNull ButtonInteractionEvent event, Button button) {
9098 if (page > maxPage ) {
9199 page = 1 ;
92100 }
101+ Pair <MessageEmbed , FileUpload > messageInfo = buildExperienceLeaderboard (event .getGuild (), page , type );
93102 event .getHook ()
94- .editOriginalEmbeds ( buildExperienceLeaderboard ( event . getGuild (), page , type ))
103+ .editOriginal ( new MessageEditBuilder (). setEmbeds ( messageInfo . first ()). setAttachments ( messageInfo . second ()). build ( ))
95104 .setComponents (buildPageControls (page , type )).queue ();
96- } catch (DataAccessException e ) {
105+ } catch (DataAccessException | IOException e ) {
97106 ExceptionLogger .capture (e , ExperienceLeaderboardSubcommand .class .getSimpleName ());
98107 }
99108 });
100109 }
101110
102111
103- private @ NotNull MessageEmbed buildExperienceLeaderboard (Guild guild , int page , LeaderboardType type ) throws DataAccessException {
112+ private @ NotNull Pair < MessageEmbed , FileUpload > buildExperienceLeaderboard (Guild guild , int page , LeaderboardType type ) throws DataAccessException , IOException {
104113 return switch (type ) {
105114 case TOTAL -> buildGenericExperienceLeaderboard (page , helpAccountRepository .getTotalAccounts (),
106115 "total Leaderboard of help experience" ,
107116 helpAccountRepository ::getAccounts , (position , account ) -> {
108117 Pair <Role , Double > currentRole = account .getCurrentExperienceGoal (guild );
109- return buildEmbed (guild , position , account .getExperience (), account .getUserId (), currentRole .first () != null ? currentRole .first ().getAsMention () + ": " : "" );
118+ return createUserData (guild , position , account .getExperience (), account .getUserId (), currentRole .first () != null ? currentRole .first ().getAsMention () + ": " : "" );
110119 });
111120 case MONTH -> buildGenericExperienceLeaderboard (page , helpTransactionRepository .getNumberOfUsersWithHelpXPInLastMonth (),
112121 """
113122 help experience leaderboard from the last 30 days
114123 This leaderboard does not include experience decay.
115124 """ ,
116125 helpTransactionRepository ::getTotalTransactionWeightsInLastMonth , (position , xpInfo ) -> {
117- return buildEmbed (guild , position , (double ) xpInfo .second (), xpInfo .first (), "" );
126+ return createUserData (guild , position , (double ) xpInfo .second (), xpInfo .first (), "" );
118127 });
119128 };
120129 }
121130
122- private <T > @ NotNull MessageEmbed buildGenericExperienceLeaderboard (int page , int totalAccounts , String description ,
123- BiFunction <Integer , Integer , List <T >> accountsReader , BiFunction <Integer , T , MessageEmbed .Field > fieldExtractor ) throws DataAccessException {
131+ private <T > @ NotNull Pair <MessageEmbed , FileUpload > buildGenericExperienceLeaderboard (int page , int totalAccounts , String description ,
132+ BiFunction <Integer , Integer , List <T >> accountsReader , BiFunction <Integer , T , UserData > fieldExtractor ) throws DataAccessException , IOException {
133+
124134 int maxPage = totalAccounts / PAGE_SIZE ;
125135 int actualPage = Math .max (1 , Math .min (page , maxPage ));
126136 List <T > accounts = accountsReader .apply (actualPage , PAGE_SIZE );
137+
127138 EmbedBuilder builder = new EmbedBuilder ()
128139 .setTitle ("Experience Leaderboard" )
129140 .setDescription (description )
130141 .setColor (Responses .Type .DEFAULT .getColor ())
131142 .setFooter (String .format ("Page %s/%s" , actualPage , maxPage ));
132- for (int i = 0 ; i < accounts .size (); i ++) {
133- int position = (i + 1 ) + (actualPage - 1 ) * PAGE_SIZE ;
134- builder .addField (fieldExtractor .apply (position , accounts .get (i )));
135- }
136- return builder .build ();
143+
144+ String pageCachePrefix = CACHE_PREFIX + "_" + page ;
145+ String cacheName = pageCachePrefix + "_" + accounts .hashCode ();
146+ byte [] bytes = LeaderboardCreator .attemptLoadFromCache (cacheName , ()->{
147+ try (LeaderboardCreator creator = new LeaderboardCreator (accounts .size (), null )){
148+ for (int i = 0 ; i < accounts .size (); i ++) {
149+ int position = (i + 1 ) + (actualPage - 1 ) * PAGE_SIZE ;
150+ UserData userInfo = fieldExtractor .apply (position , accounts .get (i ));
151+ creator .drawLeaderboardEntry (userInfo .member (), userInfo .displayName (), userInfo .xp (), position );
152+ }
153+ return creator .getImageBytes (cacheName , pageCachePrefix );
154+ }
155+ });
156+ builder .setImage ("attachment://leaderboard.png" );
157+ return new Pair <MessageEmbed , FileUpload >(builder .build (), FileUpload .fromData (bytes , "leaderboard.png" ));
137158 }
138159
139- private Field buildEmbed (Guild guild , Integer position , double experience , long userId , String prefix ) {
140- User user = guild .getJDA ().getUserById (userId );
141- return new MessageEmbed .Field (
142- String .format ("**%s.** %s" , position , user == null ? userId : UserUtils .getUserTag (user )),
143- String .format ("%s`%.0f XP`\n " , prefix , experience ),
144- false );
160+ private UserData createUserData (Guild guild , Integer position , double experience , long userId , String prefix ) {
161+ Member member = guild .getMemberById (userId );
162+ String displayName ;
163+ if (member == null ) {
164+ displayName = String .valueOf (userId );
165+ } else {
166+ displayName = UserUtils .getUserTag (member .getUser ());
167+ }
168+ return new UserData (member , displayName , (long )experience );
145169 }
146170
147171 @ Contract ("_ -> new" )
@@ -159,10 +183,12 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
159183 event .deferReply ().queue ();
160184 asyncPool .execute (() -> {
161185 try {
162- event .getHook ().sendMessageEmbeds (buildExperienceLeaderboard (event .getGuild (), page , type ))
186+ Pair <MessageEmbed , FileUpload > messageInfo = buildExperienceLeaderboard (event .getGuild (), page , type );
187+ event .getHook ().sendMessageEmbeds (messageInfo .first ())
188+ .addFiles (messageInfo .second ())
163189 .setComponents (buildPageControls (page , type ))
164190 .queue ();
165- }catch (DataAccessException e ) {
191+ }catch (DataAccessException | IOException e ) {
166192 ExceptionLogger .capture (e , ExperienceLeaderboardSubcommand .class .getSimpleName ());
167193 }
168194 });
@@ -171,4 +197,10 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
171197 private enum LeaderboardType {
172198 TOTAL , MONTH
173199 }
200+
201+ private record UserData (Member member , String displayName , long xp ) {
202+ UserData {
203+ Objects .requireNonNull (displayName );
204+ }
205+ }
174206}
0 commit comments