From ba74465ca749c07191d99ada52bd3d1301fcffe5 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 31 Dec 2024 09:01:03 +0100 Subject: [PATCH] Add leaderboard command --- youmubot-osu/src/discord/commands.rs | 98 ++++++++++++++++++++++++- youmubot-osu/src/discord/server_rank.rs | 16 ++-- youmubot-osu/src/models/mod.rs | 17 ++++- 3 files changed, 118 insertions(+), 13 deletions(-) diff --git a/youmubot-osu/src/discord/commands.rs b/youmubot-osu/src/discord/commands.rs index 0bb634a..7e26cc7 100644 --- a/youmubot-osu/src/discord/commands.rs +++ b/youmubot-osu/src/discord/commands.rs @@ -20,7 +20,8 @@ use serenity::all::User; "forcesave", "beatmap", "check", - "ranks" + "ranks", + "leaderboard" ) )] pub async fn osu(_ctx: CmdContext<'_, U>) -> Result<()> { @@ -429,7 +430,7 @@ async fn check( #[description = "Sort scores by"] sort: Option, #[description = "Reverse the sorting order"] reverse: Option, #[description = "Filter the mods on the scores"] mods: Option, - #[description = "Filter the mode of the scores"] mode: Option, + #[description = "Filter the gamemode of the scores"] mode: Option, #[description = "Find all scores in the beatmapset instead"] beatmapset: Option, #[description = "Score listing style"] style: Option, ) -> Result<()> { @@ -510,7 +511,14 @@ async fn check( .await? .into_message() .await?; - args.style + + let style = style.unwrap_or(if scores.len() <= 5 { + ScoreListStyle::Grid + } else { + ScoreListStyle::Table + }); + + style .display_scores( scores, beatmaps[0].1, @@ -550,6 +558,90 @@ async fn ranks( Ok(()) } +/// Display the leaderboard on a single map of members in the server. +#[poise::command(slash_command, guild_only)] +async fn leaderboard( + ctx: CmdContext<'_, U>, + #[description = "The link or shortlink of the map"] map: Option, + #[description = "Sort scores by"] sort: Option, + #[description = "Reverse the ordering"] reverse: Option, + #[description = "Include unranked scores"] unranked: Option, + #[description = "Filter the gamemode of the scores"] mode: Option, + #[description = "Score listing style"] style: Option, +) -> Result<()> { + let env = ctx.data().osu_env(); + let guild = ctx.partial_guild().await.unwrap(); + let style = style.unwrap_or_default(); + + let bm = match parse_map_input(ctx.channel_id(), env, map, mode, None).await? { + EmbedType::Beatmap(beatmap, _, _) => { + let nmode = beatmap.mode.with_override(mode); + BeatmapWithMode(*beatmap, nmode) + } + EmbedType::Beatmapset(_) => return Err(Error::msg("invalid map link")), + }; + + ctx.defer().await?; + + let mut scores = server_rank::get_leaderboard( + ctx.serenity_context(), + env, + &bm, + unranked.unwrap_or(false), + sort.unwrap_or(server_rank::OrderBy::PP), + guild.id, + ) + .await?; + if reverse == Some(true) { + scores.reverse(); + } + + let beatmap = &bm.0; + if scores.is_empty() { + ctx.reply(format!( + "No scores have been recorded in **{}** on [`{}`]({}).", + guild.name, + beatmap.short_link(mode, Mods::NOMOD), + beatmap.link() + )) + .await?; + return Ok(()); + } + + let header = format!( + "Here are the top scores of **{}** on {}", + guild.name, + beatmap.mention(mode, Mods::NOMOD), + ); + + match style { + ScoreListStyle::Table => { + let reply = ctx.reply(header).await?.into_message().await?; + server_rank::display_rankings_table( + ctx.serenity_context(), + reply, + scores, + &bm, + sort.unwrap_or_default(), + ) + .await?; + } + ScoreListStyle::Grid => { + let reply = ctx.reply(header).await?.into_message().await?; + style + .display_scores( + scores.into_iter().map(|s| s.score).collect(), + bm.1, + ctx.serenity_context(), + Some(guild.id), + reply, + ) + .await?; + } + } + Ok(()) +} + fn arg_from_username_or_discord( username: Option, discord_name: Option, diff --git a/youmubot-osu/src/discord/server_rank.rs b/youmubot-osu/src/discord/server_rank.rs index c04d664..5272a19 100644 --- a/youmubot-osu/src/discord/server_rank.rs +++ b/youmubot-osu/src/discord/server_rank.rs @@ -328,7 +328,7 @@ where Ok(()) } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, poise::ChoiceParameter)] pub enum OrderBy { PP, Score, @@ -376,14 +376,12 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C let style = args.single::().unwrap_or_default(); let guild = msg.guild_id.expect("Guild-only command"); let env = ctx.data.read().await.get::().unwrap().clone(); - let bm = match super::load_beatmap(&env, msg.channel_id, msg.referenced_message.as_ref()).await - { - Some((bm, _)) => bm, - None => { - msg.reply(&ctx, "No beatmap queried on this channel.") - .await?; - return Ok(()); - } + let Some((bm, _)) = + super::load_beatmap(&env, msg.channel_id, msg.referenced_message.as_ref()).await + else { + msg.reply(&ctx, "No beatmap queried on this channel.") + .await?; + return Ok(()); }; let scores = { diff --git a/youmubot-osu/src/models/mod.rs b/youmubot-osu/src/models/mod.rs index 37c75e5..86a3a0e 100644 --- a/youmubot-osu/src/models/mod.rs +++ b/youmubot-osu/src/models/mod.rs @@ -414,9 +414,16 @@ impl Beatmap { /// Gets a link pointing to the beatmap, in the new format. pub fn link(&self) -> String { + self.mode_link(None) + } + + /// Gets a link pointing to the beatmap, in the new format, with overridable mode. + pub fn mode_link(&self, mode: Option) -> String { format!( "https://osu.ppy.sh/beatmapsets/{}#{}/{}", - self.beatmapset_id, NEW_MODE_NAMES[self.mode as usize], self.beatmap_id + self.beatmapset_id, + NEW_MODE_NAMES[self.mode.with_override(mode) as usize], + self.beatmap_id ) } @@ -443,6 +450,14 @@ impl Beatmap { ) } + pub fn mention(&self, override_mode: Option, mods: &Mods) -> String { + format!( + "[`{}`]({})", + self.short_link(override_mode, mods), + self.mode_link(override_mode), + ) + } + /// Link to the cover image of the beatmap. pub fn cover_url(&self) -> String { format!(