mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-10 04:30:29 +00:00
osu: [Big!] rework load_beatmap so that it can retrieve beatmapsets
Also - scoreboard can handle beatmapsets now - "when" column is included - beatmapWithMode's mode field is now optional
This commit is contained in:
parent
60a72dad85
commit
b302bd3ce1
13 changed files with 564 additions and 378 deletions
|
@ -0,0 +1,6 @@
|
|||
-- Add migration script here
|
||||
|
||||
ALTER TABLE osu_last_beatmaps RENAME COLUMN mode TO mode_old;
|
||||
ALTER TABLE osu_last_beatmaps ADD COLUMN mode INT NULL;
|
||||
UPDATE osu_last_beatmaps SET mode = mode_old;
|
||||
ALTER TABLE osu_last_beatmaps DROP COLUMN mode_old;
|
|
@ -3,7 +3,7 @@ use crate::models::*;
|
|||
pub struct LastBeatmap {
|
||||
pub channel_id: i64,
|
||||
pub beatmap: Vec<u8>,
|
||||
pub mode: u8,
|
||||
pub mode: Option<u8>,
|
||||
}
|
||||
|
||||
impl LastBeatmap {
|
||||
|
|
|
@ -304,7 +304,7 @@ impl<'a> CollectedScore<'a> {
|
|||
.get_beatmap_default(self.score.beatmap_id)
|
||||
.await?;
|
||||
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
Ok((BeatmapWithMode(beatmap, self.mode), content))
|
||||
Ok((BeatmapWithMode(beatmap, Some(self.mode)), content))
|
||||
}
|
||||
|
||||
async fn send_message_to(
|
||||
|
|
|
@ -7,6 +7,7 @@ use embeds::ScoreEmbedBuilder;
|
|||
use link_parser::EmbedType;
|
||||
use poise::{ChoiceParameter, CreateReply};
|
||||
use serenity::all::User;
|
||||
use server_rank::get_leaderboard_from_embed;
|
||||
|
||||
/// osu!-related command group.
|
||||
#[poise::command(
|
||||
|
@ -260,7 +261,7 @@ async fn handle_listing<U: HasOsuEnv>(
|
|||
|
||||
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);
|
||||
let beatmap = BeatmapWithMode(beatmap, Some(mode));
|
||||
|
||||
ctx.send({
|
||||
CreateReply::default()
|
||||
|
@ -297,7 +298,7 @@ async fn handle_listing<U: HasOsuEnv>(
|
|||
.into_message()
|
||||
.await?;
|
||||
style
|
||||
.display_scores(plays, mode, ctx.serenity_context(), ctx.guild_id(), reply)
|
||||
.display_scores(plays, ctx.serenity_context(), ctx.guild_id(), reply)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
@ -321,24 +322,24 @@ async fn beatmap<U: HasOsuEnv>(
|
|||
|
||||
// override mods and mode if needed
|
||||
match beatmap {
|
||||
EmbedType::Beatmap(beatmap, info, bmmods) => {
|
||||
let (beatmap, info, mods) = if mods.is_none() && mode.is_none_or(|v| v == beatmap.mode)
|
||||
{
|
||||
(*beatmap, info, bmmods)
|
||||
} else {
|
||||
let mode = mode.unwrap_or(beatmap.mode);
|
||||
let mods = match mods {
|
||||
None => bmmods,
|
||||
Some(mods) => mods.to_mods(mode)?,
|
||||
EmbedType::Beatmap(beatmap, bmode, info, bmmods) => {
|
||||
let (beatmap, info, mods) =
|
||||
if mods.is_none() && mode.is_none_or(|v| v == bmode.unwrap_or(beatmap.mode)) {
|
||||
(*beatmap, info, bmmods)
|
||||
} else {
|
||||
let mode = bmode.unwrap_or(beatmap.mode);
|
||||
let mods = match mods {
|
||||
None => bmmods,
|
||||
Some(mods) => mods.to_mods(mode)?,
|
||||
};
|
||||
let beatmap = env.beatmaps.get_beatmap(beatmap.beatmap_id, mode).await?;
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(beatmap.beatmap_id)
|
||||
.await?
|
||||
.get_possible_pp_with(mode, &mods);
|
||||
(beatmap, info, mods)
|
||||
};
|
||||
let beatmap = env.beatmaps.get_beatmap(beatmap.beatmap_id, mode).await?;
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(beatmap.beatmap_id)
|
||||
.await?
|
||||
.get_possible_pp_with(mode, &mods);
|
||||
(beatmap, info, mods)
|
||||
};
|
||||
ctx.send(
|
||||
CreateReply::default()
|
||||
.content(format!("Information for {}", beatmap.mention(mode, &mods)))
|
||||
|
@ -355,9 +356,14 @@ async fn beatmap<U: HasOsuEnv>(
|
|||
)
|
||||
.await?;
|
||||
let bmode = beatmap.mode.with_override(mode);
|
||||
save_beatmap(env, ctx.channel_id(), &BeatmapWithMode(beatmap, bmode)).await?;
|
||||
save_beatmap(
|
||||
env,
|
||||
ctx.channel_id(),
|
||||
&BeatmapWithMode(beatmap, Some(bmode)),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
EmbedType::Beatmapset(vec) => {
|
||||
EmbedType::Beatmapset(vec, _) => {
|
||||
let b0 = &vec[0];
|
||||
let msg = ctx
|
||||
.clone()
|
||||
|
@ -438,35 +444,11 @@ async fn check<U: HasOsuEnv>(
|
|||
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 {
|
||||
beatmaps[0].0.mention(None, Mods::NOMOD)
|
||||
} else {
|
||||
beatmaps[0].0.beatmapset_mention()
|
||||
};
|
||||
let display = embed.mention();
|
||||
|
||||
let ordering = sort.unwrap_or_default();
|
||||
let mut scores = do_check(env, &beatmaps, mods, &args.user).await?;
|
||||
let mut scores = do_check(env, &embed, mods, &args.user).await?;
|
||||
if scores.is_empty() {
|
||||
ctx.reply(format!(
|
||||
"No plays found for {} on {} with the required criteria.",
|
||||
|
@ -499,13 +481,7 @@ async fn check<U: HasOsuEnv>(
|
|||
});
|
||||
|
||||
style
|
||||
.display_scores(
|
||||
scores,
|
||||
beatmaps[0].1,
|
||||
ctx.serenity_context(),
|
||||
ctx.guild_id(),
|
||||
msg,
|
||||
)
|
||||
.display_scores(scores, ctx.serenity_context(), ctx.guild_id(), msg)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
@ -552,23 +528,20 @@ async fn leaderboard<U: HasOsuEnv>(
|
|||
let env = ctx.data().osu_env();
|
||||
let guild = ctx.partial_guild().await.unwrap();
|
||||
let style = style.unwrap_or_default();
|
||||
let order = sort.unwrap_or_default();
|
||||
|
||||
let bm = match parse_map_input(ctx.channel_id(), env, map, mode, None).await? {
|
||||
EmbedType::Beatmap(beatmap, _, _) => {
|
||||
let nmode = beatmap.mode.with_override(mode);
|
||||
BeatmapWithMode(*beatmap, nmode)
|
||||
}
|
||||
EmbedType::Beatmapset(_) => return Err(Error::msg("invalid map link")),
|
||||
};
|
||||
let embed = parse_map_input(ctx.channel_id(), env, map, mode, None).await?;
|
||||
|
||||
ctx.defer().await?;
|
||||
|
||||
let mut scores = server_rank::get_leaderboard(
|
||||
let scoreboard_msg = embed.mention();
|
||||
let (mut scores, show_diff) = get_leaderboard_from_embed(
|
||||
ctx.serenity_context(),
|
||||
env,
|
||||
&bm,
|
||||
unranked.unwrap_or(false),
|
||||
sort.unwrap_or(server_rank::OrderBy::PP),
|
||||
&env,
|
||||
embed,
|
||||
None,
|
||||
unranked.unwrap_or(true),
|
||||
order,
|
||||
guild.id,
|
||||
)
|
||||
.await?;
|
||||
|
@ -576,12 +549,10 @@ async fn leaderboard<U: HasOsuEnv>(
|
|||
scores.reverse();
|
||||
}
|
||||
|
||||
let beatmap = &bm.0;
|
||||
if scores.is_empty() {
|
||||
ctx.reply(format!(
|
||||
"No scores have been recorded in **{}** on {}.",
|
||||
guild.name,
|
||||
beatmap.mention(mode, Mods::NOMOD),
|
||||
guild.name, scoreboard_msg,
|
||||
))
|
||||
.await?;
|
||||
return Ok(());
|
||||
|
@ -589,8 +560,7 @@ async fn leaderboard<U: HasOsuEnv>(
|
|||
|
||||
let header = format!(
|
||||
"Here are the top scores of **{}** on {}",
|
||||
guild.name,
|
||||
beatmap.mention(mode, Mods::NOMOD),
|
||||
guild.name, scoreboard_msg,
|
||||
);
|
||||
|
||||
match style {
|
||||
|
@ -600,7 +570,7 @@ async fn leaderboard<U: HasOsuEnv>(
|
|||
ctx.serenity_context(),
|
||||
reply,
|
||||
scores,
|
||||
&bm,
|
||||
show_diff,
|
||||
sort.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
@ -610,7 +580,6 @@ async fn leaderboard<U: HasOsuEnv>(
|
|||
style
|
||||
.display_scores(
|
||||
scores.into_iter().map(|s| s.score).collect(),
|
||||
bm.1,
|
||||
ctx.serenity_context(),
|
||||
Some(guild.id),
|
||||
reply,
|
||||
|
@ -659,18 +628,10 @@ async fn parse_map_input(
|
|||
) -> Result<EmbedType> {
|
||||
let output = match input {
|
||||
None => {
|
||||
let Some((BeatmapWithMode(b, mode), bmmods)) =
|
||||
load_beatmap(env, channel_id, None as Option<&'_ Message>).await
|
||||
else {
|
||||
let Some(v) = load_beatmap_from_channel(env, channel_id).await else {
|
||||
return Err(Error::msg("no beatmap mentioned in this channel"));
|
||||
};
|
||||
let mods = bmmods.unwrap_or_else(|| Mods::NOMOD.clone());
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(b.beatmap_id)
|
||||
.await?
|
||||
.get_possible_pp_with(mode, &mods);
|
||||
EmbedType::Beatmap(Box::new(b), info, mods)
|
||||
v
|
||||
}
|
||||
Some(map) => {
|
||||
if let Ok(id) = map.parse::<u64>() {
|
||||
|
@ -685,6 +646,7 @@ async fn parse_map_input(
|
|||
.get_possible_pp_with(beatmap.mode, Mods::NOMOD);
|
||||
return Ok(EmbedType::Beatmap(
|
||||
Box::new(beatmap),
|
||||
None,
|
||||
info,
|
||||
Mods::NOMOD.clone(),
|
||||
));
|
||||
|
@ -708,14 +670,14 @@ async fn parse_map_input(
|
|||
// override into beatmapset if needed
|
||||
let output = if beatmapset == Some(true) {
|
||||
match output {
|
||||
EmbedType::Beatmap(beatmap, _, _) => {
|
||||
EmbedType::Beatmap(beatmap, _, _, _) => {
|
||||
let beatmaps = env
|
||||
.beatmaps
|
||||
.get_beatmapset(beatmap.beatmapset_id, mode)
|
||||
.await?;
|
||||
EmbedType::Beatmapset(beatmaps)
|
||||
EmbedType::Beatmapset(beatmaps, mode)
|
||||
}
|
||||
bm @ EmbedType::Beatmapset(_) => bm,
|
||||
bm @ EmbedType::Beatmapset(_, _) => bm,
|
||||
}
|
||||
} else {
|
||||
output
|
||||
|
|
|
@ -81,11 +81,17 @@ impl OsuLastBeatmap {
|
|||
}
|
||||
|
||||
impl OsuLastBeatmap {
|
||||
pub async fn by_channel(&self, id: impl Into<ChannelId>) -> Result<Option<(Beatmap, Mode)>> {
|
||||
pub async fn by_channel(
|
||||
&self,
|
||||
id: impl Into<ChannelId>,
|
||||
) -> Result<Option<(Beatmap, Option<Mode>)>> {
|
||||
let last_beatmap =
|
||||
models::LastBeatmap::by_channel_id(id.into().get() as i64, &self.0).await?;
|
||||
Ok(match last_beatmap {
|
||||
Some(lb) => Some((bincode::deserialize(&lb.beatmap[..])?, lb.mode.into())),
|
||||
Some(lb) => Some((
|
||||
bincode::deserialize(&lb.beatmap[..])?,
|
||||
lb.mode.map(|s| s.into()),
|
||||
)),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
@ -94,12 +100,12 @@ impl OsuLastBeatmap {
|
|||
&self,
|
||||
channel: impl Into<ChannelId>,
|
||||
beatmap: &Beatmap,
|
||||
mode: Mode,
|
||||
mode: Option<Mode>,
|
||||
) -> Result<()> {
|
||||
let b = models::LastBeatmap {
|
||||
channel_id: channel.into().get() as i64,
|
||||
beatmap: bincode::serialize(beatmap)?,
|
||||
mode: mode as u8,
|
||||
mode: mode.map(|mode| mode as u8),
|
||||
};
|
||||
b.store(&self.0).await?;
|
||||
Ok(())
|
||||
|
|
|
@ -7,7 +7,7 @@ mod scores {
|
|||
|
||||
use youmubot_prelude::*;
|
||||
|
||||
use crate::models::{Mode, Score};
|
||||
use crate::models::Score;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ChoiceParameter)]
|
||||
/// The style for the scores list to be displayed.
|
||||
|
@ -40,16 +40,13 @@ mod scores {
|
|||
pub async fn display_scores(
|
||||
self,
|
||||
scores: Vec<Score>,
|
||||
mode: Mode,
|
||||
ctx: &Context,
|
||||
guild_id: Option<GuildId>,
|
||||
m: Message,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
ScoreListStyle::Table => table::display_scores_table(scores, mode, ctx, m).await,
|
||||
ScoreListStyle::Grid => {
|
||||
grid::display_scores_grid(scores, mode, ctx, guild_id, m).await
|
||||
}
|
||||
ScoreListStyle::Table => table::display_scores_table(scores, ctx, m).await,
|
||||
ScoreListStyle::Grid => grid::display_scores_grid(scores, ctx, guild_id, m).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,11 +61,10 @@ mod scores {
|
|||
|
||||
use crate::discord::interaction::score_components;
|
||||
use crate::discord::{cache::save_beatmap, BeatmapWithMode, OsuEnv};
|
||||
use crate::models::{Mode, Score};
|
||||
use crate::models::Score;
|
||||
|
||||
pub async fn display_scores_grid(
|
||||
scores: Vec<Score>,
|
||||
mode: Mode,
|
||||
ctx: &Context,
|
||||
guild_id: Option<GuildId>,
|
||||
mut on: Message,
|
||||
|
@ -80,11 +76,7 @@ mod scores {
|
|||
}
|
||||
|
||||
paginate_with_first_message(
|
||||
Paginate {
|
||||
scores,
|
||||
guild_id,
|
||||
mode,
|
||||
},
|
||||
Paginate { scores, guild_id },
|
||||
ctx,
|
||||
on,
|
||||
std::time::Duration::from_secs(60),
|
||||
|
@ -96,7 +88,6 @@ mod scores {
|
|||
pub struct Paginate {
|
||||
scores: Vec<Score>,
|
||||
guild_id: Option<GuildId>,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -107,9 +98,16 @@ mod scores {
|
|||
let score = &self.scores[page];
|
||||
|
||||
let hourglass = msg.react(ctx, '⌛').await?;
|
||||
let mode = self.mode;
|
||||
let beatmap = env.beatmaps.get_beatmap(score.beatmap_id, mode).await?;
|
||||
let beatmap = env
|
||||
.beatmaps
|
||||
.get_beatmap(score.beatmap_id, score.mode)
|
||||
.await?;
|
||||
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let mode = if beatmap.mode == score.mode {
|
||||
None
|
||||
} else {
|
||||
Some(score.mode)
|
||||
};
|
||||
let bm = BeatmapWithMode(beatmap, mode);
|
||||
let user = env
|
||||
.client
|
||||
|
@ -153,12 +151,11 @@ mod scores {
|
|||
use youmubot_prelude::*;
|
||||
|
||||
use crate::discord::oppai_cache::Stats;
|
||||
use crate::discord::{Beatmap, BeatmapInfo, OsuEnv};
|
||||
use crate::models::{Mode, Score};
|
||||
use crate::discord::{time_before_now, Beatmap, BeatmapInfo, OsuEnv};
|
||||
use crate::models::Score;
|
||||
|
||||
pub async fn display_scores_table(
|
||||
scores: Vec<Score>,
|
||||
mode: Mode,
|
||||
ctx: &Context,
|
||||
mut on: Message,
|
||||
) -> Result<()> {
|
||||
|
@ -172,7 +169,6 @@ mod scores {
|
|||
Paginate {
|
||||
header: on.content.clone(),
|
||||
scores,
|
||||
mode,
|
||||
},
|
||||
ctx,
|
||||
on,
|
||||
|
@ -185,7 +181,6 @@ mod scores {
|
|||
pub struct Paginate {
|
||||
header: String,
|
||||
scores: Vec<Score>,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl Paginate {
|
||||
|
@ -212,14 +207,13 @@ mod scores {
|
|||
|
||||
let hourglass = msg.react(ctx, '⌛').await?;
|
||||
let plays = &self.scores[start..end];
|
||||
let mode = self.mode;
|
||||
let beatmaps = plays
|
||||
.iter()
|
||||
.map(|play| async move {
|
||||
let beatmap = meta_cache.get_beatmap(play.beatmap_id, mode).await?;
|
||||
let beatmap = meta_cache.get_beatmap(play.beatmap_id, play.mode).await?;
|
||||
let info = {
|
||||
let b = oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
b.get_info_with(mode, &play.mods)
|
||||
b.get_info_with(play.mode, &play.mods)
|
||||
};
|
||||
Ok((beatmap, info)) as Result<(Beatmap, BeatmapInfo)>
|
||||
})
|
||||
|
@ -235,7 +229,7 @@ mod scores {
|
|||
None => {
|
||||
let b = oppai.get_beatmap(p.beatmap_id).await?;
|
||||
let pp = b.get_pp_from(
|
||||
mode,
|
||||
p.mode,
|
||||
Some(p.max_combo),
|
||||
Stats::Raw(&p.statistics),
|
||||
&p.mods,
|
||||
|
@ -289,15 +283,16 @@ mod scores {
|
|||
beatmap.artist,
|
||||
beatmap.title,
|
||||
beatmap.difficulty_name,
|
||||
beatmap.short_link(Some(self.mode), &play.mods),
|
||||
beatmap.short_link(Some(play.mode), &play.mods),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| "FETCH_FAILED".to_owned())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
const SCORE_HEADERS: [&str; 6] = ["#", "PP", "Acc", "Ranks", "Mods", "Beatmap"];
|
||||
const SCORE_ALIGNS: [Align; 6] = [Right, Right, Right, Right, Right, Left];
|
||||
const SCORE_HEADERS: [&str; 7] =
|
||||
["#", "PP", "Acc", "Ranks", "Mods", "When", "Beatmap"];
|
||||
const SCORE_ALIGNS: [Align; 7] = [Right, Right, Right, Right, Right, Right, Left];
|
||||
|
||||
let score_arr = plays
|
||||
.iter()
|
||||
|
@ -308,9 +303,10 @@ mod scores {
|
|||
[
|
||||
format!("{}", id + start + 1),
|
||||
pp.to_string(),
|
||||
format!("{:.2}%", play.accuracy(self.mode)),
|
||||
format!("{:.2}%", play.accuracy(play.mode)),
|
||||
format!("{}", rank),
|
||||
play.mods.to_string(),
|
||||
time_before_now(&play.date),
|
||||
beatmap.clone(),
|
||||
]
|
||||
})
|
||||
|
@ -486,7 +482,7 @@ mod beatmapset {
|
|||
save_beatmap(
|
||||
&env,
|
||||
msg.channel_id,
|
||||
&BeatmapWithMode(map.clone(), self.mode.unwrap_or(map.mode)),
|
||||
&BeatmapWithMode(map.clone(), self.mode),
|
||||
)
|
||||
.await
|
||||
.pls_ok();
|
||||
|
|
|
@ -506,7 +506,9 @@ impl<'a> FakeScore<'a> {
|
|||
|
||||
pub fn embed(self, ctx: &Context) -> Result<CreateEmbed> {
|
||||
let BeatmapWithMode(b, mode) = self.bm;
|
||||
let info = self.content.get_info_with(*mode, &self.mods);
|
||||
let info = self
|
||||
.content
|
||||
.get_info_with(mode.unwrap_or(b.mode), &self.mods);
|
||||
let attrs = match &info.attrs {
|
||||
rosu_pp::any::PerformanceAttributes::Osu(osu_performance_attributes) => {
|
||||
osu_performance_attributes
|
||||
|
@ -540,7 +542,7 @@ impl<'a> FakeScore<'a> {
|
|||
"".into()
|
||||
} else {
|
||||
let pp = self.content.get_pp_from(
|
||||
*mode,
|
||||
mode.unwrap_or(b.mode),
|
||||
None,
|
||||
Stats::AccOnly {
|
||||
acc: accuracy,
|
||||
|
@ -594,7 +596,7 @@ impl<'a> FakeScore<'a> {
|
|||
"Map stats",
|
||||
b.difficulty
|
||||
.apply_mods(&self.mods, attrs.stars())
|
||||
.format_info(*mode, &self.mods, b),
|
||||
.format_info(mode.unwrap_or(b.mode), &self.mods, b),
|
||||
false,
|
||||
)
|
||||
.footer(CreateEmbedFooter::new(
|
||||
|
@ -707,7 +709,7 @@ pub(crate) fn user_embed(u: User, ex: UserExtras) -> CreateEmbed {
|
|||
"> {}",
|
||||
map.difficulty
|
||||
.apply_mods(&v.mods, info.attrs.stars())
|
||||
.format_info(mode, &v.mods, &map)
|
||||
.format_info(mode.unwrap_or(map.mode), &v.mods, &map)
|
||||
.replace('\n', "\n> ")
|
||||
))
|
||||
.build(),
|
||||
|
|
|
@ -59,7 +59,7 @@ pub fn score_hook<'a>(
|
|||
let mode = score.mode;
|
||||
let content = env.oppai.get_beatmap(score.beatmap_id).await?;
|
||||
let header = env.client.user_header(score.user_id).await?.unwrap();
|
||||
Ok((score, BeatmapWithMode(bm, mode), content, header))
|
||||
Ok((score, BeatmapWithMode(bm, Some(mode)), content, header))
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -253,11 +253,10 @@ pub fn hook<'a>(
|
|||
to_join
|
||||
.then(|l| async move {
|
||||
match l.embed {
|
||||
EmbedType::Beatmap(b, info, mods) => {
|
||||
handle_beatmap(ctx, &b, info, l.link, l.mode, mods, msg)
|
||||
EmbedType::Beatmap(b, mode, info, mods) => {
|
||||
handle_beatmap(ctx, &b, info, l.link, mode, mods, msg)
|
||||
.await
|
||||
.pls_ok();
|
||||
let mode = l.mode.unwrap_or(b.mode);
|
||||
let bm = super::BeatmapWithMode(*b, mode);
|
||||
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
|
@ -266,10 +265,8 @@ pub fn hook<'a>(
|
|||
.await
|
||||
.pls_ok();
|
||||
}
|
||||
EmbedType::Beatmapset(b) => {
|
||||
handle_beatmapset(ctx, b, l.link, l.mode, msg)
|
||||
.await
|
||||
.pls_ok();
|
||||
EmbedType::Beatmapset(b, mode) => {
|
||||
handle_beatmapset(ctx, b, l.link, mode, msg).await.pls_ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -289,7 +286,6 @@ async fn handle_beatmap<'a, 'b>(
|
|||
mods: Mods,
|
||||
reply_to: &Message,
|
||||
) -> Result<()> {
|
||||
let mode = mode.unwrap_or(beatmap.mode);
|
||||
reply_to
|
||||
.channel_id
|
||||
.send_message(
|
||||
|
@ -299,10 +295,21 @@ async fn handle_beatmap<'a, 'b>(
|
|||
MessageBuilder::new()
|
||||
.push("Beatmap information for ")
|
||||
.push_mono_safe(link)
|
||||
.push(" (")
|
||||
.push(beatmap.mention(mode, &mods))
|
||||
.push(")")
|
||||
.build(),
|
||||
)
|
||||
.embed(beatmap_embed(beatmap, mode, &mods, &info))
|
||||
.components(vec![beatmap_components(mode, reply_to.guild_id)])
|
||||
.embed(beatmap_embed(
|
||||
beatmap,
|
||||
mode.unwrap_or(beatmap.mode),
|
||||
&mods,
|
||||
&info,
|
||||
))
|
||||
.components(vec![beatmap_components(
|
||||
mode.unwrap_or(beatmap.mode),
|
||||
reply_to.guild_id,
|
||||
)])
|
||||
.reference_message(reply_to),
|
||||
)
|
||||
.await?;
|
||||
|
@ -317,7 +324,14 @@ async fn handle_beatmapset<'a, 'b>(
|
|||
reply_to: &Message,
|
||||
) -> Result<()> {
|
||||
let reply = reply_to
|
||||
.reply(ctx, format!("Beatmapset information for `{}`", link))
|
||||
.reply(
|
||||
ctx,
|
||||
format!(
|
||||
"Beatmapset information for `{}` ({})",
|
||||
link,
|
||||
beatmaps[0].beatmapset_mention()
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
crate::discord::display::display_beatmapset(
|
||||
ctx.clone(),
|
||||
|
|
|
@ -14,8 +14,9 @@ use crate::{discord::embeds::FakeScore, mods::UnparsedMods, Mode, Mods, UserHead
|
|||
use super::{
|
||||
display::ScoreListStyle,
|
||||
embeds::beatmap_embed,
|
||||
server_rank::{display_rankings_table, get_leaderboard, OrderBy},
|
||||
BeatmapWithMode, OsuEnv,
|
||||
link_parser::EmbedType,
|
||||
server_rank::{display_rankings_table, get_leaderboard_from_embed, OrderBy},
|
||||
BeatmapWithMode, LoadRequest, OsuEnv,
|
||||
};
|
||||
|
||||
pub(super) const BTN_CHECK: &str = "youmubot_osu_btn_check";
|
||||
|
@ -67,7 +68,7 @@ pub fn handle_check_button<'a>(
|
|||
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))
|
||||
let embed = super::load_beatmap(&env, comp.channel_id, Some(msg), LoadRequest::Any)
|
||||
.await
|
||||
.unwrap();
|
||||
let user = match env.saved_users.by_user_id(comp.user.id).await? {
|
||||
|
@ -79,15 +80,15 @@ pub fn handle_check_button<'a>(
|
|||
};
|
||||
let header = UserHeader::from(user.clone());
|
||||
|
||||
let scores = super::do_check(&env, &vec![bm.clone()], None, &header).await?;
|
||||
let scores = super::do_check(&env, &embed, None, &header).await?;
|
||||
if scores.is_empty() {
|
||||
comp.create_followup(
|
||||
&ctx,
|
||||
CreateInteractionResponseFollowup::new().content(format!(
|
||||
"No plays found for [`{}`](<https://osu.ppy.sh/users/{}>) on `{}`.",
|
||||
"No plays found for [`{}`](<https://osu.ppy.sh/users/{}>) on {}.",
|
||||
user.username,
|
||||
user.id,
|
||||
bm.short_link(Mods::NOMOD)
|
||||
embed.mention(),
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
|
@ -98,10 +99,10 @@ pub fn handle_check_button<'a>(
|
|||
.create_followup(
|
||||
&ctx,
|
||||
CreateInteractionResponseFollowup::new().content(format!(
|
||||
"Here are the scores by [`{}`](<https://osu.ppy.sh/users/{}>) on `{}`!",
|
||||
"Here are the scores by [`{}`](<https://osu.ppy.sh/users/{}>) on {}!",
|
||||
user.username,
|
||||
user.id,
|
||||
bm.short_link(Mods::NOMOD)
|
||||
embed.mention()
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
|
@ -110,7 +111,7 @@ pub fn handle_check_button<'a>(
|
|||
let guild_id = comp.guild_id;
|
||||
spawn_future(async move {
|
||||
ScoreListStyle::Grid
|
||||
.display_scores(scores, bm.1, &ctx, guild_id, reply)
|
||||
.display_scores(scores, &ctx, guild_id, reply)
|
||||
.await
|
||||
.pls_ok();
|
||||
});
|
||||
|
@ -189,11 +190,16 @@ pub fn handle_simulate_button<'a>(
|
|||
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
|
||||
let (bm, _) = super::load_beatmap(&env, comp.channel_id, Some(msg))
|
||||
let embed = super::load_beatmap(&env, comp.channel_id, Some(msg), LoadRequest::Beatmap)
|
||||
.await
|
||||
.unwrap();
|
||||
let b = &bm.0;
|
||||
let mode = bm.1;
|
||||
let (b, mode) = match embed {
|
||||
EmbedType::Beatmap(beatmap, mode, _, _) => {
|
||||
let mode = mode.unwrap_or(beatmap.mode);
|
||||
(beatmap, mode)
|
||||
}
|
||||
EmbedType::Beatmapset(_, _) => return Err(Error::msg("Cannot find any beatmap")),
|
||||
};
|
||||
let content = env.oppai.get_beatmap(b.beatmap_id).await?;
|
||||
let info = content.get_info_with(mode, Mods::NOMOD);
|
||||
|
||||
|
@ -227,7 +233,9 @@ pub fn handle_simulate_button<'a>(
|
|||
|
||||
query.interaction.defer(&ctx).await?;
|
||||
|
||||
if let Err(err) = handle_simluate_query(ctx, &env, &query, bm).await {
|
||||
if let Err(err) =
|
||||
handle_simluate_query(ctx, &env, &query, BeatmapWithMode(*b, Some(mode))).await
|
||||
{
|
||||
query
|
||||
.interaction
|
||||
.create_followup(
|
||||
|
@ -251,7 +259,7 @@ async fn handle_simluate_query(
|
|||
bm: BeatmapWithMode,
|
||||
) -> Result<()> {
|
||||
let b = &bm.0;
|
||||
let mode = bm.1;
|
||||
let mode = bm.1.unwrap_or(b.mode);
|
||||
let content = env.oppai.get_beatmap(b.beatmap_id).await?;
|
||||
|
||||
let score: FakeScore = {
|
||||
|
@ -305,54 +313,58 @@ async fn handle_last_req(
|
|||
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
|
||||
let (bm, mods_def) = super::load_beatmap(&env, comp.channel_id, Some(msg))
|
||||
.await
|
||||
.unwrap();
|
||||
let BeatmapWithMode(b, m) = &bm;
|
||||
let embed = super::load_beatmap(
|
||||
&env,
|
||||
comp.channel_id,
|
||||
Some(msg),
|
||||
if is_beatmapset_req {
|
||||
LoadRequest::Beatmapset
|
||||
} else {
|
||||
LoadRequest::Any
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mods = mods_def.unwrap_or_default();
|
||||
|
||||
if is_beatmapset_req {
|
||||
let beatmapset = env
|
||||
.beatmaps
|
||||
.get_beatmapset(bm.0.beatmapset_id, None)
|
||||
.await?;
|
||||
let reply = comp
|
||||
.create_followup(
|
||||
&ctx,
|
||||
CreateInteractionResponseFollowup::new()
|
||||
.content(format!("Beatmapset `{}`", bm.0.beatmapset_mention())),
|
||||
match embed {
|
||||
EmbedType::Beatmapset(beatmapset, mode) => {
|
||||
let reply = comp
|
||||
.create_followup(
|
||||
&ctx,
|
||||
CreateInteractionResponseFollowup::new().content(format!(
|
||||
"Beatmapset `{}`",
|
||||
beatmapset[0].beatmapset_mention()
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
super::display::display_beatmapset(
|
||||
ctx.clone(),
|
||||
beatmapset,
|
||||
mode,
|
||||
None,
|
||||
comp.guild_id,
|
||||
reply,
|
||||
)
|
||||
.await?;
|
||||
super::display::display_beatmapset(
|
||||
ctx.clone(),
|
||||
beatmapset,
|
||||
None,
|
||||
None,
|
||||
comp.guild_id,
|
||||
reply,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
} else {
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(b.beatmap_id)
|
||||
.await?
|
||||
.get_possible_pp_with(*m, &mods);
|
||||
comp.create_followup(
|
||||
&ctx,
|
||||
serenity::all::CreateInteractionResponseFollowup::new()
|
||||
.content(format!(
|
||||
"Information for beatmap `{}`",
|
||||
bm.short_link(&mods)
|
||||
))
|
||||
.embed(beatmap_embed(b, *m, &mods, &info))
|
||||
.components(vec![beatmap_components(bm.1, comp.guild_id)]),
|
||||
)
|
||||
.await?;
|
||||
// Save the beatmap...
|
||||
super::cache::save_beatmap(&env, msg.channel_id, &bm).await?;
|
||||
return Ok(());
|
||||
}
|
||||
EmbedType::Beatmap(b, m, _, mods) => {
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(b.beatmap_id)
|
||||
.await?
|
||||
.get_possible_pp_with(m.unwrap_or(b.mode), &mods);
|
||||
comp.create_followup(
|
||||
&ctx,
|
||||
serenity::all::CreateInteractionResponseFollowup::new()
|
||||
.content(format!("Information for beatmap {}", b.mention(m, &mods)))
|
||||
.embed(beatmap_embed(&*b, m.unwrap_or(b.mode), &mods, &info))
|
||||
.components(vec![beatmap_components(m.unwrap_or(b.mode), comp.guild_id)]),
|
||||
)
|
||||
.await?;
|
||||
// Save the beatmap...
|
||||
super::cache::save_beatmap(&env, msg.channel_id, &BeatmapWithMode(*b, m)).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -380,20 +392,23 @@ pub fn handle_lb_button<'a>(
|
|||
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
|
||||
let (bm, _) = super::load_beatmap(&env, comp.channel_id, Some(msg))
|
||||
let embed = super::load_beatmap(&env, comp.channel_id, Some(msg), LoadRequest::Any)
|
||||
.await
|
||||
.unwrap();
|
||||
let order = OrderBy::default();
|
||||
let guild = comp.guild_id.expect("Guild-only command");
|
||||
|
||||
let scores = get_leaderboard(ctx, &env, &bm, false, order, guild).await?;
|
||||
let scoreboard_msg = embed.mention();
|
||||
let (scores, show_diff) =
|
||||
get_leaderboard_from_embed(ctx, &env, embed, None, false, order, guild).await?;
|
||||
|
||||
if scores.is_empty() {
|
||||
comp.create_followup(
|
||||
&ctx,
|
||||
CreateInteractionResponseFollowup::new().content(
|
||||
"No scores have been recorded for this beatmap from anyone in this server.",
|
||||
),
|
||||
CreateInteractionResponseFollowup::new().content(format!(
|
||||
"No scores have been recorded for {} from anyone in this server.",
|
||||
scoreboard_msg
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
|
@ -402,13 +417,11 @@ pub fn handle_lb_button<'a>(
|
|||
let reply = comp
|
||||
.create_followup(
|
||||
&ctx,
|
||||
CreateInteractionResponseFollowup::new().content(format!(
|
||||
"Here are the top scores on beatmap `{}`!",
|
||||
bm.short_link(Mods::NOMOD)
|
||||
)),
|
||||
CreateInteractionResponseFollowup::new()
|
||||
.content(format!("Here are the top scores on {}!", scoreboard_msg)),
|
||||
)
|
||||
.await?;
|
||||
display_rankings_table(ctx, reply, scores, &bm, order).await?;
|
||||
display_rankings_table(ctx, reply, scores, show_diff, order).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,15 +9,24 @@ use youmubot_prelude::*;
|
|||
|
||||
use super::{oppai_cache::BeatmapInfoWithPP, OsuEnv};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EmbedType {
|
||||
Beatmap(Box<Beatmap>, BeatmapInfoWithPP, Mods),
|
||||
Beatmapset(Vec<Beatmap>),
|
||||
Beatmap(Box<Beatmap>, Option<Mode>, BeatmapInfoWithPP, Mods),
|
||||
Beatmapset(Vec<Beatmap>, Option<Mode>),
|
||||
}
|
||||
|
||||
impl EmbedType {
|
||||
pub fn mention(&self) -> String {
|
||||
match self {
|
||||
EmbedType::Beatmap(beatmap, mode, _, mods) => beatmap.mention(*mode, mods),
|
||||
EmbedType::Beatmapset(vec, _) => vec[0].beatmapset_mention(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToPrint<'a> {
|
||||
pub embed: EmbedType,
|
||||
pub link: &'a str,
|
||||
pub mode: Option<Mode>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
|
@ -66,7 +75,6 @@ pub fn parse_old_links<'a>(
|
|||
Ok(ToPrint {
|
||||
embed,
|
||||
link: capture.get(0).unwrap().as_str(),
|
||||
mode,
|
||||
})
|
||||
})
|
||||
.collect::<stream::FuturesUnordered<_>>()
|
||||
|
@ -104,7 +112,7 @@ pub fn parse_new_links<'a>(
|
|||
.await
|
||||
}
|
||||
}?;
|
||||
Ok(ToPrint { embed, link, mode })
|
||||
Ok(ToPrint { embed, link })
|
||||
})
|
||||
.collect::<stream::FuturesUnordered<_>>()
|
||||
.filter_map(|v: Result<ToPrint>| future::ready(v.pls_ok()))
|
||||
|
@ -133,14 +141,14 @@ pub fn parse_short_links<'a>(
|
|||
"s" => EmbedType::from_beatmapset_id(env, id, mode).await?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(ToPrint { embed, link, mode })
|
||||
Ok(ToPrint { embed, link })
|
||||
})
|
||||
.collect::<stream::FuturesUnordered<_>>()
|
||||
.filter_map(|v: Result<ToPrint>| future::ready(v.pls_ok()))
|
||||
}
|
||||
|
||||
impl EmbedType {
|
||||
async fn from_beatmap_id(
|
||||
pub(crate) async fn from_beatmap_id(
|
||||
env: &OsuEnv,
|
||||
beatmap_id: u64,
|
||||
mode: Option<Mode>,
|
||||
|
@ -158,16 +166,17 @@ impl EmbedType {
|
|||
.await
|
||||
.map(|b| b.get_possible_pp_with(mode, &mods))?
|
||||
};
|
||||
Ok(Self::Beatmap(Box::new(bm), info, mods))
|
||||
Ok(Self::Beatmap(Box::new(bm), mode, info, mods))
|
||||
}
|
||||
|
||||
async fn from_beatmapset_id(
|
||||
pub(crate) async fn from_beatmapset_id(
|
||||
env: &OsuEnv,
|
||||
beatmapset_id: u64,
|
||||
mode: Option<Mode>,
|
||||
) -> Result<Self> {
|
||||
Ok(Self::Beatmapset(
|
||||
env.beatmaps.get_beatmapset(beatmapset_id, mode).await?,
|
||||
mode,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ use std::{borrow::Borrow, collections::HashMap as Map, str::FromStr, sync::Arc};
|
|||
|
||||
use chrono::Utc;
|
||||
use futures_util::join;
|
||||
|
||||
use interaction::{beatmap_components, score_components};
|
||||
use link_parser::EmbedType;
|
||||
use oppai_cache::BeatmapInfoWithPP;
|
||||
use rand::seq::IteratorRandom;
|
||||
use serenity::{
|
||||
|
@ -224,15 +226,11 @@ pub async fn mania(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Mode);
|
||||
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Option<Mode>);
|
||||
|
||||
impl BeatmapWithMode {
|
||||
pub fn short_link(&self, mods: &Mods) -> String {
|
||||
self.0.short_link(Some(self.1), mods)
|
||||
}
|
||||
|
||||
fn mode(&self) -> Mode {
|
||||
self.1
|
||||
self.1.unwrap_or(self.0.mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,7 +337,7 @@ pub(crate) async fn handle_save_respond(
|
|||
let osu_client = &env.client;
|
||||
async fn check(client: &OsuHttpClient, u: &User, mode: Mode, map_id: u64) -> Result<bool> {
|
||||
Ok(client
|
||||
.user_recent(UserID::ID(u.id), |f| f.mode(Mode::Std).limit(1))
|
||||
.user_recent(UserID::ID(u.id), |f| f.mode(mode).limit(1))
|
||||
.await?
|
||||
.into_iter()
|
||||
.take(1)
|
||||
|
@ -518,7 +516,7 @@ impl UserExtras {
|
|||
.get_beatmap(s.beatmap_id)
|
||||
.await?
|
||||
.get_info_with(mode, &s.mods);
|
||||
Some((s, BeatmapWithMode(beatmap, mode), info))
|
||||
Some((s, BeatmapWithMode(beatmap, Some(mode)), info))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -691,7 +689,7 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
)
|
||||
.await?;
|
||||
style
|
||||
.display_scores(plays, mode, ctx, reply.guild_id, reply)
|
||||
.display_scores(plays, ctx, reply.guild_id, reply)
|
||||
.await?;
|
||||
}
|
||||
Nth::Nth(nth) => {
|
||||
|
@ -705,7 +703,7 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
.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);
|
||||
let beatmap_mode = BeatmapWithMode(beatmap, Some(mode));
|
||||
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
|
@ -764,7 +762,7 @@ pub async fn pins(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
)
|
||||
.await?;
|
||||
style
|
||||
.display_scores(plays, mode, ctx, reply.guild_id, reply)
|
||||
.display_scores(plays, ctx, reply.guild_id, reply)
|
||||
.await?;
|
||||
}
|
||||
Nth::Nth(nth) => {
|
||||
|
@ -773,7 +771,7 @@ pub async fn pins(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
};
|
||||
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);
|
||||
let beatmap_mode = BeatmapWithMode(beatmap, Some(mode));
|
||||
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
|
@ -807,46 +805,107 @@ impl FromStr for OptBeatmapSet {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn load_beatmap_from_channel(
|
||||
env: &OsuEnv,
|
||||
channel_id: serenity::all::ChannelId,
|
||||
) -> Option<EmbedType> {
|
||||
let BeatmapWithMode(b, m) = cache::get_beatmap(env, channel_id).await.ok().flatten()?;
|
||||
let mods = Mods::NOMOD.clone();
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(b.beatmap_id)
|
||||
.await
|
||||
.pls_ok()?
|
||||
.get_possible_pp_with(m.unwrap_or(b.mode), &mods);
|
||||
Some(EmbedType::Beatmap(
|
||||
Box::new(b),
|
||||
m,
|
||||
info,
|
||||
Mods::NOMOD.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default)]
|
||||
pub(crate) enum LoadRequest {
|
||||
#[default]
|
||||
Any,
|
||||
Beatmap,
|
||||
Beatmapset,
|
||||
}
|
||||
|
||||
/// Load the mentioned beatmap from the given message.
|
||||
pub(crate) async fn load_beatmap(
|
||||
env: &OsuEnv,
|
||||
channel_id: serenity::all::ChannelId,
|
||||
referenced: Option<&impl Borrow<Message>>,
|
||||
) -> Option<(BeatmapWithMode, Option<Mods>)> {
|
||||
use link_parser::{parse_short_links, EmbedType};
|
||||
if let Some(replied) = referenced {
|
||||
req: LoadRequest,
|
||||
) -> Option<EmbedType> {
|
||||
/* If the request is Beatmapset, we keep a fallback match on beatmap, and later convert it to a beatmapset. */
|
||||
let mut fallback: Option<EmbedType> = None;
|
||||
async fn collect_referenced(
|
||||
env: &OsuEnv,
|
||||
fallback: &mut Option<EmbedType>,
|
||||
req: LoadRequest,
|
||||
replied: &impl Borrow<Message>,
|
||||
) -> Option<EmbedType> {
|
||||
use link_parser::*;
|
||||
async fn try_content(
|
||||
env: &OsuEnv,
|
||||
req: LoadRequest,
|
||||
fallback: &mut Option<EmbedType>,
|
||||
content: &str,
|
||||
) -> Option<(BeatmapWithMode, Option<Mods>)> {
|
||||
let tp = parse_short_links(env, content).next().await?;
|
||||
match tp.embed {
|
||||
EmbedType::Beatmap(b, _, mods) => {
|
||||
let mode = tp.mode.unwrap_or(b.mode);
|
||||
Some((BeatmapWithMode(*b, mode), Some(mods)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
) -> Option<EmbedType> {
|
||||
parse_short_links(env, content)
|
||||
.filter(|e| {
|
||||
future::ready(match &e.embed {
|
||||
EmbedType::Beatmap(_, _, _, _) => {
|
||||
if fallback.is_none() {
|
||||
fallback.replace(e.embed.clone());
|
||||
}
|
||||
req == LoadRequest::Beatmap || req == LoadRequest::Any
|
||||
}
|
||||
EmbedType::Beatmapset(_, _) => {
|
||||
req == LoadRequest::Beatmapset || req == LoadRequest::Any
|
||||
}
|
||||
})
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.map(|v| v.embed)
|
||||
}
|
||||
if let Some(v) = try_content(env, req, fallback, &replied.borrow().content).await {
|
||||
return Some(v);
|
||||
}
|
||||
for embed in &replied.borrow().embeds {
|
||||
for field in &embed.fields {
|
||||
if let Some(v) = try_content(env, &field.value).await {
|
||||
if let Some(v) = try_content(env, req, fallback, &field.value).await {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
if let Some(desc) = &embed.description {
|
||||
if let Some(v) = try_content(env, desc).await {
|
||||
if let Some(v) = try_content(env, req, fallback, desc).await {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(v) = try_content(env, &replied.borrow().content).await {
|
||||
return Some(v);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
let b = cache::get_beatmap(env, channel_id).await.ok().flatten();
|
||||
b.map(|b| (b, None))
|
||||
let embed = match referenced {
|
||||
Some(r) => collect_referenced(env, &mut fallback, req, r).await,
|
||||
None => load_beatmap_from_channel(env, channel_id).await,
|
||||
};
|
||||
|
||||
if req == LoadRequest::Beatmapset {
|
||||
if embed.is_none() {
|
||||
if let Some(EmbedType::Beatmap(b, mode, _, _)) = fallback {
|
||||
return EmbedType::from_beatmapset_id(env, b.beatmapset_id, mode)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
embed
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
@ -858,58 +917,56 @@ pub(crate) async fn load_beatmap(
|
|||
pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
|
||||
let b = load_beatmap(&env, msg.channel_id, msg.referenced_message.as_ref()).await;
|
||||
let beatmapset = args.find::<OptBeatmapSet>().is_ok();
|
||||
let Some(embed) = load_beatmap(
|
||||
&env,
|
||||
msg.channel_id,
|
||||
msg.referenced_message.as_ref(),
|
||||
if beatmapset {
|
||||
LoadRequest::Beatmapset
|
||||
} else {
|
||||
LoadRequest::Any
|
||||
},
|
||||
)
|
||||
.await
|
||||
else {
|
||||
msg.reply(&ctx, "No beatmap was queried on this channel.")
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
let umods = args.find::<UnparsedMods>().ok();
|
||||
|
||||
match b {
|
||||
Some((bm, mods_def)) => {
|
||||
let mods = args.find::<UnparsedMods>().ok();
|
||||
if beatmapset {
|
||||
let beatmapset = env
|
||||
.beatmaps
|
||||
.get_beatmapset(
|
||||
bm.0.beatmapset_id,
|
||||
None, /* Note that we cannot know, so don't force that */
|
||||
)
|
||||
.await?;
|
||||
let reply = msg
|
||||
.reply(&ctx, "Here is the beatmapset you requested!")
|
||||
.await?;
|
||||
display::display_beatmapset(
|
||||
ctx.clone(),
|
||||
beatmapset,
|
||||
None,
|
||||
mods,
|
||||
msg.guild_id,
|
||||
reply,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
let mods = match mods {
|
||||
Some(m) => m.to_mods(bm.mode())?,
|
||||
None => mods_def.unwrap_or_default(),
|
||||
let content_type = embed.mention();
|
||||
match embed {
|
||||
EmbedType::Beatmap(b, mode_, _, mods) => {
|
||||
let mode = mode_.unwrap_or(b.mode);
|
||||
let mods = match umods {
|
||||
Some(m) => m.to_mods(mode)?,
|
||||
None => mods,
|
||||
};
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(bm.0.beatmap_id)
|
||||
.get_beatmap(b.beatmap_id)
|
||||
.await?
|
||||
.get_possible_pp_with(bm.1, &mods);
|
||||
.get_possible_pp_with(mode, &mods);
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
CreateMessage::new()
|
||||
.content("Here is the beatmap you requested!")
|
||||
.embed(beatmap_embed(&bm.0, bm.1, &mods, &info))
|
||||
.components(vec![beatmap_components(bm.1, msg.guild_id)])
|
||||
.content(format!("Information for {}", content_type))
|
||||
.embed(beatmap_embed(&b, mode, &mods, &info))
|
||||
.components(vec![beatmap_components(mode, msg.guild_id)])
|
||||
.reference_message(msg),
|
||||
)
|
||||
.await?;
|
||||
// Save the beatmap...
|
||||
cache::save_beatmap(&env, msg.channel_id, &bm).await?;
|
||||
cache::save_beatmap(&env, msg.channel_id, &BeatmapWithMode(*b, mode_)).await?;
|
||||
}
|
||||
None => {
|
||||
msg.reply(&ctx, "No beatmap was queried on this channel.")
|
||||
EmbedType::Beatmapset(beatmaps, mode) => {
|
||||
let reply = msg
|
||||
.reply(&ctx, format!("Information for {}", content_type))
|
||||
.await?;
|
||||
display::display_beatmapset(ctx.clone(), beatmaps, mode, umods, msg.guild_id, reply)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
@ -924,26 +981,27 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
#[max_args(3)]
|
||||
pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
let bm = load_beatmap(&env, msg.channel_id, msg.referenced_message.as_ref()).await;
|
||||
|
||||
let bm = match bm {
|
||||
Some((bm, _)) => bm,
|
||||
None => {
|
||||
msg.reply(&ctx, "No beatmap queried on this channel.")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
let Some(embed) = load_beatmap(
|
||||
&env,
|
||||
msg.channel_id,
|
||||
msg.referenced_message.as_ref(),
|
||||
LoadRequest::Any,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
msg.reply(&ctx, "No beatmap queried on this channel.")
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
let mode = bm.1;
|
||||
|
||||
let umods = args.find::<UnparsedMods>().ok();
|
||||
let mods = umods.clone().unwrap_or_default().to_mods(mode)?;
|
||||
let style = args
|
||||
.single::<ScoreListStyle>()
|
||||
.unwrap_or(ScoreListStyle::Grid);
|
||||
let username_arg = args.single::<UsernameArg>().ok();
|
||||
let (_, user) = user_header_from_args(username_arg, &env, msg).await?;
|
||||
|
||||
let scores = do_check(&env, &vec![bm.clone()], umods, &user).await?;
|
||||
let scores = do_check(&env, &embed, umods, &user).await?;
|
||||
|
||||
if scores.is_empty() {
|
||||
msg.reply(&ctx, "No scores found").await?;
|
||||
|
@ -955,12 +1013,12 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
format!(
|
||||
"Here are the scores by `{}` on {}!",
|
||||
&user.username,
|
||||
bm.0.mention(Some(bm.1), &mods)
|
||||
embed.mention()
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
style
|
||||
.display_scores(scores, mode, ctx, msg.guild_id, reply)
|
||||
.display_scores(scores, ctx, msg.guild_id, reply)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
@ -968,30 +1026,41 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
|
||||
pub(crate) async fn do_check(
|
||||
env: &OsuEnv,
|
||||
bm: &[BeatmapWithMode],
|
||||
embed: &EmbedType,
|
||||
mods: Option<UnparsedMods>,
|
||||
user: &UserHeader,
|
||||
) -> Result<Vec<Score>> {
|
||||
let osu_client = &env.client;
|
||||
async fn fetch_for_beatmap(
|
||||
env: &OsuEnv,
|
||||
b: &Beatmap,
|
||||
mode_override: Option<Mode>,
|
||||
mods: &Option<UnparsedMods>,
|
||||
user: &UserHeader,
|
||||
) -> Result<Vec<Score>> {
|
||||
let osu_client = &env.client;
|
||||
let m = mode_override.unwrap_or(b.mode);
|
||||
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))
|
||||
.map_ok(move |mut v| {
|
||||
v.retain(|s| mods.as_ref().is_none_or(|m| m.contains(&s.mods)));
|
||||
v
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
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))
|
||||
.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?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let mut scores = match embed {
|
||||
EmbedType::Beatmap(beatmap, mode, _, _) => {
|
||||
fetch_for_beatmap(env, &**beatmap, *mode, &mods, user).await?
|
||||
}
|
||||
EmbedType::Beatmapset(vec, mode) => vec
|
||||
.iter()
|
||||
.map(|b| fetch_for_beatmap(env, b, *mode, &mods, user))
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?
|
||||
.concat(),
|
||||
};
|
||||
scores.sort_by(|a, b| {
|
||||
b.pp.unwrap_or(-1.0)
|
||||
.partial_cmp(&a.pp.unwrap_or(-1.0))
|
||||
|
@ -1031,7 +1100,7 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
|
||||
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);
|
||||
let beatmap = BeatmapWithMode(beatmap, Some(mode));
|
||||
|
||||
msg.channel_id
|
||||
.send_message(&ctx, {
|
||||
|
@ -1061,7 +1130,7 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
)
|
||||
.await?;
|
||||
style
|
||||
.display_scores(plays, mode, ctx, msg.guild_id, reply)
|
||||
.display_scores(plays, ctx, msg.guild_id, reply)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
@ -1182,3 +1251,20 @@ pub(in crate::discord) async fn calculate_weighted_map_age(
|
|||
/ scales().iter().take(scores.len()).sum::<f64>())
|
||||
.floor() as i64)
|
||||
}
|
||||
|
||||
pub(crate) fn time_before_now(time: &chrono::DateTime<Utc>) -> String {
|
||||
let dur = Utc::now() - time;
|
||||
if dur.num_days() >= 365 {
|
||||
format!("{}Y", dur.num_days() / 365)
|
||||
} else if dur.num_days() >= 30 {
|
||||
format!("{}M", dur.num_days() / 30)
|
||||
} else if dur.num_days() >= 1 {
|
||||
format!("{}d", dur.num_days())
|
||||
} else if dur.num_hours() >= 1 {
|
||||
format!("{}h", dur.num_hours())
|
||||
} else if dur.num_minutes() >= 1 {
|
||||
format!("{}m", dur.num_minutes())
|
||||
} else {
|
||||
format!("{}s", dur.num_seconds())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,13 @@ use youmubot_prelude::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
discord::{db::OsuUser, display::ScoreListStyle, oppai_cache::Stats, BeatmapWithMode},
|
||||
models::{Mode, Mods},
|
||||
discord::{
|
||||
db::OsuUser, display::ScoreListStyle, link_parser::EmbedType, oppai_cache::Stats,
|
||||
time_before_now,
|
||||
},
|
||||
models::Mode,
|
||||
request::UserID,
|
||||
Score,
|
||||
Beatmap, Score,
|
||||
};
|
||||
|
||||
use super::{ModeArg, OsuEnv};
|
||||
|
@ -376,20 +379,23 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
|
|||
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 Some((bm, _)) =
|
||||
super::load_beatmap(&env, msg.channel_id, msg.referenced_message.as_ref()).await
|
||||
let Some(beatmap) = super::load_beatmap(
|
||||
&env,
|
||||
msg.channel_id,
|
||||
msg.referenced_message.as_ref(),
|
||||
crate::discord::LoadRequest::Any,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
msg.reply(&ctx, "No beatmap queried on this channel.")
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let scores = {
|
||||
let reaction = msg.react(ctx, '⌛').await?;
|
||||
let s = get_leaderboard(ctx, &env, &bm, show_all, order, guild).await?;
|
||||
reaction.delete(&ctx).await?;
|
||||
s
|
||||
};
|
||||
let reaction = msg.react(ctx, '⌛').await?;
|
||||
let scoreboard_msg = beatmap.mention();
|
||||
let (scores, show_diff) =
|
||||
get_leaderboard_from_embed(ctx, &env, beatmap, None, show_all, order, guild).await?;
|
||||
reaction.delete(&ctx).await?;
|
||||
|
||||
if scores.is_empty() {
|
||||
msg.reply(&ctx, "No scores have been recorded for this beatmap.")
|
||||
|
@ -402,28 +408,24 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
|
|||
let reply = msg
|
||||
.reply(
|
||||
&ctx,
|
||||
format!(
|
||||
"⌛ Loading top scores on beatmap `{}`...",
|
||||
bm.short_link(Mods::NOMOD)
|
||||
),
|
||||
format!("⌛ Loading top scores on {}...", scoreboard_msg),
|
||||
)
|
||||
.await?;
|
||||
display_rankings_table(ctx, reply, scores, &bm, order).await?;
|
||||
display_rankings_table(ctx, reply, scores, show_diff, 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)
|
||||
"Here are the top scores on {} of this server!",
|
||||
scoreboard_msg
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
style
|
||||
.display_scores(
|
||||
scores.into_iter().map(|s| s.score).collect(),
|
||||
bm.1,
|
||||
ctx,
|
||||
Some(guild),
|
||||
reply,
|
||||
|
@ -439,19 +441,30 @@ pub struct Ranking {
|
|||
pub pp: f64, // calculated pp or score pp
|
||||
pub official: bool, // official = pp is from bancho
|
||||
pub member: Arc<String>,
|
||||
pub beatmap: Arc<Beatmap>,
|
||||
pub score: Score,
|
||||
pub star: f64,
|
||||
}
|
||||
|
||||
pub async fn get_leaderboard(
|
||||
async fn get_leaderboard(
|
||||
ctx: &Context,
|
||||
env: &OsuEnv,
|
||||
bm: &BeatmapWithMode,
|
||||
beatmaps: impl IntoIterator<Item = Beatmap>,
|
||||
mode_override: Option<Mode>,
|
||||
show_unranked: bool,
|
||||
order: OrderBy,
|
||||
guild: GuildId,
|
||||
) -> Result<Vec<Ranking>> {
|
||||
let BeatmapWithMode(beatmap, mode) = bm;
|
||||
let oppai_map = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let oppai_maps = beatmaps
|
||||
.into_iter()
|
||||
.map(|b| async move {
|
||||
let op = env.oppai.get_beatmap(b.beatmap_id).await?;
|
||||
let r: Result<_> = Ok((Arc::new(b), op));
|
||||
r
|
||||
})
|
||||
.collect::<stream::FuturesOrdered<_>>()
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
let osu_users = env
|
||||
.saved_users
|
||||
.all()
|
||||
|
@ -465,39 +478,48 @@ pub async fn get_leaderboard(
|
|||
.query_members(&ctx, guild)
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id)))
|
||||
.map(|(mem, osu_id)| {
|
||||
env.client
|
||||
.scores(bm.0.beatmap_id, move |f| {
|
||||
f.user(UserID::ID(osu_id)).mode(bm.1)
|
||||
})
|
||||
.map(|r| Some((mem, r.ok()?)))
|
||||
.filter_map(|m| {
|
||||
osu_users
|
||||
.get(&m.user.id)
|
||||
.map(|ou| (Arc::new(m.distinct()), ou.id))
|
||||
})
|
||||
.flat_map(|(mem, osu_id)| {
|
||||
oppai_maps.iter().map(move |(b, op)| {
|
||||
let mem = mem.clone();
|
||||
env.client
|
||||
.scores(b.beatmap_id, move |f| {
|
||||
f.user(UserID::ID(osu_id)).mode(mode_override)
|
||||
})
|
||||
.map(move |r| Some((b, op, mem.clone(), r.ok()?)))
|
||||
})
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.filter_map(future::ready)
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|(mem, scores)| {
|
||||
let mem = Arc::new(mem);
|
||||
.flat_map(|(b, op, mem, scores)| {
|
||||
scores
|
||||
.into_iter()
|
||||
.map(|score| {
|
||||
let pp = score.pp.map(|v| (true, v)).unwrap_or_else(|| {
|
||||
(
|
||||
false,
|
||||
oppai_map.get_pp_from(
|
||||
*mode,
|
||||
op.get_pp_from(
|
||||
mode_override.unwrap_or(b.mode),
|
||||
Some(score.max_combo),
|
||||
Stats::Raw(&score.statistics),
|
||||
&score.mods,
|
||||
),
|
||||
)
|
||||
});
|
||||
let info = op.get_info_with(score.mode, &score.mods);
|
||||
Ranking {
|
||||
pp: pp.1,
|
||||
official: pp.0,
|
||||
beatmap: b.clone(),
|
||||
member: mem.clone(),
|
||||
star: info.attrs.stars(),
|
||||
score,
|
||||
}
|
||||
})
|
||||
|
@ -536,11 +558,55 @@ pub async fn get_leaderboard(
|
|||
Ok(scores)
|
||||
}
|
||||
|
||||
pub async fn get_leaderboard_from_embed(
|
||||
ctx: &Context,
|
||||
env: &OsuEnv,
|
||||
embed: EmbedType,
|
||||
mode_override: Option<Mode>,
|
||||
show_unranked: bool,
|
||||
order: OrderBy,
|
||||
guild: GuildId,
|
||||
) -> Result<(Vec<Ranking>, bool /* should show diff */)> {
|
||||
Ok(match embed {
|
||||
EmbedType::Beatmap(map, mode, _, _) => {
|
||||
let iter = std::iter::once(*map);
|
||||
let scores = get_leaderboard(
|
||||
ctx,
|
||||
&env,
|
||||
iter,
|
||||
mode_override.or(mode),
|
||||
show_unranked,
|
||||
order,
|
||||
guild,
|
||||
)
|
||||
.await?;
|
||||
(scores, false)
|
||||
}
|
||||
EmbedType::Beatmapset(maps, _) if maps.is_empty() => (vec![], false),
|
||||
EmbedType::Beatmapset(maps, mode) => {
|
||||
let show_diff = maps.len() > 1;
|
||||
(
|
||||
get_leaderboard(
|
||||
ctx,
|
||||
&env,
|
||||
maps,
|
||||
mode_override.or(mode),
|
||||
show_unranked,
|
||||
order,
|
||||
guild,
|
||||
)
|
||||
.await?,
|
||||
show_diff,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn display_rankings_table(
|
||||
ctx: &Context,
|
||||
to: Message,
|
||||
scores: Vec<Ranking>,
|
||||
bm: &BeatmapWithMode,
|
||||
show_diff: bool,
|
||||
order: OrderBy,
|
||||
) -> Result<()> {
|
||||
let has_lazer_score = scores.iter().any(|v| v.score.mods.is_lazer);
|
||||
|
@ -558,14 +624,33 @@ pub async fn display_rankings_table(
|
|||
return Box::pin(future::ready(Ok(false)));
|
||||
}
|
||||
let scores = scores[start..end].to_vec();
|
||||
let bm = (bm.0.clone(), bm.1);
|
||||
let header = header.clone();
|
||||
Box::pin(async move {
|
||||
const SCORE_HEADERS: [&str; 8] =
|
||||
["#", "Score", "Mods", "Rank", "Acc", "Combo", "Miss", "User"];
|
||||
const PP_HEADERS: [&str; 8] =
|
||||
["#", "PP", "Mods", "Rank", "Acc", "Combo", "Miss", "User"];
|
||||
const ALIGNS: [Align; 8] = [Right, Right, Right, Right, Right, Right, Right, Left];
|
||||
let headers: [&'static str; 9] = [
|
||||
"#",
|
||||
match order {
|
||||
OrderBy::PP => "pp",
|
||||
OrderBy::Score => "Score",
|
||||
},
|
||||
if show_diff { "Map" } else { "Mods" },
|
||||
"Rank",
|
||||
"Acc",
|
||||
"Combo",
|
||||
"Miss",
|
||||
"When",
|
||||
"User",
|
||||
];
|
||||
let aligns: [Align; 9] = [
|
||||
Right,
|
||||
Right,
|
||||
if show_diff { Left } else { Right },
|
||||
Right,
|
||||
Right,
|
||||
Right,
|
||||
Right,
|
||||
Right,
|
||||
Left,
|
||||
];
|
||||
|
||||
let score_arr = scores
|
||||
.iter()
|
||||
|
@ -575,9 +660,11 @@ pub async fn display_rankings_table(
|
|||
id,
|
||||
Ranking {
|
||||
pp,
|
||||
beatmap,
|
||||
official,
|
||||
member,
|
||||
score,
|
||||
star,
|
||||
},
|
||||
)| {
|
||||
[
|
||||
|
@ -594,21 +681,35 @@ pub async fn display_rankings_table(
|
|||
})
|
||||
}
|
||||
},
|
||||
score.mods.to_string(),
|
||||
if show_diff {
|
||||
let trimmed_diff = if beatmap.difficulty_name.len() > 20 {
|
||||
let mut s = beatmap.difficulty_name.clone();
|
||||
s.truncate(17);
|
||||
s + "..."
|
||||
} else {
|
||||
beatmap.difficulty_name.clone()
|
||||
};
|
||||
format!(
|
||||
"[{:.2}*] {} {}",
|
||||
star,
|
||||
trimmed_diff,
|
||||
score.mods.to_string()
|
||||
)
|
||||
} else {
|
||||
score.mods.to_string()
|
||||
},
|
||||
score.rank.to_string(),
|
||||
format!("{:.2}%", score.accuracy(bm.1)),
|
||||
format!("{:.2}%", score.accuracy(score.mode)),
|
||||
format!("{}x", score.max_combo),
|
||||
format!("{}", score.count_miss),
|
||||
time_before_now(&score.date),
|
||||
member.to_string(),
|
||||
]
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let score_table = match order {
|
||||
OrderBy::PP => table_formatting(&PP_HEADERS, &ALIGNS, score_arr),
|
||||
OrderBy::Score => table_formatting(&SCORE_HEADERS, &ALIGNS, score_arr),
|
||||
};
|
||||
let score_table = table_formatting(&headers, &aligns, score_arr);
|
||||
let content = MessageBuilder::new()
|
||||
.push_line(header.as_ref())
|
||||
.push_line(score_table)
|
||||
|
@ -617,15 +718,6 @@ pub async fn display_rankings_table(
|
|||
page + 1,
|
||||
total_pages,
|
||||
))
|
||||
.push(
|
||||
if let crate::models::ApprovalStatus::Ranked(_) = bm.0.approval {
|
||||
""
|
||||
} else if order == OrderBy::PP {
|
||||
"PP was calculated by `oppai-rs`, **not** official values.\n"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
m.edit(&ctx, EditMessage::new().content(content)).await?;
|
||||
|
|
|
@ -452,7 +452,7 @@ impl Beatmap {
|
|||
|
||||
pub fn mention(&self, override_mode: Option<Mode>, mods: &Mods) -> String {
|
||||
format!(
|
||||
"[`{}`]({})",
|
||||
"[`{}`](<{}>)",
|
||||
self.short_link(override_mode, mods),
|
||||
self.mode_link(override_mode),
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue