mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Implement top
This commit is contained in:
parent
b76597e594
commit
8c4d8715f7
4 changed files with 152 additions and 11 deletions
|
@ -1,4 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use poise::CreateReply;
|
||||||
|
use serenity::all::User;
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
/// osu!-related command group.
|
/// osu!-related command group.
|
||||||
|
@ -11,6 +13,93 @@ pub async fn osu<U: HasOsuEnv>(_ctx: CmdContext<'_, U>) -> Result<()> {
|
||||||
///
|
///
|
||||||
/// If no osu! username is given, defaults to the currently registered user.
|
/// If no osu! username is given, defaults to the currently registered user.
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
async fn top<U: HasOsuEnv>(ctx: CmdContext<'_, U>, username: Option<String>) -> Result<()> {
|
async fn top<U: HasOsuEnv>(
|
||||||
todo!()
|
ctx: CmdContext<'_, U>,
|
||||||
|
#[description = "Index of the score"]
|
||||||
|
#[min = 1]
|
||||||
|
#[max = 100]
|
||||||
|
index: Option<u8>,
|
||||||
|
#[description = "Score listing style"] style: Option<ScoreListStyle>,
|
||||||
|
#[description = "Game mode"] mode: Option<Mode>,
|
||||||
|
#[description = "osu! username"] username: Option<String>,
|
||||||
|
#[description = "Discord username"] user: Option<User>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let env = ctx.data().osu_env();
|
||||||
|
let username_arg = match (username, user) {
|
||||||
|
(Some(v), _) => Some(UsernameArg::Raw(v)),
|
||||||
|
(_, Some(u)) => Some(UsernameArg::Tagged(u.id)),
|
||||||
|
(None, None) => None,
|
||||||
|
};
|
||||||
|
let ListingArgs {
|
||||||
|
nth,
|
||||||
|
style,
|
||||||
|
mode,
|
||||||
|
user,
|
||||||
|
} = ListingArgs::from_params(
|
||||||
|
env,
|
||||||
|
index,
|
||||||
|
style.unwrap_or(ScoreListStyle::Table),
|
||||||
|
mode,
|
||||||
|
username_arg,
|
||||||
|
ctx.author().id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let osu_client = &env.client;
|
||||||
|
|
||||||
|
ctx.defer().await?;
|
||||||
|
|
||||||
|
let mut plays = osu_client
|
||||||
|
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
plays.sort_unstable_by(|a, b| b.pp.partial_cmp(&a.pp).unwrap());
|
||||||
|
let plays = plays;
|
||||||
|
|
||||||
|
match nth {
|
||||||
|
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 = BeatmapWithMode(beatmap, mode);
|
||||||
|
|
||||||
|
ctx.send({
|
||||||
|
CreateReply::default()
|
||||||
|
.content(format!(
|
||||||
|
"Here is the #{} top play by [`{}`](<{}>)",
|
||||||
|
nth + 1,
|
||||||
|
user.username,
|
||||||
|
user.link()
|
||||||
|
))
|
||||||
|
.embed(
|
||||||
|
score_embed(&play, &beatmap, &content, user)
|
||||||
|
.top_record(nth + 1)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.components(vec![score_components(ctx.guild_id())])
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Save the beatmap...
|
||||||
|
cache::save_beatmap(&env, ctx.channel_id(), &beatmap).await?;
|
||||||
|
}
|
||||||
|
Nth::All => {
|
||||||
|
let reply = ctx
|
||||||
|
.clone()
|
||||||
|
.reply(format!(
|
||||||
|
"Here are the top plays by [`{}`](<{}>)!",
|
||||||
|
user.username,
|
||||||
|
user.link()
|
||||||
|
))
|
||||||
|
.await?
|
||||||
|
.into_message()
|
||||||
|
.await?;
|
||||||
|
style
|
||||||
|
.display_scores(plays, mode, ctx.serenity_context(), ctx.guild_id(), reply)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,19 @@ pub use beatmapset::display_beatmapset;
|
||||||
pub use scores::ScoreListStyle;
|
pub use scores::ScoreListStyle;
|
||||||
|
|
||||||
mod scores {
|
mod scores {
|
||||||
|
use poise::ChoiceParameter;
|
||||||
use serenity::{all::GuildId, model::channel::Message};
|
use serenity::{all::GuildId, model::channel::Message};
|
||||||
|
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
use crate::models::{Mode, Score};
|
use crate::models::{Mode, Score};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ChoiceParameter)]
|
||||||
/// The style for the scores list to be displayed.
|
/// The style for the scores list to be displayed.
|
||||||
pub enum ScoreListStyle {
|
pub enum ScoreListStyle {
|
||||||
|
#[name = "ASCII Table"]
|
||||||
Table,
|
Table,
|
||||||
|
#[name = "List of Embeds"]
|
||||||
Grid,
|
Grid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +169,11 @@ mod scores {
|
||||||
}
|
}
|
||||||
|
|
||||||
paginate_with_first_message(
|
paginate_with_first_message(
|
||||||
Paginate { scores, mode },
|
Paginate {
|
||||||
|
header: on.content.clone(),
|
||||||
|
scores,
|
||||||
|
mode,
|
||||||
|
},
|
||||||
ctx,
|
ctx,
|
||||||
on,
|
on,
|
||||||
std::time::Duration::from_secs(60),
|
std::time::Duration::from_secs(60),
|
||||||
|
@ -176,6 +183,7 @@ mod scores {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Paginate {
|
pub struct Paginate {
|
||||||
|
header: String,
|
||||||
scores: Vec<Score>,
|
scores: Vec<Score>,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
}
|
}
|
||||||
|
@ -311,6 +319,7 @@ mod scores {
|
||||||
let score_table = table_formatting(&SCORE_HEADERS, &SCORE_ALIGNS, score_arr);
|
let score_table = table_formatting(&SCORE_HEADERS, &SCORE_ALIGNS, score_arr);
|
||||||
|
|
||||||
let content = serenity::utils::MessageBuilder::new()
|
let content = serenity::utils::MessageBuilder::new()
|
||||||
|
.push_line(&self.header)
|
||||||
.push_line(score_table)
|
.push_line(score_table)
|
||||||
.push_line(format!("Page **{}/{}**", page + 1, self.total_pages()))
|
.push_line(format!("Page **{}/{}**", page + 1, self.total_pages()))
|
||||||
.push_line("[?] means pp was predicted by oppai-rs.")
|
.push_line("[?] means pp was predicted by oppai-rs.")
|
||||||
|
|
|
@ -569,6 +569,28 @@ struct ListingArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListingArgs {
|
impl ListingArgs {
|
||||||
|
pub async fn from_params(
|
||||||
|
env: &OsuEnv,
|
||||||
|
index: Option<u8>,
|
||||||
|
style: ScoreListStyle,
|
||||||
|
mode_override: Option<Mode>,
|
||||||
|
user: Option<UsernameArg>,
|
||||||
|
sender: serenity::all::UserId,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let nth = index
|
||||||
|
.filter(|&v| 1 <= v && v <= 100)
|
||||||
|
.map(|v| v - 1)
|
||||||
|
.map(Nth::Nth)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let (mode, user) = user_header_or_default_id(user, env, sender).await?;
|
||||||
|
let mode = mode_override.unwrap_or(mode);
|
||||||
|
Ok(Self {
|
||||||
|
nth,
|
||||||
|
style,
|
||||||
|
mode,
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
}
|
||||||
pub async fn parse(
|
pub async fn parse(
|
||||||
env: &OsuEnv,
|
env: &OsuEnv,
|
||||||
msg: &Message,
|
msg: &Message,
|
||||||
|
@ -590,10 +612,10 @@ impl ListingArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn user_header_from_args(
|
async fn user_header_or_default_id(
|
||||||
arg: Option<UsernameArg>,
|
arg: Option<UsernameArg>,
|
||||||
env: &OsuEnv,
|
env: &OsuEnv,
|
||||||
msg: &Message,
|
default_user: serenity::all::UserId,
|
||||||
) -> Result<(Mode, UserHeader)> {
|
) -> Result<(Mode, UserHeader)> {
|
||||||
let (mode, user) = match arg {
|
let (mode, user) = match arg {
|
||||||
Some(UsernameArg::Raw(r)) => {
|
Some(UsernameArg::Raw(r)) => {
|
||||||
|
@ -611,7 +633,7 @@ async fn user_header_from_args(
|
||||||
(user.preferred_mode, user.into())
|
(user.preferred_mode, user.into())
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let user = env.saved_users.by_user_id(msg.author.id).await?
|
let user = env.saved_users.by_user_id(default_user).await?
|
||||||
.ok_or(Error::msg("You do not have a saved account! Use `osu save` command to save your osu! account."))?;
|
.ok_or(Error::msg("You do not have a saved account! Use `osu save` command to save your osu! account."))?;
|
||||||
(user.preferred_mode, user.into())
|
(user.preferred_mode, user.into())
|
||||||
}
|
}
|
||||||
|
@ -619,6 +641,14 @@ async fn user_header_from_args(
|
||||||
Ok((mode, user))
|
Ok((mode, user))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn user_header_from_args(
|
||||||
|
arg: Option<UsernameArg>,
|
||||||
|
env: &OsuEnv,
|
||||||
|
msg: &Message,
|
||||||
|
) -> Result<(Mode, UserHeader)> {
|
||||||
|
user_header_or_default_id(arg, env, msg.author.id).await
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
#[aliases("rs", "rc", "r")]
|
#[aliases("rs", "rc", "r")]
|
||||||
#[description = "Gets an user's recent play"]
|
#[description = "Gets an user's recent play"]
|
||||||
|
@ -977,8 +1007,10 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.send_message(&ctx, {
|
.send_message(&ctx, {
|
||||||
CreateMessage::new()
|
CreateMessage::new()
|
||||||
.content(format!(
|
.content(format!(
|
||||||
"{}: here is the play that you requested",
|
"Here is the #{} top play by [`{}`](<{}>)",
|
||||||
msg.author
|
nth + 1,
|
||||||
|
user.username,
|
||||||
|
user.link()
|
||||||
))
|
))
|
||||||
.embed(
|
.embed(
|
||||||
score_embed(&play, &beatmap, &content, user)
|
score_embed(&play, &beatmap, &content, user)
|
||||||
|
@ -996,7 +1028,11 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
let reply = msg
|
let reply = msg
|
||||||
.reply(
|
.reply(
|
||||||
&ctx,
|
&ctx,
|
||||||
format!("Here are the top plays by `{}`!", user.username),
|
format!(
|
||||||
|
"Here are the top plays by [`{}`](<{}>)!",
|
||||||
|
user.username,
|
||||||
|
user.link()
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
style
|
style
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub mod mods;
|
||||||
pub(crate) mod rosu;
|
pub(crate) mod rosu;
|
||||||
|
|
||||||
pub use mods::Mods;
|
pub use mods::Mods;
|
||||||
|
use poise::ChoiceParameter;
|
||||||
use serenity::utils::MessageBuilder;
|
use serenity::utils::MessageBuilder;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
@ -266,11 +267,17 @@ impl fmt::Display for Language {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, std::hash::Hash)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, std::hash::Hash, ChoiceParameter,
|
||||||
|
)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
|
#[name = "osu!"]
|
||||||
Std,
|
Std,
|
||||||
|
#[name = "osu!taiko"]
|
||||||
Taiko,
|
Taiko,
|
||||||
|
#[name = "osu!catch"]
|
||||||
Catch,
|
Catch,
|
||||||
|
#[name = "osu!mania"]
|
||||||
Mania,
|
Mania,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue