mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-18 00:08:54 +00:00
osu: Implement pins (#53)
Also format recent so attempt count is displayed
This commit is contained in:
parent
a8d1d11223
commit
6fbae89dfe
5 changed files with 167 additions and 51 deletions
|
@ -272,7 +272,10 @@ impl<'a> ScoreEmbedBuilder<'a> {
|
|||
self
|
||||
}
|
||||
pub fn footer(&mut self, footer: impl Into<String>) -> &mut Self {
|
||||
self.footer = Some(footer.into());
|
||||
self.footer = Some(match self.footer.take() {
|
||||
None => footer.into(),
|
||||
Some(pre) => format!("{} | {}", pre, footer.into()),
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,6 +148,7 @@ pub async fn setup(
|
|||
save,
|
||||
forcesave,
|
||||
recent,
|
||||
pins,
|
||||
last,
|
||||
check,
|
||||
top,
|
||||
|
@ -431,6 +432,7 @@ async fn add_user(target: serenity::model::id::UserId, user: User, env: &OsuEnv)
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ModeArg(Mode);
|
||||
|
||||
impl FromStr for ModeArg {
|
||||
|
@ -464,7 +466,9 @@ async fn to_user_id_query(
|
|||
.ok_or_else(|| Error::msg("No saved account found"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
enum Nth {
|
||||
#[default]
|
||||
All,
|
||||
Nth(u8),
|
||||
}
|
||||
|
@ -483,6 +487,39 @@ impl FromStr for Nth {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ListingArgs {
|
||||
pub nth: Nth,
|
||||
pub style: ScoreListStyle,
|
||||
pub mode: Mode,
|
||||
pub user: UserID,
|
||||
}
|
||||
|
||||
impl ListingArgs {
|
||||
pub async fn parse(
|
||||
env: &OsuEnv,
|
||||
msg: &Message,
|
||||
args: &mut Args,
|
||||
default_style: ScoreListStyle,
|
||||
) -> Result<ListingArgs> {
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or(default_style);
|
||||
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
||||
let user = to_user_id_query(
|
||||
args.quoted().trimmed().single::<UsernameArg>().ok(),
|
||||
&env,
|
||||
msg.author.id,
|
||||
)
|
||||
.await?;
|
||||
Ok(Self {
|
||||
nth,
|
||||
style,
|
||||
mode,
|
||||
user,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[aliases("rs", "rc", "r")]
|
||||
#[description = "Gets an user's recent play"]
|
||||
|
@ -493,15 +530,12 @@ impl FromStr for Nth {
|
|||
pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
||||
let user = to_user_id_query(
|
||||
args.quoted().trimmed().single::<UsernameArg>().ok(),
|
||||
&env,
|
||||
msg.author.id,
|
||||
)
|
||||
.await?;
|
||||
let ListingArgs {
|
||||
nth,
|
||||
style,
|
||||
mode,
|
||||
user,
|
||||
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::Table).await?;
|
||||
|
||||
let osu_client = &env.client;
|
||||
|
||||
|
@ -509,39 +543,11 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
.user(&user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("User not found"))?;
|
||||
let plays = osu_client
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||
.await?;
|
||||
match nth {
|
||||
Nth::Nth(nth) => {
|
||||
let recent_play = osu_client
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(nth))
|
||||
.await?
|
||||
.into_iter()
|
||||
.last()
|
||||
.ok_or_else(|| Error::msg("No such play"))?;
|
||||
let beatmap = env
|
||||
.beatmaps
|
||||
.get_beatmap(recent_play.beatmap_id, mode)
|
||||
.await?;
|
||||
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let beatmap_mode = BeatmapWithMode(beatmap, mode);
|
||||
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
CreateMessage::new()
|
||||
.content("Here is the play that you requested".to_string())
|
||||
.embed(score_embed(&recent_play, &beatmap_mode, &content, &user).build())
|
||||
.components(vec![score_components(msg.guild_id)])
|
||||
.reference_message(msg),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Save the beatmap...
|
||||
cache::save_beatmap(&env, msg.channel_id, &beatmap_mode).await?;
|
||||
}
|
||||
Nth::All => {
|
||||
let plays = osu_client
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||
.await?;
|
||||
let reply = msg
|
||||
.reply(
|
||||
ctx,
|
||||
|
@ -552,6 +558,101 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
.display_scores(plays, mode, ctx, reply.guild_id, reply)
|
||||
.await?;
|
||||
}
|
||||
Nth::Nth(nth) => {
|
||||
let Some(play) = plays.get(nth as usize) else {
|
||||
Err(Error::msg("No such play"))?
|
||||
};
|
||||
let attempts = plays
|
||||
.iter()
|
||||
.skip(nth as usize)
|
||||
.take_while(|p| p.beatmap_id == play.beatmap_id && p.mods == play.mods)
|
||||
.count();
|
||||
let beatmap = env.beatmaps.get_beatmap(play.beatmap_id, mode).await?;
|
||||
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let beatmap_mode = BeatmapWithMode(beatmap, mode);
|
||||
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
CreateMessage::new()
|
||||
.content("Here is the play that you requested".to_string())
|
||||
.embed(
|
||||
score_embed(play, &beatmap_mode, &content, &user)
|
||||
.footer(format!("Attempt #{}", attempts))
|
||||
.build(),
|
||||
)
|
||||
.components(vec![score_components(msg.guild_id)])
|
||||
.reference_message(msg),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Save the beatmap...
|
||||
cache::save_beatmap(&env, msg.channel_id, &beatmap_mode).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[aliases("pin")]
|
||||
#[description = "Gets an user's pinned plays"]
|
||||
#[usage = "#[the nth recent play = --all] / [style (table or grid) = --table] / [mode (std, taiko, mania, catch) = std] / [username / user id = your saved id]"]
|
||||
#[example = "#1 / taiko / natsukagami"]
|
||||
#[delimiters("/", " ")]
|
||||
#[max_args(4)]
|
||||
pub async fn pins(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
|
||||
let ListingArgs {
|
||||
nth,
|
||||
style,
|
||||
mode,
|
||||
user,
|
||||
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::Grid).await?;
|
||||
|
||||
let osu_client = &env.client;
|
||||
|
||||
let user = osu_client
|
||||
.user(&user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("User not found"))?;
|
||||
let plays = osu_client
|
||||
.user_pins(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||
.await?;
|
||||
match nth {
|
||||
Nth::All => {
|
||||
let reply = msg
|
||||
.reply(
|
||||
ctx,
|
||||
format!("Here are the pinned plays by `{}`!", user.username),
|
||||
)
|
||||
.await?;
|
||||
style
|
||||
.display_scores(plays, mode, ctx, reply.guild_id, reply)
|
||||
.await?;
|
||||
}
|
||||
Nth::Nth(nth) => {
|
||||
let Some(play) = plays.get(nth as usize) else {
|
||||
Err(Error::msg("No such play"))?
|
||||
};
|
||||
let beatmap = env.beatmaps.get_beatmap(play.beatmap_id, mode).await?;
|
||||
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let beatmap_mode = BeatmapWithMode(beatmap, mode);
|
||||
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
CreateMessage::new()
|
||||
.content("Here is the play that you requested".to_string())
|
||||
.embed(score_embed(play, &beatmap_mode, &content, &user).build())
|
||||
.components(vec![score_components(msg.guild_id)])
|
||||
.reference_message(msg),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Save the beatmap...
|
||||
cache::save_beatmap(&env, msg.channel_id, &beatmap_mode).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -759,17 +860,15 @@ pub(crate) async fn do_check(
|
|||
#[max_args(4)]
|
||||
pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||
let mode = args
|
||||
.single::<ModeArg>()
|
||||
.map(|ModeArg(t)| t)
|
||||
.unwrap_or(Mode::Std);
|
||||
|
||||
let user_id = to_user_id_query(args.single::<UsernameArg>().ok(), &env, msg.author.id).await?;
|
||||
let ListingArgs {
|
||||
nth,
|
||||
style,
|
||||
mode,
|
||||
user,
|
||||
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::default()).await?;
|
||||
let osu_client = &env.client;
|
||||
let user = osu_client
|
||||
.user(&user_id, |f| f.mode(mode))
|
||||
.user(&user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("User not found"))?;
|
||||
|
||||
|
@ -813,7 +912,10 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||
.await?;
|
||||
let reply = msg
|
||||
.reply(&ctx, format!("Here are the top plays by `{}`!", user_id))
|
||||
.reply(
|
||||
&ctx,
|
||||
format!("Here are the top plays by `{}`!", user.username),
|
||||
)
|
||||
.await?;
|
||||
style
|
||||
.display_scores(plays, mode, ctx, msg.guild_id, reply)
|
||||
|
|
|
@ -108,6 +108,14 @@ impl OsuClient {
|
|||
self.user_scores(UserScoreType::Recent, user, f).await
|
||||
}
|
||||
|
||||
pub async fn user_pins(
|
||||
&self,
|
||||
user: UserID,
|
||||
f: impl FnOnce(&mut UserScoreRequestBuilder) -> &mut UserScoreRequestBuilder,
|
||||
) -> Result<Vec<Score>, Error> {
|
||||
self.user_scores(UserScoreType::Pin, user, f).await
|
||||
}
|
||||
|
||||
async fn user_scores(
|
||||
&self,
|
||||
u: UserScoreType,
|
||||
|
|
|
@ -238,6 +238,7 @@ pub mod builders {
|
|||
pub(crate) enum UserScoreType {
|
||||
Recent,
|
||||
Best,
|
||||
Pin,
|
||||
}
|
||||
|
||||
pub struct UserScoreRequestBuilder {
|
||||
|
@ -273,6 +274,7 @@ pub mod builders {
|
|||
r = match self.score_type {
|
||||
UserScoreType::Recent => r.recent().include_fails(true),
|
||||
UserScoreType::Best => r.best(),
|
||||
UserScoreType::Pin => r.pinned(),
|
||||
};
|
||||
if let Some(mode) = self.mode {
|
||||
r = r.mode(mode.into());
|
||||
|
|
|
@ -253,6 +253,7 @@ mod username_arg {
|
|||
use serenity::model::id::UserId;
|
||||
use std::str::FromStr;
|
||||
/// An argument that can be either a tagged user, or a raw string.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UsernameArg {
|
||||
Tagged(UserId),
|
||||
Raw(String),
|
||||
|
|
Loading…
Add table
Reference in a new issue