|
| 1 | +package vc.commands; |
| 2 | + |
| 3 | +import com.fasterxml.jackson.databind.ObjectMapper; |
| 4 | +import discord4j.core.event.domain.interaction.ButtonInteractionEvent; |
| 5 | +import discord4j.core.event.domain.interaction.DeferrableInteractionEvent; |
| 6 | +import discord4j.core.object.component.ActionRow; |
| 7 | +import discord4j.core.object.component.Button; |
| 8 | +import discord4j.core.object.entity.Message; |
| 9 | +import discord4j.core.object.reaction.ReactionEmoji; |
| 10 | +import discord4j.discordjson.possible.Possible; |
| 11 | +import org.slf4j.Logger; |
| 12 | +import reactor.core.publisher.Mono; |
| 13 | +import vc.api.model.ProfileData; |
| 14 | +import vc.util.PlayerLookup; |
| 15 | + |
| 16 | +import java.time.LocalDate; |
| 17 | +import java.util.ArrayList; |
| 18 | +import java.util.List; |
| 19 | +import java.util.Optional; |
| 20 | + |
| 21 | +// todo: I think we can codify this better by thinking more about the command inheritance structure |
| 22 | +// e.g. PaginatedCommand as a base class with standard behavior |
| 23 | +// Need to design how PlayerLookup traits are structured too as those need to also be on non-paginated commands |
| 24 | +public interface PaginatedButtonListener { |
| 25 | + Logger LOGGER = org.slf4j.LoggerFactory.getLogger(PaginatedButtonListener.class); |
| 26 | + String ID_PREFIX_DELIMITER = ":"; |
| 27 | + |
| 28 | + Mono<Message> handleButton(ButtonInteractionEvent event); |
| 29 | + |
| 30 | + @FunctionalInterface |
| 31 | + interface CommandResolver { |
| 32 | + Mono<Message> resolve(DeferrableInteractionEvent event, ProfileData identity, int page, LocalDate startDate, LocalDate endDate); |
| 33 | + } |
| 34 | + |
| 35 | + interface ErrorResolver { |
| 36 | + Mono<Message> error(DeferrableInteractionEvent event, String message); |
| 37 | + } |
| 38 | + |
| 39 | + default Mono<Message> paginatedPlayerLookupButtonHandler(ButtonInteractionEvent event, ObjectMapper objectMapper, String commandName, PlayerLookup playerLookup, CommandResolver resolver, ErrorResolver errorResolver) { |
| 40 | + var args = decodeButtonId(objectMapper, commandName, event.getCustomId()); |
| 41 | + return Mono.defer(() -> { |
| 42 | + Optional<ProfileData> playerIdentityOptional = playerLookup.getPlayerIdentity(args.playerName()); |
| 43 | + if (playerIdentityOptional.isEmpty()) { |
| 44 | + return errorResolver.error(event, "Unable to find player"); |
| 45 | + } |
| 46 | + ProfileData identity = playerIdentityOptional.get(); |
| 47 | + return resolver.resolve(event, identity, args.page(), args.startDate(), args.endDate()); |
| 48 | + }); |
| 49 | + } |
| 50 | + |
| 51 | + default Possible<List<ActionRow>> getButtonRow(ObjectMapper objectMapper, String commandName, int totalPageCount, int page, ProfileData identity, LocalDate startDate, LocalDate endDate) { |
| 52 | + return getButtons(objectMapper, commandName, totalPageCount, page, identity, startDate, endDate) |
| 53 | + .map(buttons -> List.of(ActionRow.of(buttons))); |
| 54 | + } |
| 55 | + |
| 56 | + default void addButtonSafe(String encodedId, ReactionEmoji emoji, List<Button> out) { |
| 57 | + if (encodedId.length() > 100) { |
| 58 | + LOGGER.warn("Button ID too long: {}", encodedId); |
| 59 | + return; |
| 60 | + } |
| 61 | + out.add(Button.secondary(encodedId, emoji)); |
| 62 | + } |
| 63 | + |
| 64 | + default Possible<List<Button>> getButtons(ObjectMapper objectMapper, String commandName, int totalPageCount, int page, ProfileData identity, LocalDate startDate, LocalDate endDate) { |
| 65 | + List<Button> buttons = new ArrayList<>(); |
| 66 | + if (page > 1) { |
| 67 | + var firstPageArgs = new PaginatedCommandArgs(identity.name(), 1, startDate, endDate); |
| 68 | + addButtonSafe(encodeButtonId(objectMapper, commandName, firstPageArgs), ReactionEmoji.unicode("⏮"), buttons); |
| 69 | + if (page - 1 > 1) { |
| 70 | + var prevPageArgs = new PaginatedCommandArgs(identity.name(), page - 1, startDate, endDate); |
| 71 | + addButtonSafe(encodeButtonId(objectMapper, commandName, prevPageArgs), ReactionEmoji.unicode("◀"), buttons); |
| 72 | + } |
| 73 | + } |
| 74 | + if (page < totalPageCount) { |
| 75 | + if (page + 1 < totalPageCount) { |
| 76 | + var nextPageArgs = new PaginatedCommandArgs(identity.name(), page + 1, startDate, endDate); |
| 77 | + addButtonSafe(encodeButtonId(objectMapper, commandName, nextPageArgs), ReactionEmoji.unicode("▶"), buttons); |
| 78 | + } |
| 79 | + var lastPageArgs = new PaginatedCommandArgs(identity.name(), totalPageCount, startDate, endDate); |
| 80 | + addButtonSafe(encodeButtonId(objectMapper, commandName, lastPageArgs), ReactionEmoji.unicode("⏭"), buttons); |
| 81 | + } |
| 82 | + return buttons.isEmpty() ? Possible.absent() : Possible.of(buttons); |
| 83 | + } |
| 84 | + |
| 85 | + default String encodeButtonId(ObjectMapper objectMapper, String commandName, PaginatedCommandArgs args) { |
| 86 | + try { |
| 87 | + return commandName + ID_PREFIX_DELIMITER + objectMapper.writeValueAsString(args); |
| 88 | + } catch (Exception e) { |
| 89 | + throw new RuntimeException(e); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + default PaginatedCommandArgs decodeButtonId(ObjectMapper objectMapper, String commandName, String id) { |
| 94 | + try { |
| 95 | + var json = id.substring(commandName.length() + ID_PREFIX_DELIMITER.length()); |
| 96 | + return objectMapper.readValue(json, PaginatedCommandArgs.class); |
| 97 | + } catch (Exception e) { |
| 98 | + throw new RuntimeException(e); |
| 99 | + } |
| 100 | + } |
| 101 | +} |
0 commit comments