osu: Implement server rankings button

This commit is contained in:
Natsu Kagami 2024-07-14 05:28:03 +02:00
parent d48ea0c377
commit fcd6879f86
Signed by: nki
GPG key ID: 55A032EB38B49ADB
7 changed files with 318 additions and 178 deletions

View file

@ -341,7 +341,7 @@ impl<'a> CollectedScore<'a> {
} }
.build() .build()
}) })
.components(vec![score_components()]), .components(vec![score_components(Some(guild))]),
) )
.await?; .await?;

View file

@ -2,7 +2,7 @@ pub use beatmapset::display_beatmapset;
pub use scores::ScoreListStyle; pub use scores::ScoreListStyle;
mod scores { mod scores {
use serenity::model::channel::Message; use serenity::{all::GuildId, model::channel::Message};
use youmubot_prelude::*; use youmubot_prelude::*;
@ -39,17 +39,21 @@ mod scores {
scores: Vec<Score>, scores: Vec<Score>,
mode: Mode, mode: Mode,
ctx: &'a Context, ctx: &'a Context,
guild_id: Option<GuildId>,
m: Message, m: Message,
) -> Result<()> { ) -> Result<()> {
match self { match self {
ScoreListStyle::Table => table::display_scores_table(scores, mode, ctx, m).await, ScoreListStyle::Table => table::display_scores_table(scores, mode, ctx, m).await,
ScoreListStyle::Grid => grid::display_scores_grid(scores, mode, ctx, m).await, ScoreListStyle::Grid => {
grid::display_scores_grid(scores, mode, ctx, guild_id, m).await
}
} }
} }
} }
mod grid { mod grid {
use pagination::paginate_with_first_message; use pagination::paginate_with_first_message;
use serenity::all::GuildId;
use serenity::builder::EditMessage; use serenity::builder::EditMessage;
use serenity::model::channel::Message; use serenity::model::channel::Message;
@ -63,6 +67,7 @@ mod scores {
scores: Vec<Score>, scores: Vec<Score>,
mode: Mode, mode: Mode,
ctx: &'a Context, ctx: &'a Context,
guild_id: Option<GuildId>,
mut on: Message, mut on: Message,
) -> Result<()> { ) -> Result<()> {
if scores.is_empty() { if scores.is_empty() {
@ -72,7 +77,11 @@ mod scores {
} }
paginate_with_first_message( paginate_with_first_message(
Paginate { scores, mode }, Paginate {
scores,
guild_id,
mode,
},
ctx, ctx,
on, on,
std::time::Duration::from_secs(60), std::time::Duration::from_secs(60),
@ -83,6 +92,7 @@ mod scores {
pub struct Paginate { pub struct Paginate {
scores: Vec<Score>, scores: Vec<Score>,
guild_id: Option<GuildId>,
mode: Mode, mode: Mode,
} }
@ -112,7 +122,7 @@ mod scores {
.footer(format!("Page {}/{}", page + 1, self.scores.len())) .footer(format!("Page {}/{}", page + 1, self.scores.len()))
.build() .build()
}) })
.components(vec![score_components()]), .components(vec![score_components(self.guild_id)]),
) )
.await?; .await?;
save_beatmap(&env, msg.channel_id, &bm).await?; save_beatmap(&env, msg.channel_id, &bm).await?;
@ -331,10 +341,9 @@ mod scores {
mod beatmapset { mod beatmapset {
use serenity::{ use serenity::{
all::Reaction, all::{GuildId, Reaction},
builder::{CreateEmbedFooter, EditMessage}, builder::{CreateEmbedFooter, EditMessage},
model::channel::Message, model::channel::{Message, ReactionType},
model::channel::ReactionType,
}; };
use youmubot_prelude::*; use youmubot_prelude::*;
@ -353,6 +362,7 @@ mod beatmapset {
mode: Option<Mode>, mode: Option<Mode>,
mods: Option<Mods>, mods: Option<Mods>,
reply_to: &Message, reply_to: &Message,
guild_id: Option<GuildId>,
message: impl AsRef<str>, message: impl AsRef<str>,
) -> Result<bool> { ) -> Result<bool> {
let mods = mods.unwrap_or(Mods::NOMOD); let mods = mods.unwrap_or(Mods::NOMOD);
@ -366,6 +376,7 @@ mod beatmapset {
maps: beatmapset, maps: beatmapset,
mode, mode,
mods, mods,
guild_id,
message: message.as_ref().to_owned(), message: message.as_ref().to_owned(),
}; };
@ -385,6 +396,7 @@ mod beatmapset {
mode: Option<Mode>, mode: Option<Mode>,
mods: Mods, mods: Mods,
message: String, message: String,
guild_id: Option<GuildId>,
} }
impl Paginate { impl Paginate {
@ -447,7 +459,7 @@ mod beatmapset {
)) ))
}) })
) )
.components(vec![beatmap_components()]), .components(vec![beatmap_components(self.guild_id)]),
) )
.await?; .await?;
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone(); let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();

View file

@ -85,7 +85,7 @@ pub fn score_hook<'a>(
) )
}) })
.embed(score_embed(&s, &b, &c, h).build()) .embed(score_embed(&s, &b, &c, h).build())
.components(vec![score_components()]), .components(vec![score_components(msg.guild_id)]),
) )
.await .await
.pls_ok(); .pls_ok();
@ -303,7 +303,7 @@ async fn handle_beatmap<'a, 'b>(
mods, mods,
info, info,
)) ))
.components(vec![beatmap_components()]) .components(vec![beatmap_components(reply_to.guild_id)])
.reference_message(reply_to), .reference_message(reply_to),
) )
.await?; .await?;
@ -323,6 +323,7 @@ async fn handle_beatmapset<'a, 'b>(
mode, mode,
None, None,
reply_to, reply_to,
reply_to.guild_id,
format!("Beatmapset information for `{}`", link), format!("Beatmapset information for `{}`", link),
) )
.await .await

View file

@ -2,34 +2,48 @@ use std::pin::Pin;
use future::Future; use future::Future;
use serenity::all::{ use serenity::all::{
ComponentInteractionDataKind, CreateActionRow, CreateButton, CreateInteractionResponseMessage, ComponentInteractionDataKind, CreateActionRow, CreateButton, CreateInteractionResponse,
Interaction, CreateInteractionResponseFollowup, CreateInteractionResponseMessage, GuildId, Interaction,
}; };
use youmubot_prelude::*; use youmubot_prelude::*;
use crate::Mods; use crate::Mods;
use super::{display::ScoreListStyle, embeds::beatmap_embed, BeatmapWithMode, OsuEnv}; use super::{
display::ScoreListStyle,
embeds::beatmap_embed,
server_rank::{display_rankings_table, get_leaderboard, OrderBy},
BeatmapWithMode, OsuEnv,
};
pub(super) const BTN_CHECK: &'static str = "youmubot_osu_btn_check"; pub(super) const BTN_CHECK: &'static str = "youmubot_osu_btn_check";
pub(super) const BTN_LB: &'static str = "youmubot_osu_btn_lb";
pub(super) const BTN_LAST: &'static str = "youmubot_osu_btn_last"; pub(super) const BTN_LAST: &'static str = "youmubot_osu_btn_last";
/// Create an action row for score pages. /// Create an action row for score pages.
pub fn score_components() -> CreateActionRow { pub fn score_components(guild_id: Option<GuildId>) -> CreateActionRow {
CreateActionRow::Buttons(vec![check_button(), last_button()]) let mut btns = vec![check_button(), last_button()];
if guild_id.is_some() {
btns.insert(1, lb_button());
}
CreateActionRow::Buttons(btns)
} }
/// Create an action row for score pages. /// Create an action row for score pages.
pub fn beatmap_components() -> CreateActionRow { pub fn beatmap_components(guild_id: Option<GuildId>) -> CreateActionRow {
CreateActionRow::Buttons(vec![check_button()]) let mut btns = vec![check_button()];
if guild_id.is_some() {
btns.push(lb_button());
}
CreateActionRow::Buttons(btns)
} }
/// Creates a new check button. /// Creates a new check button.
pub fn check_button() -> CreateButton { pub fn check_button() -> CreateButton {
CreateButton::new(BTN_CHECK) CreateButton::new(BTN_CHECK)
.label("Check your score") .label("Check")
.emoji('🔎') .emoji('🔎')
.style(serenity::all::ButtonStyle::Secondary) .style(serenity::all::ButtonStyle::Success)
} }
/// Implements the `check` button on scores and beatmaps. /// Implements the `check` button on scores and beatmaps.
@ -72,7 +86,7 @@ pub fn handle_check_button<'a>(
comp.get_response(&ctx).await? comp.get_response(&ctx).await?
}; };
ScoreListStyle::Grid ScoreListStyle::Grid
.display_scores(scores, bm.1, ctx, reply) .display_scores(scores, bm.1, ctx, comp.guild_id, reply)
.await?; .await?;
Ok(()) Ok(())
@ -82,9 +96,9 @@ pub fn handle_check_button<'a>(
/// Creates a new check button. /// Creates a new check button.
pub fn last_button() -> CreateButton { pub fn last_button() -> CreateButton {
CreateButton::new(BTN_LAST) CreateButton::new(BTN_LAST)
.label("View Beatmap") .label("Map")
.emoji('🎼') .emoji('🎼')
.style(serenity::all::ButtonStyle::Secondary) .style(serenity::all::ButtonStyle::Success)
} }
/// Implements the `last` button on scores and beatmaps. /// Implements the `last` button on scores and beatmaps.
@ -123,7 +137,7 @@ pub fn handle_last_button<'a>(
CreateInteractionResponseMessage::new() CreateInteractionResponseMessage::new()
.content("Here is the beatmap you requested!") .content("Here is the beatmap you requested!")
.embed(beatmap_embed(&b, m, mods, info)) .embed(beatmap_embed(&b, m, mods, info))
.components(vec![beatmap_components()]), .components(vec![beatmap_components(comp.guild_id)]),
), ),
) )
.await?; .await?;
@ -133,3 +147,67 @@ pub fn handle_last_button<'a>(
Ok(()) Ok(())
}) })
} }
/// Creates a new check button.
pub fn lb_button() -> CreateButton {
CreateButton::new(BTN_LB)
.label("Ranks")
.emoji('👑')
.style(serenity::all::ButtonStyle::Success)
}
/// Implements the `lb` button on scores and beatmaps.
pub fn handle_lb_button<'a>(
ctx: &'a Context,
interaction: &'a Interaction,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
Box::pin(async move {
let comp = match interaction.as_message_component() {
Some(comp)
if comp.data.custom_id == BTN_LB
&& matches!(comp.data.kind, ComponentInteractionDataKind::Button) =>
{
comp
}
_ => return Ok(()),
};
let msg = &*comp.message;
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
let (bm, _) = super::load_beatmap(&env, comp.channel_id, Some(msg))
.await
.unwrap();
let order = OrderBy::default();
let guild = comp.guild_id.expect("Guild-only command");
comp.create_response(
&ctx,
CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()),
)
.await?;
let scores = get_leaderboard(&ctx, &env, &bm, order, guild).await?;
if scores.is_empty() {
comp.create_followup(
&ctx,
CreateInteractionResponseFollowup::new()
.content("No scores have been recorded for this beatmap."),
)
.await?;
return Ok(());
}
let reply = comp
.create_followup(
&ctx,
CreateInteractionResponseFollowup::new().content(format!(
"⌛ Loading top scores on beatmap `{}`...",
bm.short_link(Mods::NOMOD)
)),
)
.await?;
display_rankings_table(&ctx, reply, scores, &bm, order).await?;
Ok(())
})
}

View file

@ -296,7 +296,7 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
&ctx, &ctx,
EditMessage::new() EditMessage::new()
.embed(beatmap_embed(&beatmap, mode, Mods::NOMOD, info)) .embed(beatmap_embed(&beatmap, mode, Mods::NOMOD, info))
.components(vec![beatmap_components()]), .components(vec![beatmap_components(msg.guild_id)]),
) )
.await?; .await?;
let reaction = reply.react(&ctx, '👌').await?; let reaction = reply.react(&ctx, '👌').await?;
@ -520,7 +520,7 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
CreateMessage::new() CreateMessage::new()
.content("Here is the play that you requested".to_string()) .content("Here is the play that you requested".to_string())
.embed(score_embed(&recent_play, &beatmap_mode, &content, &user).build()) .embed(score_embed(&recent_play, &beatmap_mode, &content, &user).build())
.components(vec![score_components()]) .components(vec![score_components(msg.guild_id)])
.reference_message(msg), .reference_message(msg),
) )
.await?; .await?;
@ -538,7 +538,9 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
format!("Here are the recent plays by `{}`!", user.username), format!("Here are the recent plays by `{}`!", user.username),
) )
.await?; .await?;
style.display_scores(plays, mode, ctx, reply).await?; style
.display_scores(plays, mode, ctx, reply.guild_id, reply)
.await?;
} }
} }
Ok(()) Ok(())
@ -623,6 +625,7 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
None, None,
Some(mods), Some(mods),
msg, msg,
msg.guild_id,
"Here is the beatmapset you requested!", "Here is the beatmapset you requested!",
) )
.await?; .await?;
@ -639,7 +642,7 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
CreateMessage::new() CreateMessage::new()
.content("Here is the beatmap you requested!") .content("Here is the beatmap you requested!")
.embed(beatmap_embed(&bm.0, bm.1, mods, info)) .embed(beatmap_embed(&bm.0, bm.1, mods, info))
.components(vec![beatmap_components()]) .components(vec![beatmap_components(msg.guild_id)])
.reference_message(msg), .reference_message(msg),
) )
.await?; .await?;
@ -696,7 +699,9 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
), ),
) )
.await?; .await?;
style.display_scores(scores, mode, ctx, reply).await?; style
.display_scores(scores, mode, ctx, msg.guild_id, reply)
.await?;
Ok(()) Ok(())
} }
@ -779,7 +784,7 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
.top_record(rank) .top_record(rank)
.build(), .build(),
) )
.components(vec![score_components()]) .components(vec![score_components(msg.guild_id)])
}) })
.await?; .await?;
@ -793,7 +798,9 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
let reply = msg let reply = msg
.reply(&ctx, format!("Here are the top plays by `{}`!", user_id)) .reply(&ctx, format!("Here are the top plays by `{}`!", user_id))
.await?; .await?;
style.display_scores(plays, mode, ctx, reply).await?; style
.display_scores(plays, mode, ctx, msg.guild_id, reply)
.await?;
} }
} }
Ok(()) Ok(())

View file

@ -1,6 +1,8 @@
use std::{collections::HashMap, str::FromStr, sync::Arc}; use std::{collections::HashMap, str::FromStr, sync::Arc};
use pagination::paginate_with_first_message;
use serenity::{ use serenity::{
all::GuildId,
builder::EditMessage, builder::EditMessage,
framework::standard::{macros::command, Args, CommandResult}, framework::standard::{macros::command, Args, CommandResult},
model::channel::Message, model::channel::Message,
@ -15,9 +17,10 @@ use youmubot_prelude::{
}; };
use crate::{ use crate::{
discord::{display::ScoreListStyle, oppai_cache::Accuracy}, discord::{display::ScoreListStyle, oppai_cache::Accuracy, BeatmapWithMode},
models::{Mode, Mods}, models::{Mode, Mods},
request::UserID, request::UserID,
Score,
}; };
use super::{ModeArg, OsuEnv}; use super::{ModeArg, OsuEnv};
@ -187,7 +190,7 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OrderBy { pub enum OrderBy {
PP, PP,
Score, Score,
} }
@ -219,15 +222,11 @@ impl FromStr for OrderBy {
pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let order = args.single::<OrderBy>().unwrap_or_default(); let order = args.single::<OrderBy>().unwrap_or_default();
let style = args.single::<ScoreListStyle>().unwrap_or_default(); let style = args.single::<ScoreListStyle>().unwrap_or_default();
let guild = msg.guild_id.expect("Guild-only command");
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone(); let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
let bm = match super::load_beatmap(&env, msg.channel_id, msg.referenced_message.as_ref()).await
let (bm, _) = {
match super::load_beatmap(&env, msg.channel_id, msg.referenced_message.as_ref()).await { Some((bm, _)) => bm,
Some((bm, mods_def)) => {
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
(bm, mods)
}
None => { None => {
msg.reply(&ctx, "No beatmap queried on this channel.") msg.reply(&ctx, "No beatmap queried on this channel.")
.await?; .await?;
@ -235,19 +234,73 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
} }
}; };
let osu_client = env.client.clone();
// Get oppai map.
let mode = bm.1;
let oppai = env.oppai;
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
let guild = msg.guild_id.expect("Guild-only command");
let scores = { let scores = {
const NO_SCORES: &str = "No scores have been recorded for this beatmap."; let reaction = msg.react(ctx, '⌛').await?;
// Signal that we are running. let s = get_leaderboard(&ctx, &env, &bm, order, guild).await?;
let running_reaction = msg.react(&ctx, '⌛').await?; reaction.delete(&ctx).await?;
s
};
if scores.is_empty() {
msg.reply(&ctx, "No scores have been recorded for this beatmap.")
.await?;
return Ok(());
}
match style {
ScoreListStyle::Table => {
let reply = msg
.reply(
&ctx,
format!(
"⌛ Loading top scores on beatmap `{}`...",
bm.short_link(Mods::NOMOD)
),
)
.await?;
display_rankings_table(&ctx, reply, scores, &bm, order).await?;
}
ScoreListStyle::Grid => {
let reply = msg
.reply(
&ctx,
format!(
"Here are the top scores on beatmap `{}` of this server!",
bm.short_link(Mods::NOMOD)
),
)
.await?;
style
.display_scores(
scores.into_iter().map(|s| s.score).collect(),
bm.1,
ctx,
Some(guild),
reply,
)
.await?;
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct Ranking {
pub pp: f64, // calculated pp or score pp
pub official: bool, // official = pp is from bancho
pub member: Arc<String>,
pub score: Score,
}
pub async fn get_leaderboard(
ctx: &Context,
env: &OsuEnv,
bm: &BeatmapWithMode,
order: OrderBy,
guild: GuildId,
) -> Result<Vec<Ranking>> {
let BeatmapWithMode(beatmap, mode) = bm;
let oppai_map = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
let osu_users = env let osu_users = env
.saved_users .saved_users
.all() .all()
@ -263,7 +316,7 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
.iter() .iter()
.filter_map(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id))) .filter_map(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id)))
.map(|(mem, osu_id)| { .map(|(mem, osu_id)| {
osu_client env.client
.scores(bm.0.beatmap_id, move |f| { .scores(bm.0.beatmap_id, move |f| {
f.user(UserID::ID(osu_id)).mode(bm.1) f.user(UserID::ID(osu_id)).mode(bm.1)
}) })
@ -282,7 +335,7 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
let pp = score.pp.map(|v| (true, v)).or_else(|| { let pp = score.pp.map(|v| (true, v)).or_else(|| {
oppai_map oppai_map
.get_pp_from( .get_pp_from(
mode, *mode,
Some(score.max_combo as usize), Some(score.max_combo as usize),
Accuracy::ByCount( Accuracy::ByCount(
score.count_300, score.count_300,
@ -295,65 +348,44 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
.ok() .ok()
.map(|v| (false, v)) .map(|v| (false, v))
})?; })?;
Some((pp, mem.clone(), score)) Some(Ranking {
pp: pp.1,
official: pp.0,
member: mem.clone(),
score,
})
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
running_reaction.delete(&ctx).await?;
if scores.is_empty() {
msg.reply(&ctx, NO_SCORES).await?;
return Ok(());
}
match order { match order {
OrderBy::PP => scores.sort_by(|(a, _, _), (b, _, _)| { OrderBy::PP => scores.sort_by(|a, b| {
b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal) (b.official, b.pp)
.partial_cmp(&(a.official, a.pp))
.unwrap_or(std::cmp::Ordering::Equal)
}), }),
OrderBy::Score => { OrderBy::Score => {
scores.sort_by(|(_, _, a), (_, _, b)| b.normalized_score.cmp(&a.normalized_score)) scores.sort_by(|a, b| b.score.normalized_score.cmp(&a.score.normalized_score))
} }
}; };
scores Ok(scores)
}; }
if scores.is_empty() { pub async fn display_rankings_table(
msg.reply( ctx: &Context,
&ctx, to: Message,
"No scores have been recorded for this beatmap. Run `osu check` to scan for yours!", scores: Vec<Ranking>,
) bm: &BeatmapWithMode,
.await?; order: OrderBy,
return Ok(()); ) -> Result<()> {
} let has_lazer_score = scores.iter().any(|v| v.score.score.is_none());
if let ScoreListStyle::Grid = style {
let reply = msg
.reply(
&ctx,
format!(
"Here are the top scores on beatmap `{}` of this server!",
bm.short_link(Mods::NOMOD)
),
)
.await?;
style
.display_scores(
scores.into_iter().map(|(_, _, a)| a).collect(),
mode,
ctx,
reply,
)
.await?;
return Ok(());
}
let has_lazer_score = scores.iter().any(|(_, _, v)| v.score.is_none());
const ITEMS_PER_PAGE: usize = 5; const ITEMS_PER_PAGE: usize = 5;
let total_len = scores.len(); let total_len = scores.len();
let total_pages = (total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE; let total_pages = (total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE;
paginate_reply( paginate_with_first_message(
paginate_from_fn(move |page: u8, ctx: &Context, m: &mut Message| { paginate_from_fn(move |page: u8, ctx: &Context, m: &mut Message| {
let start = (page as usize) * ITEMS_PER_PAGE; let start = (page as usize) * ITEMS_PER_PAGE;
let end = (start + ITEMS_PER_PAGE).min(scores.len()); let end = (start + ITEMS_PER_PAGE).min(scores.len());
@ -372,7 +404,16 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
let score_arr = scores let score_arr = scores
.iter() .iter()
.enumerate() .enumerate()
.map(|(id, ((official, pp), member, score))| { .map(
|(
id,
Ranking {
pp,
official,
member,
score,
},
)| {
[ [
format!("{}", 1 + id + start), format!("{}", 1 + id + start),
match order { match order {
@ -394,7 +435,8 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
format!("{}", score.count_miss), format!("{}", score.count_miss),
member.to_string(), member.to_string(),
] ]
}) },
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let score_table = match order { let score_table = match order {
@ -425,10 +467,9 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
}) })
.with_page_count(total_pages), .with_page_count(total_pages),
ctx, ctx,
msg, to,
std::time::Duration::from_secs(60), std::time::Duration::from_secs(60),
) )
.await?; .await?;
Ok(()) Ok(())
} }

View file

@ -168,7 +168,8 @@ async fn main() {
handler.push_hook(youmubot_osu::discord::dot_osu_hook); handler.push_hook(youmubot_osu::discord::dot_osu_hook);
handler.push_hook(youmubot_osu::discord::score_hook); handler.push_hook(youmubot_osu::discord::score_hook);
handler.push_interaction_hook(youmubot_osu::discord::interaction::handle_check_button); handler.push_interaction_hook(youmubot_osu::discord::interaction::handle_check_button);
handler.push_interaction_hook(youmubot_osu::discord::interaction::handle_last_button) handler.push_interaction_hook(youmubot_osu::discord::interaction::handle_last_button);
handler.push_interaction_hook(youmubot_osu::discord::interaction::handle_lb_button);
} }
#[cfg(feature = "codeforces")] #[cfg(feature = "codeforces")]
handler.push_hook(youmubot_cf::InfoHook); handler.push_hook(youmubot_cf::InfoHook);