mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-10 04:30:29 +00:00
osu: Allow returning a full file instead when requesting a table check/leaderboard
This commit is contained in:
parent
ed15406f51
commit
a36fa87964
4 changed files with 215 additions and 146 deletions
|
@ -6,7 +6,7 @@ use display::display_beatmapset;
|
|||
use embeds::ScoreEmbedBuilder;
|
||||
use link_parser::EmbedType;
|
||||
use poise::{ChoiceParameter, CreateReply};
|
||||
use serenity::all::User;
|
||||
use serenity::all::{CreateAttachment, User};
|
||||
use server_rank::get_leaderboard_from_embed;
|
||||
|
||||
/// osu!-related command group.
|
||||
|
@ -581,6 +581,7 @@ async fn leaderboard<U: HasOsuEnv>(
|
|||
"Here are the top scores of **{}** on {}",
|
||||
guild.name, scoreboard_msg,
|
||||
);
|
||||
let has_lazer_score = scores.iter().any(|v| v.score.mods.is_lazer);
|
||||
|
||||
match style {
|
||||
ScoreListStyle::Table => {
|
||||
|
@ -589,11 +590,30 @@ async fn leaderboard<U: HasOsuEnv>(
|
|||
ctx.serenity_context(),
|
||||
reply,
|
||||
scores,
|
||||
has_lazer_score,
|
||||
show_diff,
|
||||
sort.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ScoreListStyle::File => {
|
||||
ctx.send(
|
||||
CreateReply::default()
|
||||
.content(header)
|
||||
.attachment(CreateAttachment::bytes(
|
||||
server_rank::rankings_to_table(
|
||||
&scores,
|
||||
0,
|
||||
scores.len(),
|
||||
has_lazer_score,
|
||||
show_diff,
|
||||
order,
|
||||
),
|
||||
"rankings.txt",
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ScoreListStyle::Grid => {
|
||||
let reply = ctx.reply(header).await?;
|
||||
style
|
||||
|
|
|
@ -16,6 +16,8 @@ mod scores {
|
|||
Table,
|
||||
#[name = "List of Embeds"]
|
||||
Grid,
|
||||
#[name = "Table File"]
|
||||
File,
|
||||
}
|
||||
|
||||
impl Default for ScoreListStyle {
|
||||
|
@ -46,6 +48,7 @@ mod scores {
|
|||
) -> Result<()> {
|
||||
match self {
|
||||
ScoreListStyle::Table => table::display_scores_table(scores, ctx, m).await,
|
||||
ScoreListStyle::File => table::display_scores_as_file(scores, ctx, m).await,
|
||||
ScoreListStyle::Grid => grid::display_scores_grid(scores, ctx, guild_id, m).await,
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +155,7 @@ mod scores {
|
|||
use std::borrow::Cow;
|
||||
|
||||
use pagination::paginate_with_first_message;
|
||||
use serenity::all::CreateActionRow;
|
||||
use serenity::all::{CreateActionRow, CreateAttachment};
|
||||
|
||||
use youmubot_prelude::table_format::Align::{Left, Right};
|
||||
use youmubot_prelude::table_format::{table_formatting, Align};
|
||||
|
@ -162,6 +165,29 @@ mod scores {
|
|||
use crate::discord::{time_before_now, Beatmap, BeatmapInfo, OsuEnv};
|
||||
use crate::models::Score;
|
||||
|
||||
pub async fn display_scores_as_file(
|
||||
scores: Vec<Score>,
|
||||
ctx: &Context,
|
||||
mut on: impl CanEdit,
|
||||
) -> Result<()> {
|
||||
if scores.is_empty() {
|
||||
on.apply_edit(CreateReply::default().content("No plays found"))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
let p = Paginate {
|
||||
env: ctx.data.read().await.get::<OsuEnv>().unwrap().clone(),
|
||||
header: on.get_message().await?.content.clone(),
|
||||
scores,
|
||||
};
|
||||
let content = p.to_table(0, p.scores.len()).await;
|
||||
on.apply_edit(
|
||||
CreateReply::default().attachment(CreateAttachment::bytes(content, "table.txt")),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn display_scores_table(
|
||||
scores: Vec<Score>,
|
||||
ctx: &Context,
|
||||
|
@ -197,30 +223,13 @@ mod scores {
|
|||
fn total_pages(&self) -> usize {
|
||||
(self.scores.len() + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE
|
||||
}
|
||||
}
|
||||
|
||||
const ITEMS_PER_PAGE: usize = 5;
|
||||
async fn to_table(&self, start: usize, end: usize) -> String {
|
||||
let scores = &self.scores[start..end];
|
||||
let meta_cache = &self.env.beatmaps;
|
||||
let oppai = &self.env.oppai;
|
||||
|
||||
#[async_trait]
|
||||
impl pagination::Paginate for Paginate {
|
||||
async fn render(
|
||||
&mut self,
|
||||
page: u8,
|
||||
btns: Vec<CreateActionRow>,
|
||||
) -> Result<Option<CreateReply>> {
|
||||
let env = &self.env;
|
||||
|
||||
let meta_cache = &env.beatmaps;
|
||||
let oppai = &env.oppai;
|
||||
let page = page as usize;
|
||||
let start = page * ITEMS_PER_PAGE;
|
||||
let end = self.scores.len().min(start + ITEMS_PER_PAGE);
|
||||
if start >= end {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let plays = &self.scores[start..end];
|
||||
let beatmaps = plays
|
||||
let beatmaps = scores
|
||||
.iter()
|
||||
.map(|play| async move {
|
||||
let beatmap = meta_cache.get_beatmap(play.beatmap_id, play.mode).await?;
|
||||
|
@ -234,7 +243,7 @@ mod scores {
|
|||
.map(|v| v.ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pps = plays
|
||||
let pps = scores
|
||||
.iter()
|
||||
.map(|p| async move {
|
||||
match p.pp.map(|pp| format!("{:.2}", pp)) {
|
||||
|
@ -257,7 +266,7 @@ mod scores {
|
|||
|
||||
let (beatmaps, pps) = future::join(beatmaps, pps).await;
|
||||
|
||||
let ranks = plays
|
||||
let ranks = scores
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, p)| -> Cow<'static, str> {
|
||||
|
@ -288,7 +297,7 @@ mod scores {
|
|||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, b)| {
|
||||
let play = &plays[i];
|
||||
let play = &scores[i];
|
||||
b.map(|(beatmap, info)| {
|
||||
format!(
|
||||
"[{:.1}*] {} - {} [{}] ({})",
|
||||
|
@ -307,7 +316,7 @@ mod scores {
|
|||
["#", "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 = scores
|
||||
.iter()
|
||||
.zip(beatmaps.iter())
|
||||
.zip(ranks.iter().zip(pps.iter()))
|
||||
|
@ -325,9 +334,30 @@ mod scores {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let score_table = table_formatting(&SCORE_HEADERS, &SCORE_ALIGNS, score_arr);
|
||||
table_formatting(&SCORE_HEADERS, &SCORE_ALIGNS, score_arr)
|
||||
}
|
||||
}
|
||||
|
||||
const ITEMS_PER_PAGE: usize = 5;
|
||||
|
||||
#[async_trait]
|
||||
impl pagination::Paginate for Paginate {
|
||||
async fn render(
|
||||
&mut self,
|
||||
page: u8,
|
||||
btns: Vec<CreateActionRow>,
|
||||
) -> Result<Option<CreateReply>> {
|
||||
let page = page as usize;
|
||||
let start = page * ITEMS_PER_PAGE;
|
||||
let end = self.scores.len().min(start + ITEMS_PER_PAGE);
|
||||
if start >= end {
|
||||
return Ok(None);
|
||||
}
|
||||
let plays = &self.scores[start..end];
|
||||
|
||||
let has_oppai = plays.iter().any(|p| p.pp.is_none());
|
||||
|
||||
let score_table = self.to_table(start, end).await;
|
||||
let mut content = serenity::utils::MessageBuilder::new();
|
||||
content
|
||||
.push_line(&self.header)
|
||||
|
|
|
@ -413,7 +413,8 @@ pub fn handle_lb_button<'a>(
|
|||
.content(format!("Here are the top scores on {}!", scoreboard_msg)),
|
||||
)
|
||||
.await?;
|
||||
display_rankings_table(ctx, reply, scores, show_diff, order).await?;
|
||||
let has_lazer_score = scores.iter().any(|s| s.score.mods.is_lazer);
|
||||
display_rankings_table(ctx, reply, scores, has_lazer_score, show_diff, order).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
|||
use chrono::DateTime;
|
||||
use pagination::paginate_with_first_message;
|
||||
use serenity::{
|
||||
all::{GuildId, Member, PartialGuild},
|
||||
all::{CreateAttachment, CreateMessage, GuildId, Member, PartialGuild},
|
||||
framework::standard::{macros::command, Args, CommandResult},
|
||||
model::channel::Message,
|
||||
utils::MessageBuilder,
|
||||
|
@ -401,26 +401,41 @@ pub async fn show_leaderboard(ctx: &Context, msg: &Message, mut args: Args) -> C
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let header = format!(
|
||||
"Here are the top scores of **{}** on {}",
|
||||
guild.name(&ctx).unwrap(),
|
||||
scoreboard_msg,
|
||||
);
|
||||
let has_lazer_score = scores.iter().any(|v| v.score.mods.is_lazer);
|
||||
|
||||
match style {
|
||||
ScoreListStyle::Table => {
|
||||
let reply = msg
|
||||
.reply(
|
||||
let reply = msg.reply(&ctx, header).await?;
|
||||
display_rankings_table(ctx, reply, scores, has_lazer_score, show_diff, order).await?;
|
||||
}
|
||||
ScoreListStyle::File => {
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
format!("⌛ Loading top scores on {}...", scoreboard_msg),
|
||||
CreateMessage::new()
|
||||
.content(header)
|
||||
.reference_message(msg)
|
||||
.add_file(CreateAttachment::bytes(
|
||||
rankings_to_table(
|
||||
&scores,
|
||||
0,
|
||||
scores.len(),
|
||||
has_lazer_score,
|
||||
show_diff,
|
||||
order,
|
||||
),
|
||||
"rankings.txt",
|
||||
)),
|
||||
)
|
||||
.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 {} of this server!",
|
||||
scoreboard_msg
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
let reply = msg.reply(&ctx, header).await?;
|
||||
style
|
||||
.display_scores(
|
||||
scores.into_iter().map(|s| s.score).collect(),
|
||||
|
@ -600,19 +615,107 @@ pub async fn get_leaderboard_from_embed(
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn rankings_to_table(
|
||||
scores: &[Ranking],
|
||||
start: usize,
|
||||
end: usize,
|
||||
has_lazer_score: bool,
|
||||
show_diff: bool,
|
||||
order: OrderBy,
|
||||
) -> String {
|
||||
assert!(start < end);
|
||||
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()
|
||||
.enumerate()
|
||||
.map(
|
||||
|(
|
||||
id,
|
||||
Ranking {
|
||||
pp,
|
||||
beatmap,
|
||||
official,
|
||||
member,
|
||||
score,
|
||||
star,
|
||||
},
|
||||
)| {
|
||||
[
|
||||
format!("{}", 1 + id + start),
|
||||
match order {
|
||||
OrderBy::PP => {
|
||||
format!("{:.2}{}", pp, if *official { "" } else { "[?]" })
|
||||
}
|
||||
OrderBy::Score => {
|
||||
crate::discord::embeds::grouped_number(if has_lazer_score {
|
||||
score.normalized_score as u64
|
||||
} else {
|
||||
score.score
|
||||
})
|
||||
}
|
||||
},
|
||||
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(score.mode)),
|
||||
format!("{}x", score.max_combo),
|
||||
format!("{}", score.count_miss),
|
||||
time_before_now(&score.date),
|
||||
member.to_string(),
|
||||
]
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
table_formatting(&headers, &aligns, &score_arr)
|
||||
}
|
||||
|
||||
pub async fn display_rankings_table(
|
||||
ctx: &Context,
|
||||
to: Message,
|
||||
scores: Vec<Ranking>,
|
||||
has_lazer_score: bool,
|
||||
show_diff: bool,
|
||||
order: OrderBy,
|
||||
) -> Result<()> {
|
||||
let has_lazer_score = scores.iter().any(|v| v.score.mods.is_lazer);
|
||||
|
||||
const ITEMS_PER_PAGE: usize = 5;
|
||||
let total_len = scores.len();
|
||||
let total_pages = (total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE;
|
||||
let header = Arc::new(to.content.clone());
|
||||
let header = to.content.clone();
|
||||
|
||||
paginate_with_first_message(
|
||||
paginate_from_fn(move |page: u8, btns| {
|
||||
|
@ -622,106 +725,21 @@ pub async fn display_rankings_table(
|
|||
return Box::pin(future::ready(Ok(None)));
|
||||
}
|
||||
let scores = scores[start..end].to_vec();
|
||||
let header = header.clone();
|
||||
Box::pin(async move {
|
||||
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()
|
||||
.enumerate()
|
||||
.map(
|
||||
|(
|
||||
id,
|
||||
Ranking {
|
||||
pp,
|
||||
beatmap,
|
||||
official,
|
||||
member,
|
||||
score,
|
||||
star,
|
||||
},
|
||||
)| {
|
||||
[
|
||||
format!("{}", 1 + id + start),
|
||||
match order {
|
||||
OrderBy::PP => {
|
||||
format!("{:.2}{}", pp, if *official { "" } else { "[?]" })
|
||||
}
|
||||
OrderBy::Score => {
|
||||
crate::discord::embeds::grouped_number(if has_lazer_score {
|
||||
score.normalized_score as u64
|
||||
} else {
|
||||
score.score
|
||||
})
|
||||
}
|
||||
},
|
||||
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(score.mode)),
|
||||
format!("{}x", score.max_combo),
|
||||
format!("{}", score.count_miss),
|
||||
time_before_now(&score.date),
|
||||
member.to_string(),
|
||||
]
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let score_table = table_formatting(&headers, &aligns, score_arr);
|
||||
let content = MessageBuilder::new()
|
||||
.push_line(header.as_ref())
|
||||
.push_line(score_table)
|
||||
.push_line(format!(
|
||||
"Page **{}**/**{}**. Not seeing your scores? Run `osu check` to update.",
|
||||
page + 1,
|
||||
total_pages,
|
||||
))
|
||||
.build();
|
||||
|
||||
Ok(Some(
|
||||
CreateReply::default().content(content).components(btns),
|
||||
let score_table =
|
||||
rankings_to_table(&scores, start, end, has_lazer_score, show_diff, order);
|
||||
let content = MessageBuilder::new()
|
||||
.push_line(&header)
|
||||
.push_line(score_table)
|
||||
.push_line(format!(
|
||||
"Page **{}**/**{}**. Not seeing your scores? Run `osu check` to update.",
|
||||
page + 1,
|
||||
total_pages,
|
||||
))
|
||||
})
|
||||
.build();
|
||||
|
||||
Box::pin(future::ready(Ok(Some(
|
||||
CreateReply::default().content(content).components(btns),
|
||||
))))
|
||||
})
|
||||
.with_page_count(total_pages),
|
||||
ctx,
|
||||
|
|
Loading…
Add table
Reference in a new issue