mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 00:38: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
|
self
|
||||||
}
|
}
|
||||||
pub fn footer(&mut self, footer: impl Into<String>) -> &mut 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
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,7 @@ pub async fn setup(
|
||||||
save,
|
save,
|
||||||
forcesave,
|
forcesave,
|
||||||
recent,
|
recent,
|
||||||
|
pins,
|
||||||
last,
|
last,
|
||||||
check,
|
check,
|
||||||
top,
|
top,
|
||||||
|
@ -431,6 +432,7 @@ async fn add_user(target: serenity::model::id::UserId, user: User, env: &OsuEnv)
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct ModeArg(Mode);
|
struct ModeArg(Mode);
|
||||||
|
|
||||||
impl FromStr for ModeArg {
|
impl FromStr for ModeArg {
|
||||||
|
@ -464,7 +466,9 @@ async fn to_user_id_query(
|
||||||
.ok_or_else(|| Error::msg("No saved account found"))
|
.ok_or_else(|| Error::msg("No saved account found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
enum Nth {
|
enum Nth {
|
||||||
|
#[default]
|
||||||
All,
|
All,
|
||||||
Nth(u8),
|
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]
|
#[command]
|
||||||
#[aliases("rs", "rc", "r")]
|
#[aliases("rs", "rc", "r")]
|
||||||
#[description = "Gets an user's recent play"]
|
#[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 {
|
pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
|
|
||||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
let ListingArgs {
|
||||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
nth,
|
||||||
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
style,
|
||||||
let user = to_user_id_query(
|
mode,
|
||||||
args.quoted().trimmed().single::<UsernameArg>().ok(),
|
user,
|
||||||
&env,
|
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::Table).await?;
|
||||||
msg.author.id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let osu_client = &env.client;
|
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))
|
.user(&user, |f| f.mode(mode))
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::msg("User not found"))?;
|
.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 {
|
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 => {
|
Nth::All => {
|
||||||
let plays = osu_client
|
|
||||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
|
||||||
.await?;
|
|
||||||
let reply = msg
|
let reply = msg
|
||||||
.reply(
|
.reply(
|
||||||
ctx,
|
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)
|
.display_scores(plays, mode, ctx, reply.guild_id, reply)
|
||||||
.await?;
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -759,17 +860,15 @@ pub(crate) async fn do_check(
|
||||||
#[max_args(4)]
|
#[max_args(4)]
|
||||||
pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
let ListingArgs {
|
||||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
nth,
|
||||||
let mode = args
|
style,
|
||||||
.single::<ModeArg>()
|
mode,
|
||||||
.map(|ModeArg(t)| t)
|
user,
|
||||||
.unwrap_or(Mode::Std);
|
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::default()).await?;
|
||||||
|
|
||||||
let user_id = to_user_id_query(args.single::<UsernameArg>().ok(), &env, msg.author.id).await?;
|
|
||||||
let osu_client = &env.client;
|
let osu_client = &env.client;
|
||||||
let user = osu_client
|
let user = osu_client
|
||||||
.user(&user_id, |f| f.mode(mode))
|
.user(&user, |f| f.mode(mode))
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::msg("User not found"))?;
|
.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))
|
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||||
.await?;
|
.await?;
|
||||||
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.username),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
style
|
style
|
||||||
.display_scores(plays, mode, ctx, msg.guild_id, reply)
|
.display_scores(plays, mode, ctx, msg.guild_id, reply)
|
||||||
|
|
|
@ -108,6 +108,14 @@ impl OsuClient {
|
||||||
self.user_scores(UserScoreType::Recent, user, f).await
|
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(
|
async fn user_scores(
|
||||||
&self,
|
&self,
|
||||||
u: UserScoreType,
|
u: UserScoreType,
|
||||||
|
|
|
@ -238,6 +238,7 @@ pub mod builders {
|
||||||
pub(crate) enum UserScoreType {
|
pub(crate) enum UserScoreType {
|
||||||
Recent,
|
Recent,
|
||||||
Best,
|
Best,
|
||||||
|
Pin,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UserScoreRequestBuilder {
|
pub struct UserScoreRequestBuilder {
|
||||||
|
@ -273,6 +274,7 @@ pub mod builders {
|
||||||
r = match self.score_type {
|
r = match self.score_type {
|
||||||
UserScoreType::Recent => r.recent().include_fails(true),
|
UserScoreType::Recent => r.recent().include_fails(true),
|
||||||
UserScoreType::Best => r.best(),
|
UserScoreType::Best => r.best(),
|
||||||
|
UserScoreType::Pin => r.pinned(),
|
||||||
};
|
};
|
||||||
if let Some(mode) = self.mode {
|
if let Some(mode) = self.mode {
|
||||||
r = r.mode(mode.into());
|
r = r.mode(mode.into());
|
||||||
|
|
|
@ -253,6 +253,7 @@ mod username_arg {
|
||||||
use serenity::model::id::UserId;
|
use serenity::model::id::UserId;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
/// An argument that can be either a tagged user, or a raw string.
|
/// An argument that can be either a tagged user, or a raw string.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum UsernameArg {
|
pub enum UsernameArg {
|
||||||
Tagged(UserId),
|
Tagged(UserId),
|
||||||
Raw(String),
|
Raw(String),
|
||||||
|
|
Loading…
Add table
Reference in a new issue