mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
osu: Implement server rankings button
This commit is contained in:
parent
d48ea0c377
commit
fcd6879f86
7 changed files with 318 additions and 178 deletions
|
@ -341,7 +341,7 @@ impl<'a> CollectedScore<'a> {
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
})
|
})
|
||||||
.components(vec![score_components()]),
|
.components(vec![score_components(Some(guild))]),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Reference in a new issue