diff --git a/menu/src/main/java/com/jagrosh/jdautilities/menu/ButtonMenu.java b/menu/src/main/java/com/jagrosh/jdautilities/menu/ButtonMenu.java index ed2a768b..205f95f7 100644 --- a/menu/src/main/java/com/jagrosh/jdautilities/menu/ButtonMenu.java +++ b/menu/src/main/java/com/jagrosh/jdautilities/menu/ButtonMenu.java @@ -87,7 +87,17 @@ public void display(Message message) { initialize(message.editMessage(getMessage())); } - + + @Override + public void displayWithButtons(MessageChannel channel){ + + } + + @Override + public void displayWithReactions(MessageChannel channel){ + + } + // Initializes the ButtonMenu using a Message RestAction // This is either through editing a previously existing Message // OR through sending a new one to a TextChannel. @@ -154,7 +164,7 @@ private Message getMessage() if(text!=null) mbuilder.append(text); if(description!=null) - mbuilder.setEmbed(new EmbedBuilder().setColor(color).setDescription(description).build()); + mbuilder.setEmbeds(new EmbedBuilder().setColor(color).setDescription(description).build()); return mbuilder.build(); } diff --git a/menu/src/main/java/com/jagrosh/jdautilities/menu/EmbedPaginator.java b/menu/src/main/java/com/jagrosh/jdautilities/menu/EmbedPaginator.java index f2e80e05..567d5be8 100644 --- a/menu/src/main/java/com/jagrosh/jdautilities/menu/EmbedPaginator.java +++ b/menu/src/main/java/com/jagrosh/jdautilities/menu/EmbedPaginator.java @@ -42,7 +42,9 @@ * @author Andre_601 */ public class EmbedPaginator extends Menu{ - + + private final PaginationHandler paginationHandler; + private final BiFunction text; private final Consumer finalAction; private final boolean waitOnSinglePage; @@ -74,34 +76,73 @@ protected EmbedPaginator(EventWaiter waiter, Set users, Set roles, l this.leftText = leftText; this.rightText = rightText; this.allowTextInput = allowTextInput; + + this.paginationHandler = new PaginationHandler.Builder(this) + .allowPageWrap(wrapPageEnds) + .allowSinglePage(waitOnSinglePage) + .setBulkSkipLeft(bulkSkipNumber > 1 ? PaginationHandler.EMOJI_BULK_SKIP_LEFT : null) + .setBulkSkipRight(bulkSkipNumber > 1 ? PaginationHandler.EMOJI_BULK_SKIP_RIGHT : null) + .setEmbeds(embeds) + .setFinalAction(finalAction) + .build(); } /** - * Begins pagination on page 1 as a new {@link net.dv8tion.jda.api.entities.Message Message} - * in the provided {@link net.dv8tion.jda.api.entities.MessageChannel MessageChannel}. + * {@inheritDoc} * * @param channel * The MessageChannel to send the new Message to + * + * @deprecated Deprecated in favour of {@link #displayWithButtons(MessageChannel)} and + * {@link #displayWithReactions(MessageChannel)}. */ @Override + @Deprecated public void display(MessageChannel channel) { - paginate(channel, 1); + paginationHandler.displayWithReactions(channel, 1); } /** - * Begins pagination on page 1 displaying this by editing the provided - * {@link net.dv8tion.jda.api.entities.Message Message}. + * {@inheritDoc} * * @param message * The Message to display the Menu in + * + * @deprecated Deprecated in favour of {@link #displayWithButtons(MessageChannel)} and + * {@link #displayWithReactions(MessageChannel)}. */ @Override + @Deprecated public void display(Message message) { - paginate(message, 1); + paginationHandler.displayWithReactions(message, 1); } - + + /** + * {@inheritDoc} + * + * @param channel + * The {@link net.dv8tion.jda.api.entities.MessageChannel MessageChannel} to display the menu in. + */ + @Override + public void displayWithButtons(MessageChannel channel) + { + paginationHandler.displayWithButtons(channel, 1); + } + + /** + * {@inheritDoc} + * + * @param channel + * The {@link net.dv8tion.jda.api.entities.MessageChannel MessageChannel} to display the menu in. + */ + @Override + public void displayWithReactions(MessageChannel channel) + { + paginationHandler.displayWithReactions(channel, 1); + } + /** * Begins pagination as a new {@link net.dv8tion.jda.api.entities.Message Message} * in the provided {@link net.dv8tion.jda.api.entities.MessageChannel MessageChannel}, starting @@ -114,12 +155,7 @@ public void display(Message message) */ public void paginate(MessageChannel channel, int pageNum) { - if(pageNum < 1) - pageNum = 1; - else if(pageNum > embeds.size()) - pageNum = embeds.size(); - Message msg = renderPage(pageNum); - initialize(channel.sendMessage(msg), pageNum); + paginationHandler.displayWithReactions(channel, pageNum); } /** @@ -134,188 +170,7 @@ else if(pageNum > embeds.size()) */ public void paginate(Message message, int pageNum) { - if(pageNum < 1) - pageNum = 1; - else if(pageNum > embeds.size()) - pageNum = embeds.size(); - Message msg = renderPage(pageNum); - initialize(message.editMessage(msg), pageNum); - } - - private void initialize(RestAction action, int pageNum) - { - action.queue(m -> { - if(embeds.size()>1) - { - if(bulkSkipNumber > 1) - m.addReaction(BIG_LEFT).queue(); - m.addReaction(LEFT).queue(); - m.addReaction(STOP).queue(); - if(bulkSkipNumber > 1) - m.addReaction(RIGHT).queue(); - m.addReaction(bulkSkipNumber > 1 ? BIG_RIGHT : RIGHT) - .queue(v -> pagination(m, pageNum), t -> pagination(m, pageNum)); - } - else if(waitOnSinglePage) - { - m.addReaction(STOP).queue(v -> pagination(m, pageNum), t -> pagination(m, pageNum)); - } - else - { - finalAction.accept(m); - } - }); - } - - private void pagination(Message message, int pageNum) - { - if(allowTextInput || (leftText != null && rightText != null)) - paginationWithTextInput(message, pageNum); - else - paginationWithoutTextInput(message, pageNum); - } - - private void paginationWithTextInput(Message message, int pageNum) - { - waiter.waitForEvent(GenericMessageEvent.class, event -> { - if(event instanceof MessageReactionAddEvent) - return checkReaction((MessageReactionAddEvent) event, message.getIdLong()); - else if(event instanceof MessageReceivedEvent) - { - MessageReceivedEvent mre = (MessageReceivedEvent) event; - if(!mre.getChannel().equals(message.getChannel())) - return false; - String rawContent = mre.getMessage().getContentRaw().trim(); - if(leftText != null && rightText != null) - { - if(rawContent.equalsIgnoreCase(leftText) || rawContent.equalsIgnoreCase(rightText)) - return isValidUser(mre.getAuthor(), mre.isFromGuild() ? mre.getGuild() : null); - } - - if(allowTextInput) - { - try { - int i = Integer.parseInt(rawContent); - - if(1 <= i && i <= embeds.size() && i != pageNum) - return isValidUser(mre.getAuthor(), mre.isFromGuild() ? mre.getGuild() : null); - } catch(NumberFormatException ignored) {} - } - } - return false; - }, event -> { - if(event instanceof MessageReactionAddEvent) - { - handleMessageReactionAddAction((MessageReactionAddEvent)event, message, pageNum); - } - else - { - MessageReceivedEvent mre = (MessageReceivedEvent) event; - String rawContent = mre.getMessage().getContentRaw().trim(); - - int pages = embeds.size(); - final int targetPage; - - if(leftText != null && rawContent.equalsIgnoreCase(leftText) && (1 < pageNum || wrapPageEnds)) - targetPage = pageNum - 1 < 1 && wrapPageEnds ? pages : pageNum - 1; - else if(rightText != null && rawContent.equalsIgnoreCase(rightText) && (pageNum < pages || wrapPageEnds)) - targetPage = pageNum + 1 > pages && wrapPageEnds ? 1 : pageNum + 1; - else - targetPage = Integer.parseInt(rawContent); - - message.editMessage(renderPage(targetPage)).queue(m -> pagination(m, targetPage)); - mre.getMessage().delete().queue(v -> {}, t -> {}); - } - }, timeout, unit, () -> finalAction.accept(message)); - } - - private void paginationWithoutTextInput(Message message, int pageNum) - { - waiter.waitForEvent(MessageReactionAddEvent.class, - event -> checkReaction(event, message.getIdLong()), - event -> handleMessageReactionAddAction(event, message, pageNum), - timeout, unit, () -> finalAction.accept(message)); - } - - private boolean checkReaction(MessageReactionAddEvent event, long messageId) - { - if(event.getMessageIdLong() != messageId) - return false; - switch(event.getReactionEmote().getName()) - { - case LEFT: - case STOP: - case RIGHT: - return isValidUser(event.getUser(), event.isFromGuild() ? event.getGuild() : null); - case BIG_LEFT: - case BIG_RIGHT: - return bulkSkipNumber > 1 && isValidUser(event.getUser(), event.isFromGuild() ? event.getGuild() : null); - default: - return false; - } - } - - private void handleMessageReactionAddAction(MessageReactionAddEvent event, Message message, int pageNum) - { - int newPageNum = pageNum; - int pages = embeds.size(); - switch(event.getReaction().getReactionEmote().getName()) - { - case LEFT: - if(newPageNum == 1 && wrapPageEnds) - newPageNum = pages + 1; - if(newPageNum > 1) - newPageNum--; - break; - case RIGHT: - if(newPageNum == pages && wrapPageEnds) - newPageNum = 0; - if(newPageNum < pages) - newPageNum++; - break; - case BIG_LEFT: - if(newPageNum > 1 || wrapPageEnds) - { - for(int i = 1; (newPageNum > 1 || wrapPageEnds) && i < bulkSkipNumber; i++) - { - if(newPageNum == 1 && wrapPageEnds) - newPageNum = pages + 1; - newPageNum--; - } - } - break; - case BIG_RIGHT: - if(newPageNum < pages || wrapPageEnds) - { - for(int i = 1; (newPageNum < pages || wrapPageEnds) && i < bulkSkipNumber; i++) - { - if(newPageNum == pages && wrapPageEnds) - newPageNum = 0; - newPageNum++; - } - } - break; - case STOP: - finalAction.accept(message); - return; - } - - try { - event.getReaction().removeReaction(event.getUser()).queue(); - } catch(PermissionException ignored) {} - - int n = newPageNum; - message.editMessage(renderPage(newPageNum)).queue(m -> pagination(m, n)); - } - - private Message renderPage(int pageNum) - { - MessageBuilder mbuilder = new MessageBuilder(); - MessageEmbed membed = this.embeds.get(pageNum-1); - mbuilder.setEmbed(membed); - if(text != null) - mbuilder.append(text.apply(pageNum, embeds.size())); - return mbuilder.build(); + paginationHandler.displayWithReactions(message, pageNum); } /** diff --git a/menu/src/main/java/com/jagrosh/jdautilities/menu/Menu.java b/menu/src/main/java/com/jagrosh/jdautilities/menu/Menu.java index 5fd8b39c..2684f439 100644 --- a/menu/src/main/java/com/jagrosh/jdautilities/menu/Menu.java +++ b/menu/src/main/java/com/jagrosh/jdautilities/menu/Menu.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import com.jagrosh.jdautilities.commons.waiter.EventWaiter; import net.dv8tion.jda.api.entities.*; @@ -75,7 +76,11 @@ protected Menu(EventWaiter waiter, Set users, Set roles, long timeou * * @param channel * The MessageChannel to display this Menu in + * + * @deprecated Deprecated in favour of {@link #displayWithButtons(MessageChannel)} and + * {@link #displayWithReactions(MessageChannel)}. */ + @Deprecated public abstract void display(MessageChannel channel); /** @@ -86,14 +91,36 @@ protected Menu(EventWaiter waiter, Set users, Set roles, long timeou * * @param message * The Message to display this Menu as + * + * @deprecated Deprecated in favour of {@link #displayWithButtons(MessageChannel)} and + * {@link #displayWithReactions(MessageChannel)}. */ + @Deprecated public abstract void display(Message message); - + + /** + * Displays this menu in the provided {@link net.dv8tion.jda.api.entities.MessageChannel MessageChannel}. + *
The menu will use Discord's Buttons to navigate through the different pages. + * + * @param channel + * The MessageChannel to display this menu in. + */ + public abstract void displayWithButtons(MessageChannel channel); + + /** + * Displays this menu in the provided {@link net.dv8tion.jda.api.entities.MessageChannel MessageChannel}. + *
The menu will use Reactions to navigate through the different pages. + * + * @param channel + * The MessageChannel to display this menu in. + */ + public abstract void displayWithReactions(MessageChannel channel); + /** * Checks to see if the provided {@link net.dv8tion.jda.api.entities.User User} - * is valid to interact with this Menu.

+ * is valid to interact with this Menu. * - * This is a shortcut for {@link Menu#isValidUser(User, Guild)} where the Guild + *

This is a shortcut for {@link Menu#isValidUser(User, Guild)} where the Guild * is {@code null}. * * @param user @@ -125,6 +152,9 @@ protected boolean isValidUser(User user) * *

  • If the Guild is {@code null}, or if the User is not a member on the Guild, this * will return {@code false}.
  • + * + *
  • If the Member was null, this will return {@code false}. A member will always be null + * if Caching and the GUILD_MEMBERS intent aren't enabled.
  • * *
  • Finally, the determination will be if the User on the provided Guild has any * of the builder-specified Roles.
  • @@ -142,8 +172,10 @@ protected boolean isValidUser(User user) * * @return {@code true} if the User is valid, {@code false} otherwise. */ - protected boolean isValidUser(User user, @Nullable Guild guild) + protected boolean isValidUser(@Nullable User user, @Nullable Guild guild) { + if(user == null) + return false; if(user.isBot()) return false; if(users.isEmpty() && roles.isEmpty()) @@ -152,8 +184,12 @@ protected boolean isValidUser(User user, @Nullable Guild guild) return true; if(guild == null || !guild.isMember(user)) return false; + + Member member = guild.getMember(user); + if(member == null) + return false; - return guild.getMember(user).getRoles().stream().anyMatch(roles::contains); + return member.getRoles().stream().anyMatch(roles::contains); } /** diff --git a/menu/src/main/java/com/jagrosh/jdautilities/menu/OrderedMenu.java b/menu/src/main/java/com/jagrosh/jdautilities/menu/OrderedMenu.java index 52e138f7..03c7dc45 100644 --- a/menu/src/main/java/com/jagrosh/jdautilities/menu/OrderedMenu.java +++ b/menu/src/main/java/com/jagrosh/jdautilities/menu/OrderedMenu.java @@ -146,6 +146,16 @@ public void display(Message message) throw new PermissionException("Must be able to add reactions if not allowing typed input!"); initialize(message.editMessage(getMessage())); } + + @Override + public void displayWithButtons(MessageChannel channel){ + + } + + @Override + public void displayWithReactions(MessageChannel channel){ + + } // Initializes the OrderedMenu using a Message RestAction // This is either through editing a previously existing Message diff --git a/menu/src/main/java/com/jagrosh/jdautilities/menu/PaginationHandler.java b/menu/src/main/java/com/jagrosh/jdautilities/menu/PaginationHandler.java new file mode 100644 index 00000000..78a85ccb --- /dev/null +++ b/menu/src/main/java/com/jagrosh/jdautilities/menu/PaginationHandler.java @@ -0,0 +1,639 @@ +package com.jagrosh.jdautilities.menu; + +import com.jagrosh.jdautilities.commons.waiter.EventWaiter; +import net.dv8tion.jda.api.MessageBuilder; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.Button; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.internal.utils.Checks; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class PaginationHandler +{ + public static final String EMOJI_BULK_SKIP_LEFT = "\u23EA"; // ⏪ + public static final String EMOJI_LEFT = "\u25C0"; // ◀ + public static final String EMOJI_STOP = "\u23F9"; // ⏹ + public static final String EMOJI_RIGHT = "\u25B6"; // ▶ + public static final String EMOJI_BULK_SKIP_RIGHT = "\u23E9"; // ⏩ + + public static final String BUTTON_ID_BULK_SKIP_LEFT = "bulk_skip_left"; + public static final String BUTTON_ID_LEFT = "left"; + public static final String BUTTON_ID_STOP = "stop"; + public static final String BUTTON_ID_RIGHT = "right"; + public static final String BUTTON_ID_BULK_SKIP_RIGHT = "bulk_skip_right"; + + private final Menu menu; + private final int totalPages; + private final int bulkSkip; + private final boolean allowSinglePage; + private final boolean allowPageWrap; + private final long time; + private final TimeUnit unit; + private final Consumer finalAction; + + private final PaginatorButton bulkSkipLeftButton; + private final PaginatorButton leftButton; + private final PaginatorButton stopButton; + private final PaginatorButton rightButton; + private final PaginatorButton bulkSkipRightButton; + + private final String bulkSkipLeftName; + private final String leftName; + private final String stopName; + private final String rightName; + private final String bulkSkipRightName; + + private final List embeds; + + public PaginationHandler() throws IllegalAccessException{ + throw new IllegalAccessException("Cannot initialize PaginationHandler directly! Use the Builder instead!"); + } + + private PaginationHandler(Menu menu, int totalPages, int bulkSkip, boolean allowSinglePage, + boolean allowPageWrap, long time, TimeUnit unit, Consumer finalAction, + PaginatorButton bulkSkipLeftButton, PaginatorButton leftButton, PaginatorButton stopButton, + PaginatorButton rightButton, PaginatorButton bulkSkipRightButton, List embeds) + { + this.menu = menu; + this.totalPages = totalPages; + this.bulkSkip = bulkSkip; + this.allowSinglePage = allowSinglePage; + this.allowPageWrap = allowPageWrap; + this.time = time; + this.unit = unit; + this.finalAction = finalAction; + + this.bulkSkipLeftButton = bulkSkipLeftButton; + this.leftButton = leftButton; + this.stopButton = stopButton; + this.rightButton = rightButton; + this.bulkSkipRightButton = bulkSkipRightButton; + + this.bulkSkipLeftName = bulkSkipLeftButton.getName(); + this.leftName = leftButton.getName(); + this.stopName = stopButton.getName(); + this.rightName = rightButton.getName(); + this.bulkSkipRightName = bulkSkipRightButton.getName(); + + this.embeds = embeds; + } + + public void displayWithReactions(MessageChannel channel, int pageNumber) + { + channel.sendMessage(renderMessage(pageNumber)).queue(m -> displayWithReactions(m, pageNumber)); + } + + public void displayWithReactions(Message message, int pageNumber) + { + Checks.notEmpty(embeds, "List of embeds cannot be null or empty."); + pageNumber = setPageNumberLimit(pageNumber); + + initReactions(message, pageNumber); + } + + public void displayWithButtons(MessageChannel channel, int pageNumber) + { + Checks.notEmpty(embeds, "List of embeds cannot be null or empty."); + + pageNumber = setPageNumberLimit(pageNumber); + + initButtons(channel, pageNumber); + } + + private int setPageNumberLimit(int pageNumber) + { + if(pageNumber < 1) + return 1; + + return Math.min(pageNumber, totalPages); + + } + + private void initReactions(Message message, int pageNumber) + { + if(totalPages > 1) + { + Set> reactions = new HashSet<>(); + if(bulkSkip > 1 && bulkSkipLeftButton != null) + reactions.add(message.addReaction(bulkSkipLeftButton.toString())); + + reactions.add(message.addReaction(leftButton.toString())); + reactions.add(message.addReaction(stopButton.toString())); + reactions.add(message.addReaction(rightButton.toString())); + + if(bulkSkip > 1 && bulkSkipRightButton != null) + reactions.add(message.addReaction(bulkSkipRightButton.toString())); + + RestAction.allOf(reactions).queue(); + } + else if(allowSinglePage) + { + message.addReaction(stopButton.toString()).queue(); + } + else + { + finalAction.accept(message); + } + } + + private void initButtons(MessageChannel channel, int pageNumber) + { + MessageBuilder builder = new MessageBuilder(renderMessage(pageNumber)); + + if(totalPages > 1) + { + Set