Skip to content

Commit 20e393d

Browse files
committed
page scroll buttons
1 parent ab50ec7 commit 20e393d

File tree

9 files changed

+229
-23
lines changed

9 files changed

+229
-23
lines changed

src/main/java/vc/commands/ChatSearchCommand.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package vc.commands;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
35
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
6+
import discord4j.core.event.domain.interaction.DeferrableInteractionEvent;
47
import discord4j.core.object.command.ApplicationCommandInteractionOption;
58
import discord4j.core.object.command.ApplicationCommandInteractionOptionValue;
69
import discord4j.core.object.entity.Message;
@@ -9,6 +12,7 @@
912
import org.slf4j.Logger;
1013
import org.springframework.stereotype.Component;
1114
import reactor.core.publisher.Mono;
15+
import vc.api.model.ProfileDataImpl;
1216
import vc.openapi.handler.ChatsApi;
1317
import vc.openapi.model.ChatSearchResponse;
1418

@@ -19,12 +23,14 @@
1923
import static org.slf4j.LoggerFactory.getLogger;
2024

2125
@Component
22-
public class ChatSearchCommand implements SlashCommand {
26+
public class ChatSearchCommand implements SlashCommand, PaginatedButtonListener {
2327
private static final Logger LOGGER = getLogger(ChatSearchCommand.class);
2428
private final ChatsApi chatsApi;
29+
private final ObjectMapper objectMapper;
2530

26-
public ChatSearchCommand(final ChatsApi chatsApi) {
31+
public ChatSearchCommand(final ChatsApi chatsApi, final ObjectMapper objectMapper) {
2732
this.chatsApi = chatsApi;
33+
this.objectMapper = objectMapper;
2834
}
2935

3036
@Override
@@ -62,6 +68,10 @@ public Mono<Message> handle(final ChatInputInteractionEvent event) {
6268
.orElse(1);
6369
if (page <= 0)
6470
return error(event, "Page must be greater than 0");
71+
return resolve(event, word, page, startDate, endDate);
72+
}
73+
74+
public Mono<Message> resolve(DeferrableInteractionEvent event, String word, int page, LocalDate startDate, LocalDate endDate) {
6575
return Mono.defer(() -> {
6676
ChatSearchResponse response;
6777
try {
@@ -100,7 +110,16 @@ public Mono<Message> handle(final ChatInputInteractionEvent event) {
100110
.addField("Total", ""+response.getTotal(), true)
101111
.addField("Current Page", ""+page, true)
102112
.addField("Total Pages", ""+response.getPageCount(), true)
103-
.build());
113+
.build())
114+
.withComponents(getButtonRow(objectMapper, getName(), response.getPageCount(), page,
115+
// stuff the word into the profile data's player name field bc im lazy xdd
116+
new ProfileDataImpl(word, null), startDate, endDate));
104117
});
105118
}
119+
120+
@Override
121+
public Mono<Message> handleButton(final ButtonInteractionEvent event) {
122+
var args = decodeButtonId(objectMapper, getName(), event.getCustomId());
123+
return resolve(event, args.playerName(), args.page(), args.startDate(), args.endDate());
124+
}
106125
}

src/main/java/vc/commands/ChatsCommand.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package vc.commands;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
35
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
6+
import discord4j.core.event.domain.interaction.DeferrableInteractionEvent;
47
import discord4j.core.object.entity.Message;
58
import discord4j.core.spec.EmbedCreateSpec;
69
import discord4j.rest.util.Color;
@@ -19,12 +22,15 @@
1922
import static org.slf4j.LoggerFactory.getLogger;
2023

2124
@Component
22-
public class ChatsCommand extends PlayerLookupCommand {
25+
public class ChatsCommand extends PlayerLookupCommand implements PaginatedButtonListener {
2326
private static final Logger LOGGER = getLogger(ChatsCommand.class);
2427
private final ChatsApi chatsApi;
25-
public ChatsCommand(final ChatsApi chatsApi, final PlayerLookup playerLookup) {
28+
private final ObjectMapper objectMapper;
29+
30+
public ChatsCommand(final ChatsApi chatsApi, final PlayerLookup playerLookup, final ObjectMapper objectMapper) {
2631
super(playerLookup);
2732
this.chatsApi = chatsApi;
33+
this.objectMapper = objectMapper;
2834
}
2935

3036
@Override
@@ -37,7 +43,7 @@ public Mono<Message> handle(final ChatInputInteractionEvent event) {
3743
return resolveData(event, this::resolveChats);
3844
}
3945

40-
private Mono<Message> resolveChats(final ChatInputInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
46+
private Mono<Message> resolveChats(final DeferrableInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
4147
ChatsResponse chatsResponse = null;
4248
try {
4349
chatsResponse = chatsApi.chats(identity.uuid(), null, startDate, endDate, 25, page);
@@ -77,6 +83,12 @@ private Mono<Message> resolveChats(final ChatInputInteractionEvent event, final
7783
.addField("Current Page", ""+page, true)
7884
.addField("Total Pages", ""+chatsResponse.getPageCount(), true)
7985
.thumbnail(playerLookup.getAvatarURL(identity.uuid()).toString())
80-
.build());
86+
.build())
87+
.withComponents(getButtonRow(objectMapper, getName(), chatsResponse.getPageCount(), page, identity, startDate, endDate));
88+
}
89+
90+
@Override
91+
public Mono<Message> handleButton(final ButtonInteractionEvent event) {
92+
return paginatedPlayerLookupButtonHandler(event, objectMapper, getName(), playerLookup, this::resolveChats, this::error);
8193
}
8294
}

src/main/java/vc/commands/ConnectionsCommand.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package vc.commands;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
35
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
6+
import discord4j.core.event.domain.interaction.DeferrableInteractionEvent;
47
import discord4j.core.object.entity.Message;
58
import discord4j.core.spec.EmbedCreateSpec;
69
import discord4j.rest.util.Color;
@@ -19,13 +22,15 @@
1922
import static org.slf4j.LoggerFactory.getLogger;
2023

2124
@Component
22-
public class ConnectionsCommand extends PlayerLookupCommand {
25+
public class ConnectionsCommand extends PlayerLookupCommand implements PaginatedButtonListener {
2326
private static final Logger LOGGER = getLogger(ConnectionsCommand.class);
2427
private final ConnectionsApi connectionsApi;
28+
private final ObjectMapper objectMapper;
2529

26-
public ConnectionsCommand(final ConnectionsApi connectionsApi, final PlayerLookup playerLookup) {
30+
public ConnectionsCommand(final ConnectionsApi connectionsApi, final PlayerLookup playerLookup, final ObjectMapper objectMapper) {
2731
super(playerLookup);
2832
this.connectionsApi = connectionsApi;
33+
this.objectMapper = objectMapper;
2934
}
3035

3136
@Override
@@ -38,7 +43,7 @@ public Mono<Message> handle(final ChatInputInteractionEvent event) {
3843
return resolveData(event, this::resolveConnections);
3944
}
4045

41-
private Mono<Message> resolveConnections(final ChatInputInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
46+
private Mono<Message> resolveConnections(final DeferrableInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
4247
ConnectionsResponse connectionsResponse = null;
4348
try {
4449
connectionsResponse = connectionsApi.connections(identity.uuid(), null, startDate, endDate, 25, page);
@@ -78,6 +83,12 @@ private Mono<Message> resolveConnections(final ChatInputInteractionEvent event,
7883
.addField("Current Page", ""+page, true)
7984
.addField("Page Count", ""+connectionsResponse.getPageCount(), true)
8085
.thumbnail(playerLookup.getAvatarURL(identity.uuid()).toString())
81-
.build());
86+
.build())
87+
.withComponents(getButtonRow(objectMapper, getName(), connectionsResponse.getPageCount(), page, identity, startDate, endDate));
88+
}
89+
90+
@Override
91+
public Mono<Message> handleButton(final ButtonInteractionEvent event) {
92+
return paginatedPlayerLookupButtonHandler(event, objectMapper, getName(), playerLookup, this::resolveConnections, this::error);
8293
}
8394
}

src/main/java/vc/commands/DeathsCommand.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package vc.commands;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
35
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
6+
import discord4j.core.event.domain.interaction.DeferrableInteractionEvent;
47
import discord4j.core.object.entity.Message;
58
import discord4j.core.spec.EmbedCreateSpec;
69
import discord4j.rest.util.Color;
@@ -19,13 +22,15 @@
1922
import static org.slf4j.LoggerFactory.getLogger;
2023

2124
@Component
22-
public class DeathsCommand extends PlayerLookupCommand {
25+
public class DeathsCommand extends PlayerLookupCommand implements PaginatedButtonListener {
2326
private static final Logger LOGGER = getLogger(DeathsCommand.class);
2427
private final DeathsApi deathsApi;
28+
private final ObjectMapper objectMapper;
2529

26-
public DeathsCommand(final DeathsApi deathsApi, final PlayerLookup playerLookup) {
30+
public DeathsCommand(final DeathsApi deathsApi, final PlayerLookup playerLookup, final ObjectMapper objectMapper) {
2731
super(playerLookup);
2832
this.deathsApi = deathsApi;
33+
this.objectMapper = objectMapper;
2934
}
3035

3136
@Override
@@ -38,7 +43,7 @@ public Mono<Message> handle(final ChatInputInteractionEvent event) {
3843
return resolveData(event, this::resolveDeaths);
3944
}
4045

41-
private Mono<Message> resolveDeaths(final ChatInputInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
46+
private Mono<Message> resolveDeaths(final DeferrableInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
4247
DeathsResponse deathsResponse = null;
4348
try {
4449
deathsResponse = deathsApi.deaths(identity.uuid(), null, startDate, endDate, 25, page);
@@ -78,6 +83,12 @@ private Mono<Message> resolveDeaths(final ChatInputInteractionEvent event, final
7883
.addField("Page", ""+page, true)
7984
.addField("Page Count", ""+deathsResponse.getPageCount(), true)
8085
.thumbnail(playerLookup.getAvatarURL(identity.uuid()).toString())
81-
.build());
86+
.build())
87+
.withComponents(getButtonRow(objectMapper, getName(), deathsResponse.getPageCount(), page, identity, startDate, endDate));
88+
}
89+
90+
@Override
91+
public Mono<Message> handleButton(final ButtonInteractionEvent event) {
92+
return paginatedPlayerLookupButtonHandler(event, objectMapper, getName(), playerLookup, this::resolveDeaths, this::error);
8293
}
8394
}

src/main/java/vc/commands/KillsCommand.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package vc.commands;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
35
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
6+
import discord4j.core.event.domain.interaction.DeferrableInteractionEvent;
47
import discord4j.core.object.entity.Message;
58
import discord4j.core.spec.EmbedCreateSpec;
69
import discord4j.rest.util.Color;
@@ -19,13 +22,15 @@
1922
import static org.slf4j.LoggerFactory.getLogger;
2023

2124
@Component
22-
public class KillsCommand extends PlayerLookupCommand {
25+
public class KillsCommand extends PlayerLookupCommand implements PaginatedButtonListener {
2326
private static final Logger LOGGER = getLogger(KillsCommand.class);
2427
private final DeathsApi deathsApi;
28+
private final ObjectMapper objectMapper;
2529

26-
public KillsCommand(final DeathsApi deathsApi, final PlayerLookup playerLookup) {
30+
public KillsCommand(final DeathsApi deathsApi, final PlayerLookup playerLookup, final ObjectMapper objectMapper) {
2731
super(playerLookup);
2832
this.deathsApi = deathsApi;
33+
this.objectMapper = objectMapper;
2934
}
3035

3136
@Override
@@ -38,7 +43,7 @@ public Mono<Message> handle(final ChatInputInteractionEvent event) {
3843
return resolveData(event, this::resolveKills);
3944
}
4045

41-
private Mono<Message> resolveKills(final ChatInputInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
46+
private Mono<Message> resolveKills(final DeferrableInteractionEvent event, final ProfileData identity, int page, LocalDate startDate, LocalDate endDate) {
4247
KillsResponse killsResponse = null;
4348
try {
4449
killsResponse = deathsApi.kills(identity.uuid(), null, startDate, endDate, 25, page);
@@ -78,6 +83,12 @@ private Mono<Message> resolveKills(final ChatInputInteractionEvent event, final
7883
.addField("Page", ""+page, true)
7984
.addField("Page Count", ""+killsResponse.getPageCount(), true)
8085
.thumbnail(playerLookup.getAvatarURL(identity.uuid()).toString())
81-
.build());
86+
.build())
87+
.withComponents(getButtonRow(objectMapper, getName(), killsResponse.getPageCount(), page, identity, startDate, endDate));
88+
}
89+
90+
@Override
91+
public Mono<Message> handleButton(final ButtonInteractionEvent event) {
92+
return paginatedPlayerLookupButtonHandler(event, objectMapper, getName(), playerLookup, this::resolveKills, this::error);
8293
}
8394
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package vc.commands;
2+
3+
import org.springframework.lang.Nullable;
4+
5+
import java.time.LocalDate;
6+
7+
public record PaginatedCommandArgs(String playerName, int page, @Nullable LocalDate startDate, @Nullable LocalDate endDate) {
8+
}

src/main/java/vc/commands/SlashCommand.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package vc.commands;
22

33
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
4+
import discord4j.core.event.domain.interaction.DeferrableInteractionEvent;
45
import discord4j.core.object.command.ApplicationCommandInteractionOption;
56
import discord4j.core.object.command.ApplicationCommandInteractionOptionValue;
67
import discord4j.core.object.entity.Message;
@@ -22,7 +23,7 @@ public interface SlashCommand {
2223

2324
Mono<Message> handle(ChatInputInteractionEvent event);
2425

25-
default Mono<Message> error(ChatInputInteractionEvent event, final String message) {
26+
default Mono<Message> error(DeferrableInteractionEvent event, final String message) {
2627
return event.createFollowup()
2728
.withEmbeds(EmbedCreateSpec.builder()
2829
.title("Error")

0 commit comments

Comments
 (0)