Reuse table_formatting logic for almost everything (#39)

This commit is contained in:
huynd2001 2024-03-09 23:01:44 -05:00 committed by GitHub
parent 54426ed477
commit 13683aa229
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 273 additions and 388 deletions

View file

@ -1,3 +1,5 @@
use std::{collections::HashMap, sync::Arc, time::Duration};
use codeforces::Contest; use codeforces::Contest;
use serenity::{ use serenity::{
builder::{CreateMessage, EditMessage}, builder::{CreateMessage, EditMessage},
@ -8,8 +10,15 @@ use serenity::{
model::{channel::Message, guild::Member}, model::{channel::Message, guild::Member},
utils::MessageBuilder, utils::MessageBuilder,
}; };
use std::{collections::HashMap, sync::Arc, time::Duration};
use youmubot_prelude::*; use db::{CfSavedUsers, CfUser};
pub use hook::InfoHook;
use youmubot_prelude::table_format::table_formatting_unsafe;
use youmubot_prelude::table_format::Align::{Left, Right};
use youmubot_prelude::{
table_format::{table_formatting, Align},
*,
};
mod announcer; mod announcer;
mod db; mod db;
@ -26,10 +35,6 @@ impl TypeMapKey for CFClient {
type Value = Arc<codeforces::Client>; type Value = Arc<codeforces::Client>;
} }
use db::{CfSavedUsers, CfUser};
pub use hook::InfoHook;
/// Sets up the CF databases. /// Sets up the CF databases.
pub async fn setup(path: &std::path::Path, data: &mut TypeMap, announcers: &mut AnnouncerHandler) { pub async fn setup(path: &std::path::Path, data: &mut TypeMap, announcers: &mut AnnouncerHandler) {
CfSavedUsers::insert_into(data, path.join("cf_saved_users.yaml")) CfSavedUsers::insert_into(data, path.join("cf_saved_users.yaml"))
@ -174,6 +179,7 @@ pub async fn ranks(ctx: &Context, m: &Message) -> CommandResult {
paginate_reply_fn( paginate_reply_fn(
move |page, ctx, msg| { move |page, ctx, msg| {
use Align::*;
let ranks = ranks.clone(); let ranks = ranks.clone();
Box::pin(async move { Box::pin(async move {
let page = page as usize; let page = page as usize;
@ -184,56 +190,37 @@ pub async fn ranks(ctx: &Context, m: &Message) -> CommandResult {
} }
let ranks = &ranks[start..end]; let ranks = &ranks[start..end];
let handle_width = ranks.iter().map(|(_, cfu)| cfu.handle.len()).max().unwrap(); const HEADERS: [&'static str; 4] = ["Rank", "Rating", "Handle", "Username"];
let username_width = ranks const ALIGNS: [Align; 4] = [Right, Right, Left, Left];
let ranks_arr = ranks
.iter() .iter()
.map(|(mem, _)| mem.distinct().len()) .enumerate()
.max() .map(|(i, (mem, cfu))| {
.unwrap(); [
format!("#{}", 1 + i + start),
let mut m = MessageBuilder::new();
m.push_line("```");
// Table header
m.push_line(format!(
"Rank | Rating | {:hw$} | {:uw$}",
"Handle",
"Username",
hw = handle_width,
uw = username_width
));
m.push_line(format!(
"----------------{:->hw$}---{:->uw$}",
"",
"",
hw = handle_width,
uw = username_width
));
for (id, (mem, cfu)) in ranks.iter().enumerate() {
let id = id + start + 1;
m.push_line(format!(
"{:>4} | {:>6} | {:hw$} | {:uw$}",
format!("#{}", id),
cfu.rating cfu.rating
.map(|v| v.to_string()) .map(|v| v.to_string())
.unwrap_or_else(|| "----".to_owned()), .unwrap_or_else(|| "----".to_owned()),
cfu.handle, cfu.handle.clone(),
mem.distinct(), mem.distinct(),
hw = handle_width, ]
uw = username_width })
)); .collect::<Vec<_>>();
}
m.push_line("```"); let table = table_formatting(&HEADERS, &ALIGNS, ranks_arr);
m.push(format!(
let content = MessageBuilder::new()
.push_line(table)
.push_line(format!(
"Page **{}/{}**. Last updated **{}**", "Page **{}/{}**. Last updated **{}**",
page + 1, page + 1,
total_pages, total_pages,
last_updated.to_rfc2822() last_updated.to_rfc2822()
)); ))
.build();
msg.edit(ctx, EditMessage::new().content(m.build())).await?; msg.edit(ctx, EditMessage::new().content(content)).await?;
Ok(true) Ok(true)
}) })
}, },
@ -340,79 +327,66 @@ pub(crate) async fn contest_rank_table(
return Ok(false); return Ok(false);
} }
let ranks = &ranks[start..end]; let ranks = &ranks[start..end];
let hw = ranks
.iter()
.map(|(mem, handle, _)| format!("{} ({})", handle, mem.distinct()).len())
.max()
.unwrap_or(0)
.max(6);
let hackw = ranks
.iter()
.map(|(_, _, row)| {
format!(
"{}/{}",
row.successful_hack_count, row.unsuccessful_hack_count
)
.len()
})
.max()
.unwrap_or(0)
.max(5);
let mut table = MessageBuilder::new(); let score_headers: Vec<&str> = [
let mut header = MessageBuilder::new(); vec!["Rank", "Handle", "User", "Total", "Hacks"],
// Header problems
header.push(format!( .iter()
" Rank | {:hw$} | Total | {:hackw$}", .map(|p| p.index.as_str())
"Handle", .collect::<Vec<&str>>(),
"Hacks", ]
hw = hw, .concat();
hackw = hackw
)); let score_aligns: Vec<Align> = [
for p in &problems { vec![Right, Left, Left, Right, Right],
header.push(format!(" | {:4}", p.index)); problems.iter().map(|_| Right).collect::<Vec<Align>>(),
]
.concat();
let score_arr = ranks
.iter()
.map(|(mem, handle, row)| {
let mut p_results: Vec<String> = Vec::new();
for result in &row.problem_results {
if result.points > 0.0 {
p_results.push(format!("{}", result.points));
} else if result.best_submission_time_seconds.is_some() {
p_results.push(format!("{}", "?"));
} else if result.rejected_attempt_count > 0 {
p_results.push(format!("-{}", result.rejected_attempt_count));
} else {
p_results.push(format!("{}", "----"));
}
} }
let header = header.build();
table
.push_line(&header)
.push_line(format!("{:-<w$}", "", w = header.len()));
// Body [
for (mem, handle, row) in ranks { vec![
table.push(format!( format!("{}", row.rank),
"{:>5} | {:<hw$} | {:>5.0} | {:<hackw$}", handle.clone(),
row.rank, mem.distinct(),
format!("{} ({})", handle, mem.distinct()), format!("{}", row.points),
row.points,
format!( format!(
"{}/{}", "{}/{}",
row.successful_hack_count, row.unsuccessful_hack_count row.successful_hack_count, row.unsuccessful_hack_count
), ),
hw = hw, ],
hackw = hackw p_results,
)); ]
for p in &row.problem_results { .concat()
table.push(" | "); })
if p.points > 0.0 { .collect::<Vec<_>>();
table.push(format!("{:^4.0}", p.points));
} else if p.best_submission_time_seconds.is_some() {
table.push(format!("{:^4}", "?"));
} else if p.rejected_attempt_count > 0 {
table.push(format!("{:^4}", format!("-{}", p.rejected_attempt_count)));
} else {
table.push(format!("{:^4}", ""));
}
}
table.push_line("");
}
let mut m = MessageBuilder::new(); let score_table = table_formatting_unsafe(&score_headers, &score_aligns, score_arr);
m.push_bold_safe(&contest.name)
let content = MessageBuilder::new()
.push_bold_safe(&contest.name)
.push(" ") .push(" ")
.push_line(contest.url()) .push_line(contest.url())
.push_codeblock(table.build(), None) .push_line(score_table)
.push_line(format!("Page **{}/{}**", page + 1, total_pages)); .push_line(format!("Page **{}/{}**", page + 1, total_pages))
msg.edit(ctx, EditMessage::new().content(m.build())).await?; .build();
msg.edit(ctx, EditMessage::new().content(content)).await?;
Ok(true) Ok(true)
}) })
}, },

View file

@ -1,4 +1,3 @@
use crate::db::Roles as DB;
use serenity::{ use serenity::{
builder::EditMessage, builder::EditMessage,
framework::standard::{macros::command, Args, CommandResult}, framework::standard::{macros::command, Args, CommandResult},
@ -9,9 +8,13 @@ use serenity::{
}, },
utils::MessageBuilder, utils::MessageBuilder,
}; };
use youmubot_prelude::*;
pub use reaction_watcher::Watchers as ReactionWatchers; pub use reaction_watcher::Watchers as ReactionWatchers;
use youmubot_prelude::table_format::Align::Right;
use youmubot_prelude::table_format::{table_formatting, Align};
use youmubot_prelude::*;
use crate::db::Roles as DB;
#[command("listroles")] #[command("listroles")]
#[description = "List all available roles in the server."] #[description = "List all available roles in the server."]
@ -50,59 +53,31 @@ async fn list(ctx: &Context, m: &Message, _: Args) -> CommandResult {
if end <= start { if end <= start {
return Ok(false); return Ok(false);
} }
let roles = &roles[start..end]; let roles = &roles[start..end];
let nw = roles // name width
const ROLE_HEADERS: [&'static str; 3] = ["Name", "ID", "Description"];
const ROLE_ALIGNS: [Align; 3] = [Right, Right, Right];
let roles_arr = roles
.iter() .iter()
.map(|(r, _)| r.name.len()) .map(|(role, description)| {
.max() [
.unwrap() role.name.clone(),
.max(6); format!("{}", role.id),
let idw = roles[0].0.id.to_string().len(); description.clone(),
let dw = roles ]
.iter() })
.map(|v| v.1.len()) .collect::<Vec<_>>();
.max()
.unwrap()
.max(" Description ".len());
let mut m = MessageBuilder::new();
m.push_line("```");
// Table header let roles_table = table_formatting(&ROLE_HEADERS, &ROLE_ALIGNS, roles_arr);
m.push_line(format!(
"{:nw$} | {:idw$} | {:dw$}",
"Name",
"ID",
"Description",
nw = nw,
idw = idw,
dw = dw,
));
m.push_line(format!(
"{:->nw$}---{:->idw$}---{:->dw$}",
"",
"",
"",
nw = nw,
idw = idw,
dw = dw,
));
for (role, description) in roles.iter() { let content = MessageBuilder::new()
m.push_line(format!( .push_line(roles_table)
"{:nw$} | {:idw$} | {:dw$}", .push_line(format!("Page **{}/{}**", page + 1, pages))
role.name, .build();
role.id,
description,
nw = nw,
idw = idw,
dw = dw,
));
}
m.push_line("```");
m.push(format!("Page **{}/{}**", page + 1, pages));
msg.edit(ctx, EditMessage::new().content(m.to_string())) msg.edit(ctx, EditMessage::new().content(content)).await?;
.await?;
Ok(true) Ok(true)
}) })
}, },
@ -415,7 +390,6 @@ async fn rmrolemessage(ctx: &Context, m: &Message, _args: Args) -> CommandResult
} }
mod reaction_watcher { mod reaction_watcher {
use crate::db::{Role, RoleMessage, Roles};
use dashmap::DashMap; use dashmap::DashMap;
use flume::{Receiver, Sender}; use flume::{Receiver, Sender};
use serenity::{ use serenity::{
@ -427,8 +401,11 @@ mod reaction_watcher {
id::{ChannelId, GuildId, MessageId}, id::{ChannelId, GuildId, MessageId},
}, },
}; };
use youmubot_prelude::*; use youmubot_prelude::*;
use crate::db::{Role, RoleMessage, Roles};
/// A set of watchers. /// A set of watchers.
#[derive(Debug)] #[derive(Debug)]
pub struct Watchers { pub struct Watchers {

View file

@ -2,10 +2,12 @@ pub use beatmapset::display_beatmapset;
pub use scores::ScoreListStyle; pub use scores::ScoreListStyle;
mod scores { mod scores {
use crate::models::{Mode, Score};
use serenity::{framework::standard::CommandResult, model::channel::Message}; use serenity::{framework::standard::CommandResult, model::channel::Message};
use youmubot_prelude::*; use youmubot_prelude::*;
use crate::models::{Mode, Score};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// The style for the scores list to be displayed. /// The style for the scores list to be displayed.
pub enum ScoreListStyle { pub enum ScoreListStyle {
@ -47,13 +49,15 @@ mod scores {
} }
pub mod grid { pub mod grid {
use serenity::builder::EditMessage;
use serenity::{framework::standard::CommandResult, model::channel::Message};
use youmubot_prelude::*;
use crate::discord::{ use crate::discord::{
cache::save_beatmap, BeatmapCache, BeatmapMetaCache, BeatmapWithMode, cache::save_beatmap, BeatmapCache, BeatmapMetaCache, BeatmapWithMode,
}; };
use crate::models::{Mode, Score}; use crate::models::{Mode, Score};
use serenity::builder::EditMessage;
use serenity::{framework::standard::CommandResult, model::channel::Message};
use youmubot_prelude::*;
pub async fn display_scores_grid<'a>( pub async fn display_scores_grid<'a>(
scores: Vec<Score>, scores: Vec<Score>,
@ -126,12 +130,16 @@ mod scores {
pub mod table { pub mod table {
use std::borrow::Cow; use std::borrow::Cow;
use serenity::builder::EditMessage;
use serenity::{framework::standard::CommandResult, model::channel::Message};
use youmubot_prelude::table_format::Align::{Left, Right};
use youmubot_prelude::table_format::{table_formatting, Align};
use youmubot_prelude::*;
use crate::discord::oppai_cache::Accuracy; use crate::discord::oppai_cache::Accuracy;
use crate::discord::{Beatmap, BeatmapCache, BeatmapInfo, BeatmapMetaCache}; use crate::discord::{Beatmap, BeatmapCache, BeatmapInfo, BeatmapMetaCache};
use crate::models::{Mode, Score}; use crate::models::{Mode, Score};
use serenity::builder::EditMessage;
use serenity::{framework::standard::CommandResult, model::channel::Message};
use youmubot_prelude::*;
pub async fn display_scores_table<'a>( pub async fn display_scores_table<'a>(
scores: Vec<Score>, scores: Vec<Score>,
@ -196,7 +204,8 @@ mod scores {
.collect::<stream::FuturesOrdered<_>>() .collect::<stream::FuturesOrdered<_>>()
.map(|v| v.ok()) .map(|v| v.ok())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let pp = plays
let pps = plays
.iter() .iter()
.map(|p| async move { .map(|p| async move {
match p.pp.map(|pp| format!("{:.2}", pp)) { match p.pp.map(|pp| format!("{:.2}", pp)) {
@ -226,7 +235,8 @@ mod scores {
.collect::<stream::FuturesOrdered<_>>() .collect::<stream::FuturesOrdered<_>>()
.map(|v| v.unwrap_or_else(|_| "-".to_owned())) .map(|v| v.unwrap_or_else(|_| "-".to_owned()))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let (beatmaps, pp) = future::join(beatmaps, pp).await;
let (beatmaps, pps) = future::join(beatmaps, pps).await;
let ranks = plays let ranks = plays
.iter() .iter()
@ -274,64 +284,36 @@ mod scores {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let pw = pp.iter().map(|v| v.len()).max().unwrap_or(2); const SCORE_HEADERS: [&'static str; 6] =
/*mods width*/ ["#", "PP", "Acc", "Ranks", "Mods", "Beatmap"];
let mw = plays.iter().map(|v| v.mods.str_len()).max().unwrap().max(4); const SCORE_ALIGNS: [Align; 6] = [Right, Right, Right, Right, Right, Left];
/*beatmap names*/
let bw = beatmaps.iter().map(|v| v.len()).max().unwrap().max(7);
/* ranks width */
let rw = ranks.iter().map(|v| v.len()).max().unwrap().max(5);
let mut m = serenity::utils::MessageBuilder::new(); let score_arr = plays
// Table header .iter()
m.push_line(format!( .zip(beatmaps.iter())
" # | {:pw$} | accuracy | {:rw$} | {:mw$} | {:bw$}", .zip(ranks.iter().zip(pps.iter()))
"pp", .enumerate()
"ranks", .map(|(id, ((play, beatmap), (rank, pp)))| {
"mods", [
"beatmap", format!("{}", id + start + 1),
rw = rw, format!("{}", pp),
pw = pw,
mw = mw,
bw = bw
));
m.push_line(format!(
"------{:-<pw$}--------------{:-<rw$}---{:-<mw$}---{:-<bw$}",
"",
"",
"",
"",
rw = rw,
pw = pw,
mw = mw,
bw = bw
));
// Each row
for (id, (play, beatmap)) in plays.iter().zip(beatmaps.iter()).enumerate() {
m.push_line(format!(
"{:>3} | {:>pw$} | {:>8} | {:>rw$} | {} | {:bw$}",
id + start + 1,
pp[id],
format!("{:.2}%", play.accuracy(self.mode)), format!("{:.2}%", play.accuracy(self.mode)),
ranks[id], format!("{}", rank),
play.mods.to_string_padded(mw), play.mods.to_string(),
beatmap, beatmap.clone(),
rw = rw, ]
pw = pw, })
bw = bw .collect::<Vec<_>>();
));
} let score_table = table_formatting(&SCORE_HEADERS, &SCORE_ALIGNS, score_arr);
// End
let table = m.build().replace("```", "\\`\\`\\`"); let content = serenity::utils::MessageBuilder::new()
let mut m = serenity::utils::MessageBuilder::new(); .push_line(score_table)
m.push_codeblock(table, None).push_line(format!( .push_line(format!("Page **{}/{}**", page + 1, self.total_pages()))
"Page **{}/{}**", .push_line("[?] means pp was predicted by oppai-rs.")
page + 1, .build();
self.total_pages()
)); msg.edit(ctx, EditMessage::new().content(content)).await?;
m.push_line("[?] means pp was predicted by oppai-rs.");
msg.edit(ctx, EditMessage::new().content(m.to_string()))
.await?;
hourglass.delete(ctx).await?; hourglass.delete(ctx).await?;
Ok(true) Ok(true)
} }
@ -344,20 +326,22 @@ mod scores {
} }
mod beatmapset { mod beatmapset {
use crate::{
discord::{
cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapCache, BeatmapWithMode,
},
models::{Beatmap, Mode, Mods},
};
use serenity::{ use serenity::{
all::Reaction, all::Reaction,
builder::{CreateEmbedFooter, EditMessage}, builder::{CreateEmbedFooter, EditMessage},
model::channel::Message, model::channel::Message,
model::channel::ReactionType, model::channel::ReactionType,
}; };
use youmubot_prelude::*; use youmubot_prelude::*;
use crate::{
discord::{
cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapCache, BeatmapWithMode,
},
models::{Beatmap, Mode, Mods},
};
const SHOW_ALL_EMOTE: &str = "🗒️"; const SHOW_ALL_EMOTE: &str = "🗒️";
pub async fn display_beatmapset( pub async fn display_beatmapset(
@ -456,7 +440,7 @@ mod beatmapset {
self.mods, self.mods,
info, info,
) )
.footer( { .footer({
CreateEmbedFooter::new(format!( CreateEmbedFooter::new(format!(
"Difficulty {}/{}. To show all difficulties in a single embed (old style), react {}", "Difficulty {}/{}. To show all difficulties in a single embed (old style), react {}",
page + 1, page + 1,
@ -464,7 +448,7 @@ mod beatmapset {
SHOW_ALL_EMOTE, SHOW_ALL_EMOTE,
)) ))
}) })
) ),
) )
.await?; .await?;
save_beatmap( save_beatmap(

View file

@ -1,6 +1,19 @@
use std::{collections::HashMap, str::FromStr, sync::Arc}; use std::{collections::HashMap, str::FromStr, sync::Arc};
use super::{db::OsuSavedUsers, ModeArg, OsuClient}; use serenity::{
builder::EditMessage,
framework::standard::{macros::command, Args, CommandResult},
model::channel::Message,
utils::MessageBuilder,
};
use youmubot_prelude::table_format::Align::{Left, Right};
use youmubot_prelude::{
stream::FuturesUnordered,
table_format::{table_formatting, Align},
*,
};
use crate::{ use crate::{
discord::{ discord::{
display::ScoreListStyle, display::ScoreListStyle,
@ -10,17 +23,7 @@ use crate::{
request::UserID, request::UserID,
}; };
use serenity::{ use super::{db::OsuSavedUsers, ModeArg, OsuClient};
builder::EditMessage,
framework::standard::{macros::command, Args, CommandResult},
model::channel::Message,
utils::MessageBuilder,
};
use youmubot_prelude::{
stream::FuturesUnordered,
table_format::{table_formatting, Align},
*,
};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum RankQuery { enum RankQuery {
@ -345,124 +348,63 @@ pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> Com
let scores = scores[start..end].to_vec(); let scores = scores[start..end].to_vec();
let bm = (bm.0.clone(), bm.1); let bm = (bm.0.clone(), bm.1);
Box::pin(async move { Box::pin(async move {
// username width const SCORE_HEADERS: [&'static str; 8] =
let uw = scores ["#", "Score", "Mods", "Rank", "Acc", "Combo", "Miss", "User"];
const PP_HEADERS: [&'static str; 8] =
["#", "PP", "Mods", "Rank", "Acc", "Combo", "Miss", "User"];
const ALIGNS: [Align; 8] = [Right, Right, Right, Right, Right, Right, Right, Left];
let score_arr = scores
.iter() .iter()
.map(|(_, u, _)| u.len()) .enumerate()
.max() .map(|(id, ((official, pp), member, score))| {
.unwrap_or(8) [
.max(8); format!("{}", 1 + id + start),
let accuracies = scores match order {
.iter() OrderBy::PP => {
.map(|(_, _, v)| format!("{:.2}%", v.accuracy(bm.1))) format!("{:.2}{}", pp, if *official { "" } else { "[?]" })
.collect::<Vec<_>>(); }
let aw = accuracies.iter().map(|v| v.len()).max().unwrap().max(3); OrderBy::Score => {
let misses = scores crate::discord::embeds::grouped_number(if has_lazer_score {
.iter() score.normalized_score as u64
.map(|(_, _, v)| format!("{}", v.count_miss)) } else {
.collect::<Vec<_>>(); score.score.unwrap()
let mw = misses.iter().map(|v| v.len()).max().unwrap().max(4); })
let ranks = scores }
.iter() },
.map(|(_, _, v)| v.rank.to_string()) score.mods.to_string(),
.collect::<Vec<_>>(); score.rank.to_string(),
let rw = ranks.iter().map(|v| v.len()).max().unwrap().max(4); format!("{:.2}%", score.accuracy(bm.1)),
let pp_label = match order { format!("{}x", score.max_combo),
OrderBy::PP => "pp", format!("{}", score.count_miss),
OrderBy::Score => "score", member.to_string(),
}; ]
let pp = scores
.iter()
.map(|((official, pp), _, s)| match order {
OrderBy::PP => format!("{:.2}{}", pp, if *official { "" } else { "[?]" }),
OrderBy::Score => crate::discord::embeds::grouped_number(if has_lazer_score { s.normalized_score as u64 } else { s.score.unwrap() }),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let pw = pp.iter().map(|v| v.len()).max().unwrap_or(pp_label.len());
/*mods width*/ let score_table = match order {
let mdw = scores OrderBy::PP => table_formatting(&PP_HEADERS, &ALIGNS, score_arr),
.iter() OrderBy::Score => table_formatting(&SCORE_HEADERS, &ALIGNS, score_arr),
.map(|(_, _, v)| v.mods.str_len()) };
.max() let content = MessageBuilder::new()
.unwrap() .push_line(score_table)
.max(4);
let combos = scores
.iter()
.map(|(_, _, v)| format!("{}x", v.max_combo))
.collect::<Vec<_>>();
let cw = combos
.iter()
.map(|v| v.len())
.max()
.unwrap()
.max(5);
let mut content = MessageBuilder::new();
content
.push_line("```")
.push_line(format!( .push_line(format!(
"rank | {:>pw$} | {:mdw$} | {:rw$} | {:>aw$} | {:>cw$} | {:mw$} | {:uw$}",
pp_label,
"mods",
"rank",
"acc",
"combo",
"miss",
"user",
pw = pw,
mdw = mdw,
rw = rw,
aw = aw,
mw = mw,
uw = uw,
cw = cw,
))
.push_line(format!(
"-------{:-<pw$}---{:-<mdw$}---{:-<rw$}---{:-<aw$}---{:-<cw$}---{:-<mw$}---{:-<uw$}",
"",
"",
"",
"",
"",
"",
"",
pw = pw,
mdw = mdw,
rw = rw,
aw = aw,
mw = mw,
uw = uw,
cw = cw,
));
for (id, (_, member, p)) in scores.iter().enumerate() {
content.push_line_safe(format!(
"{:>4} | {:>pw$} | {} | {:>rw$} | {:>aw$} | {:>cw$} | {:>mw$} | {:uw$}",
format!("#{}", 1 + id + start),
pp[id],
p.mods.to_string_padded(mdw),
ranks[id],
accuracies[id],
combos[id],
misses[id],
member,
pw = pw,
rw = rw,
aw = aw,
cw = cw,
mw = mw,
uw = uw,
));
}
content.push_line("```").push_line(format!(
"Page **{}**/**{}**. Not seeing your scores? Run `osu check` to update.", "Page **{}**/**{}**. Not seeing your scores? Run `osu check` to update.",
page + 1, page + 1,
(total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE, (total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE,
)); ))
.push(
if let crate::models::ApprovalStatus::Ranked(_) = bm.0.approval { if let crate::models::ApprovalStatus::Ranked(_) = bm.0.approval {
""
} else if order == OrderBy::PP { } else if order == OrderBy::PP {
content.push_line("PP was calculated by `oppai-rs`, **not** official values."); "PP was calculated by `oppai-rs`, **not** official values.\n"
} } else {
""
},
)
.build();
m.edit(&ctx, EditMessage::new().content(content.build())).await?; m.edit(&ctx, EditMessage::new().content(content)).await?;
Ok(true) Ok(true)
}) })
}, },

View file

@ -17,9 +17,9 @@ impl Align {
} }
} }
pub fn table_formatting<const N: usize, S: AsRef<str> + std::fmt::Debug, Ts: AsRef<[[S; N]]>>( pub fn table_formatting_unsafe<S: AsRef<str> + std::fmt::Debug, Ss: AsRef<[S]>, Ts: AsRef<[Ss]>>(
headers: &[&'static str; N], headers: &[&str],
padding: &[Align; N], padding: &[Align],
table: Ts, table: Ts,
) -> String { ) -> String {
let table = table.as_ref(); let table = table.as_ref();
@ -68,3 +68,11 @@ pub fn table_formatting<const N: usize, S: AsRef<str> + std::fmt::Debug, Ts: AsR
m.push("```"); m.push("```");
m.build() m.build()
} }
pub fn table_formatting<const N: usize, S: AsRef<str> + std::fmt::Debug, Ts: AsRef<[[S; N]]>>(
headers: &[&'static str; N],
padding: &[Align; N],
table: Ts,
) -> String {
table_formatting_unsafe(headers, padding, table)
}