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