Skip to content

Commit 4b62c0c

Browse files
committed
refactor(paginate): render pagination via components v2 container
1 parent ae5679b commit 4b62c0c

1 file changed

Lines changed: 58 additions & 49 deletions

File tree

src/abstraction/command.rs

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
use crate::abstraction::components_v2::{self, Status};
12
use crate::built_info;
23
use lazy_static::lazy_static;
3-
use poise::CreateReply;
4-
use poise::serenity_prelude::CreateActionRow;
54
use reqwest::header::HeaderMap;
6-
use serenity::all::RoleId;
7-
use serenity::builder::{
8-
CreateButton, CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage,
5+
use serenity::all::{
6+
ButtonStyle, ComponentInteractionCollector, CreateButton, CreateComponent,
7+
CreateInteractionResponse, CreateInteractionResponseMessage, MessageFlags, RoleId,
98
};
109
use std::env;
1110
use std::sync::Arc;
@@ -28,7 +27,7 @@ impl Default for CommandData {
2827
"Authorization",
2928
format!(
3029
"Bearer {}",
31-
std::env::var("PLAYMATCH_API_AUTH").expect("missing PLAYMATCH_API_AUTH")
30+
env::var("PLAYMATCH_API_AUTH").expect("missing PLAYMATCH_API_AUTH")
3231
)
3332
.parse()
3433
.expect("Invalid Authorization header"),
@@ -72,7 +71,6 @@ pub async fn is_user_trusted_or_above(ctx: CommandContext<'_>) -> CheckResult {
7271
let user = ctx.author();
7372
let member = ctx.author_member().await;
7473

75-
// Check if user is owner
7674
if ctx.framework().options().owners.contains(&user.id) {
7775
return Ok(true);
7876
}
@@ -83,75 +81,86 @@ pub async fn is_user_trusted_or_above(ctx: CommandContext<'_>) -> CheckResult {
8381

8482
let member = member.unwrap();
8583

86-
// Check if user has the Staff or Trusted role
8784
if member.roles.iter().any(|role_id| {
8885
role_id == &RoleId::new(*STAFF_ROLE_ID) || TRUSTED_ROLE_IDS.contains(&role_id.get())
8986
}) {
9087
return Ok(true);
9188
}
9289

93-
ctx.say("You do not have permission to use this command.")
94-
.await?;
90+
ctx.send(components_v2::status_reply(
91+
Status::Error,
92+
"You do not have permission to use this command.",
93+
))
94+
.await?;
9595

9696
Ok(false)
9797
}
9898

99-
pub async fn paginate<U, E>(
99+
pub async fn paginate<U: Send + Sync + 'static, E>(
100100
ctx: poise::Context<'_, U, E>,
101101
pages: &[&str],
102102
) -> Result<(), serenity::Error> {
103-
// Define some unique identifiers for the navigation buttons
104103
let ctx_id = ctx.id();
105104
let author_id = ctx.author().id;
106-
let prev_button_id = format!("{}prev", ctx_id);
107-
let next_button_id = format!("{}next", ctx_id);
108-
109-
// Send the embed with the first page as content
110-
let reply = {
111-
let components = CreateActionRow::Buttons(vec![
112-
CreateButton::new(&prev_button_id).emoji('◀'),
113-
CreateButton::new(&next_button_id).emoji('▶'),
114-
]);
115-
116-
CreateReply::default()
117-
.embed(CreateEmbed::default().description(pages[0]))
118-
.components(vec![components])
105+
let prev_button_id = format!("{ctx_id}prev");
106+
let next_button_id = format!("{ctx_id}next");
107+
let ctx_id_str = ctx_id.to_string();
108+
let total = pages.len().max(1);
109+
110+
let build_buttons = |prev_id: &str, next_id: &str| -> Vec<CreateButton<'static>> {
111+
vec![
112+
CreateButton::new(prev_id.to_owned())
113+
.emoji('◀')
114+
.style(ButtonStyle::Secondary),
115+
CreateButton::new(next_id.to_owned())
116+
.emoji('▶')
117+
.style(ButtonStyle::Secondary),
118+
]
119119
};
120120

121-
ctx.send(reply).await?;
122-
123-
// Loop through incoming interactions with the navigation buttons
124-
let mut current_page = 0;
125-
while let Some(press) = serenity::collector::ComponentInteractionCollector::new(ctx)
126-
// We defined our button IDs to start with `ctx_id`. If they don't, some other command's
127-
// button was pressed
128-
.filter(move |press| {
129-
press.data.custom_id.starts_with(&ctx_id.to_string()) && press.user.id == author_id
130-
})
131-
// Timeout when no navigation button has been pressed for 24 hours
132-
.timeout(Duration::from_secs(3600 * 24))
133-
.await
134-
{
135-
// Depending on which button was pressed, go to next or previous page
121+
let container = components_v2::paginate_container(
122+
pages[0].to_owned(),
123+
0,
124+
total,
125+
build_buttons(&prev_button_id, &next_button_id),
126+
);
127+
ctx.send(components_v2::reply_from_container(container))
128+
.await?;
129+
130+
let mut current_page = 0usize;
131+
loop {
132+
let prefix = ctx_id_str.clone();
133+
let Some(press) = ComponentInteractionCollector::new(ctx.serenity_context())
134+
.timeout(Duration::from_secs(3600 * 24))
135+
.author_id(author_id)
136+
.filter(move |press| press.data.custom_id.starts_with(&prefix))
137+
.await
138+
else {
139+
break;
140+
};
141+
136142
if press.data.custom_id == next_button_id {
137-
current_page += 1;
138-
if current_page >= pages.len() {
139-
current_page = 0;
140-
}
143+
current_page = (current_page + 1) % total;
141144
} else if press.data.custom_id == prev_button_id {
142-
current_page = current_page.checked_sub(1).unwrap_or(pages.len() - 1);
145+
current_page = current_page.checked_sub(1).unwrap_or(total - 1);
143146
} else {
144-
// This is an unrelated button interaction
145147
continue;
146148
}
147149

148-
// Update the message with the new page contents
150+
let container = components_v2::paginate_container(
151+
pages[current_page].to_owned(),
152+
current_page,
153+
total,
154+
build_buttons(&prev_button_id, &next_button_id),
155+
);
156+
149157
press
150158
.create_response(
151-
ctx.serenity_context(),
159+
ctx.serenity_context().http.as_ref(),
152160
CreateInteractionResponse::UpdateMessage(
153161
CreateInteractionResponseMessage::new()
154-
.embed(CreateEmbed::new().description(pages[current_page])),
162+
.flags(MessageFlags::IS_COMPONENTS_V2)
163+
.components(vec![CreateComponent::Container(container)]),
155164
),
156165
)
157166
.await?;

0 commit comments

Comments
 (0)