mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 08:48:54 +00:00
Add check command
This commit is contained in:
parent
0db52c5c2b
commit
3f9db46032
4 changed files with 190 additions and 34 deletions
|
@ -1,14 +1,26 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use cache::save_beatmap;
|
||||||
use display::display_beatmapset;
|
use display::display_beatmapset;
|
||||||
use embeds::ScoreEmbedBuilder;
|
use embeds::ScoreEmbedBuilder;
|
||||||
use link_parser::EmbedType;
|
use link_parser::EmbedType;
|
||||||
use poise::CreateReply;
|
use poise::{ChoiceParameter, CreateReply};
|
||||||
use serenity::all::User;
|
use serenity::all::User;
|
||||||
|
|
||||||
/// osu!-related command group.
|
/// osu!-related command group.
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
subcommands("profile", "top", "recent", "pinned", "save", "forcesave", "beatmap")
|
subcommands(
|
||||||
|
"profile",
|
||||||
|
"top",
|
||||||
|
"recent",
|
||||||
|
"pinned",
|
||||||
|
"save",
|
||||||
|
"forcesave",
|
||||||
|
"beatmap",
|
||||||
|
"check"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn osu<U: HasOsuEnv>(_ctx: CmdContext<'_, U>) -> Result<()> {
|
pub async fn osu<U: HasOsuEnv>(_ctx: CmdContext<'_, U>) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -308,20 +320,7 @@ async fn beatmap<U: HasOsuEnv>(
|
||||||
|
|
||||||
ctx.defer().await?;
|
ctx.defer().await?;
|
||||||
|
|
||||||
let beatmap = parse_map_input(ctx.channel_id(), env, map, mode).await?;
|
let beatmap = parse_map_input(ctx.channel_id(), env, map, mode, beatmapset).await?;
|
||||||
|
|
||||||
// override into beatmapset if needed
|
|
||||||
let beatmap = if beatmapset == Some(true) {
|
|
||||||
match beatmap {
|
|
||||||
EmbedType::Beatmap(beatmap, _, _) => {
|
|
||||||
let beatmaps = env.beatmaps.get_beatmapset(beatmap.beatmapset_id).await?;
|
|
||||||
EmbedType::Beatmapset(beatmaps)
|
|
||||||
}
|
|
||||||
bm @ EmbedType::Beatmapset(_) => bm,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
beatmap
|
|
||||||
};
|
|
||||||
|
|
||||||
// override mods and mode if needed
|
// override mods and mode if needed
|
||||||
match beatmap {
|
match beatmap {
|
||||||
|
@ -361,6 +360,8 @@ async fn beatmap<U: HasOsuEnv>(
|
||||||
)]),
|
)]),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
let bmode = beatmap.mode.with_override(mode);
|
||||||
|
save_beatmap(env, ctx.channel_id(), &BeatmapWithMode(beatmap, bmode)).await?;
|
||||||
}
|
}
|
||||||
EmbedType::Beatmapset(vec) => {
|
EmbedType::Beatmapset(vec) => {
|
||||||
let b0 = &vec[0];
|
let b0 = &vec[0];
|
||||||
|
@ -389,6 +390,138 @@ async fn beatmap<U: HasOsuEnv>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ChoiceParameter, Default)]
|
||||||
|
enum SortScoreBy {
|
||||||
|
#[default]
|
||||||
|
PP,
|
||||||
|
Score,
|
||||||
|
#[name = "Maximum Combo"]
|
||||||
|
Combo,
|
||||||
|
#[name = "Miss Count"]
|
||||||
|
Miss,
|
||||||
|
Accuracy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SortScoreBy {
|
||||||
|
fn compare(self, a: &Score, b: &Score) -> Ordering {
|
||||||
|
match self {
|
||||||
|
SortScoreBy::PP => {
|
||||||
|
b.pp.unwrap_or(0.0)
|
||||||
|
.partial_cmp(&a.pp.unwrap_or(0.0))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
SortScoreBy::Score => b.normalized_score.cmp(&a.normalized_score),
|
||||||
|
SortScoreBy::Combo => b.max_combo.cmp(&a.max_combo),
|
||||||
|
SortScoreBy::Miss => b.count_miss.cmp(&a.count_miss),
|
||||||
|
SortScoreBy::Accuracy => b.server_accuracy.partial_cmp(&a.server_accuracy).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display your or a player's scores for a certain beatmap/beatmapset.
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
async fn check<U: HasOsuEnv>(
|
||||||
|
ctx: CmdContext<'_, U>,
|
||||||
|
#[description = "A link or shortlink to the beatmap or beatmapset"] map: Option<String>,
|
||||||
|
#[description = "osu! username"] username: Option<String>,
|
||||||
|
#[description = "Discord username"] discord_name: Option<User>,
|
||||||
|
#[description = "Sort scores by"] sort: Option<SortScoreBy>,
|
||||||
|
#[description = "Reverse the sorting order"] reverse: Option<bool>,
|
||||||
|
#[description = "Filter the mods on the scores"] mods: Option<UnparsedMods>,
|
||||||
|
#[description = "Filter the mode of the scores"] mode: Option<Mode>,
|
||||||
|
#[description = "Find all scores in the beatmapset instead"] beatmapset: Option<bool>,
|
||||||
|
#[description = "Score listing style"] style: Option<ScoreListStyle>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let env = ctx.data().osu_env();
|
||||||
|
|
||||||
|
let user = arg_from_username_or_discord(username, discord_name);
|
||||||
|
let args = ListingArgs::from_params(
|
||||||
|
env,
|
||||||
|
None,
|
||||||
|
style.unwrap_or(ScoreListStyle::Grid),
|
||||||
|
mode,
|
||||||
|
user,
|
||||||
|
ctx.author().id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
ctx.defer().await?;
|
||||||
|
|
||||||
|
let embed = parse_map_input(ctx.channel_id(), env, map, mode, beatmapset).await?;
|
||||||
|
let beatmaps = match embed {
|
||||||
|
EmbedType::Beatmap(beatmap, _, _) => {
|
||||||
|
let nmode = beatmap.mode.with_override(mode);
|
||||||
|
vec![BeatmapWithMode(*beatmap, nmode)]
|
||||||
|
}
|
||||||
|
EmbedType::Beatmapset(vec) => match mode {
|
||||||
|
None => {
|
||||||
|
let default_mode = vec[0].mode;
|
||||||
|
vec.into_iter()
|
||||||
|
.filter(|b| b.mode == default_mode)
|
||||||
|
.map(|b| BeatmapWithMode(b, default_mode))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
Some(m) => vec
|
||||||
|
.into_iter()
|
||||||
|
.filter(|b| b.mode == Mode::Std || b.mode == m)
|
||||||
|
.map(|b| BeatmapWithMode(b, m))
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let display = if beatmaps.len() == 1 {
|
||||||
|
format!(
|
||||||
|
"[{}](<{}>)",
|
||||||
|
beatmaps[0].0.short_link(None, Mods::NOMOD),
|
||||||
|
beatmaps[0].0.link()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"[/s/{}](<{}>)",
|
||||||
|
beatmaps[0].0.beatmapset_id,
|
||||||
|
beatmaps[0].0.beatmapset_link()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ordering = sort.unwrap_or_default();
|
||||||
|
let mut scores = do_check(env, &beatmaps, mods, &args.user).await?;
|
||||||
|
if scores.is_empty() {
|
||||||
|
ctx.reply(format!(
|
||||||
|
"No plays found for {} on {} with the required criteria.",
|
||||||
|
args.user.mention(),
|
||||||
|
display
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
scores.sort_unstable_by(|a, b| ordering.compare(a, b));
|
||||||
|
if reverse == Some(true) {
|
||||||
|
scores.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = ctx
|
||||||
|
.clone()
|
||||||
|
.reply(format!(
|
||||||
|
"Here are the plays by {} on {}!",
|
||||||
|
args.user.mention(),
|
||||||
|
display
|
||||||
|
))
|
||||||
|
.await?
|
||||||
|
.into_message()
|
||||||
|
.await?;
|
||||||
|
args.style
|
||||||
|
.display_scores(
|
||||||
|
scores,
|
||||||
|
beatmaps[0].1,
|
||||||
|
ctx.serenity_context(),
|
||||||
|
ctx.guild_id(),
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn arg_from_username_or_discord(
|
fn arg_from_username_or_discord(
|
||||||
username: Option<String>,
|
username: Option<String>,
|
||||||
discord_name: Option<User>,
|
discord_name: Option<User>,
|
||||||
|
@ -405,8 +538,9 @@ async fn parse_map_input(
|
||||||
env: &OsuEnv,
|
env: &OsuEnv,
|
||||||
input: Option<String>,
|
input: Option<String>,
|
||||||
mode: Option<Mode>,
|
mode: Option<Mode>,
|
||||||
|
beatmapset: Option<bool>,
|
||||||
) -> Result<EmbedType> {
|
) -> Result<EmbedType> {
|
||||||
Ok(match input {
|
let output = match input {
|
||||||
None => {
|
None => {
|
||||||
let Some((BeatmapWithMode(b, mode), bmmods)) =
|
let Some((BeatmapWithMode(b, mode), bmmods)) =
|
||||||
load_beatmap(env, channel_id, None as Option<&'_ Message>).await
|
load_beatmap(env, channel_id, None as Option<&'_ Message>).await
|
||||||
|
@ -452,5 +586,20 @@ async fn parse_map_input(
|
||||||
};
|
};
|
||||||
results.embed
|
results.embed
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
|
|
||||||
|
// override into beatmapset if needed
|
||||||
|
let output = if beatmapset == Some(true) {
|
||||||
|
match output {
|
||||||
|
EmbedType::Beatmap(beatmap, _, _) => {
|
||||||
|
let beatmaps = env.beatmaps.get_beatmapset(beatmap.beatmapset_id).await?;
|
||||||
|
EmbedType::Beatmapset(beatmaps)
|
||||||
|
}
|
||||||
|
bm @ EmbedType::Beatmapset(_) => bm,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,7 +194,7 @@ mod scores {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ITEMS_PER_PAGE: usize = 5;
|
const ITEMS_PER_PAGE: usize = 10;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl pagination::Paginate for Paginate {
|
impl pagination::Paginate for Paginate {
|
||||||
|
|
|
@ -79,7 +79,7 @@ pub fn handle_check_button<'a>(
|
||||||
};
|
};
|
||||||
let header = UserHeader::from(user.clone());
|
let header = UserHeader::from(user.clone());
|
||||||
|
|
||||||
let scores = super::do_check(&env, &bm, Mods::NOMOD, &header).await?;
|
let scores = super::do_check(&env, &vec![bm.clone()], None, &header).await?;
|
||||||
if scores.is_empty() {
|
if scores.is_empty() {
|
||||||
comp.create_followup(
|
comp.create_followup(
|
||||||
&ctx,
|
&ctx,
|
||||||
|
|
|
@ -21,7 +21,7 @@ use db::{OsuLastBeatmap, OsuSavedUsers, OsuUser, OsuUserMode};
|
||||||
use embeds::{beatmap_embed, score_embed, user_embed};
|
use embeds::{beatmap_embed, score_embed, user_embed};
|
||||||
pub use hook::{dot_osu_hook, hook, score_hook};
|
pub use hook::{dot_osu_hook, hook, score_hook};
|
||||||
use server_rank::{SERVER_RANK_COMMAND, SHOW_LEADERBOARD_COMMAND};
|
use server_rank::{SERVER_RANK_COMMAND, SHOW_LEADERBOARD_COMMAND};
|
||||||
use stream::FuturesOrdered;
|
use stream::{FuturesOrdered, FuturesUnordered};
|
||||||
use youmubot_prelude::announcer::AnnouncerHandler;
|
use youmubot_prelude::announcer::AnnouncerHandler;
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
@ -925,18 +925,15 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mode = bm.1;
|
let mode = bm.1;
|
||||||
let mods = args
|
let umods = args.find::<UnparsedMods>().ok();
|
||||||
.find::<UnparsedMods>()
|
let mods = umods.clone().unwrap_or_default().to_mods(mode)?;
|
||||||
.ok()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_mods(mode)?;
|
|
||||||
let style = args
|
let style = args
|
||||||
.single::<ScoreListStyle>()
|
.single::<ScoreListStyle>()
|
||||||
.unwrap_or(ScoreListStyle::Grid);
|
.unwrap_or(ScoreListStyle::Grid);
|
||||||
let username_arg = args.single::<UsernameArg>().ok();
|
let username_arg = args.single::<UsernameArg>().ok();
|
||||||
let (_, user) = user_header_from_args(username_arg, &env, msg).await?;
|
let (_, user) = user_header_from_args(username_arg, &env, msg).await?;
|
||||||
|
|
||||||
let scores = do_check(&env, &bm, &mods, &user).await?;
|
let scores = do_check(&env, &vec![bm.clone()], umods, &user).await?;
|
||||||
|
|
||||||
if scores.is_empty() {
|
if scores.is_empty() {
|
||||||
msg.reply(&ctx, "No scores found").await?;
|
msg.reply(&ctx, "No scores found").await?;
|
||||||
|
@ -961,19 +958,29 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
||||||
|
|
||||||
pub(crate) async fn do_check(
|
pub(crate) async fn do_check(
|
||||||
env: &OsuEnv,
|
env: &OsuEnv,
|
||||||
bm: &BeatmapWithMode,
|
bm: &[BeatmapWithMode],
|
||||||
mods: &Mods,
|
mods: Option<UnparsedMods>,
|
||||||
user: &UserHeader,
|
user: &UserHeader,
|
||||||
) -> Result<Vec<Score>> {
|
) -> Result<Vec<Score>> {
|
||||||
let BeatmapWithMode(b, m) = bm;
|
|
||||||
|
|
||||||
let osu_client = &env.client;
|
let osu_client = &env.client;
|
||||||
|
|
||||||
let mut scores = osu_client
|
let mut scores = bm
|
||||||
|
.iter()
|
||||||
|
.map(|bm| {
|
||||||
|
let BeatmapWithMode(b, m) = bm;
|
||||||
|
let mods = mods.clone().and_then(|t| t.to_mods(*m).ok());
|
||||||
|
osu_client
|
||||||
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(*m))
|
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(*m))
|
||||||
|
.map_ok(move |mut v| {
|
||||||
|
v.retain(|s| mods.as_ref().is_none_or(|m| m.contains(&s.mods)));
|
||||||
|
v
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<FuturesUnordered<_>>()
|
||||||
|
.try_collect::<Vec<_>>()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|s| s.mods.contains(mods))
|
.flatten()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
scores.sort_by(|a, b| {
|
scores.sort_by(|a, b| {
|
||||||
b.pp.unwrap_or(-1.0)
|
b.pp.unwrap_or(-1.0)
|
||||||
|
|
Loading…
Add table
Reference in a new issue