mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Reuse table_formatting
logic for almost everything (#39)
This commit is contained in:
parent
54426ed477
commit
13683aa229
5 changed files with 273 additions and 388 deletions
|
@ -1,3 +1,5 @@
|
|||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
|
||||
use codeforces::Contest;
|
||||
use serenity::{
|
||||
builder::{CreateMessage, EditMessage},
|
||||
|
@ -8,8 +10,15 @@ use serenity::{
|
|||
model::{channel::Message, guild::Member},
|
||||
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 db;
|
||||
|
@ -26,10 +35,6 @@ impl TypeMapKey for CFClient {
|
|||
type Value = Arc<codeforces::Client>;
|
||||
}
|
||||
|
||||
use db::{CfSavedUsers, CfUser};
|
||||
|
||||
pub use hook::InfoHook;
|
||||
|
||||
/// Sets up the CF databases.
|
||||
pub async fn setup(path: &std::path::Path, data: &mut TypeMap, announcers: &mut AnnouncerHandler) {
|
||||
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(
|
||||
move |page, ctx, msg| {
|
||||
use Align::*;
|
||||
let ranks = ranks.clone();
|
||||
Box::pin(async move {
|
||||
let page = page as usize;
|
||||
|
@ -184,56 +190,37 @@ pub async fn ranks(ctx: &Context, m: &Message) -> CommandResult {
|
|||
}
|
||||
let ranks = &ranks[start..end];
|
||||
|
||||
let handle_width = ranks.iter().map(|(_, cfu)| cfu.handle.len()).max().unwrap();
|
||||
let username_width = ranks
|
||||
const HEADERS: [&'static str; 4] = ["Rank", "Rating", "Handle", "Username"];
|
||||
const ALIGNS: [Align; 4] = [Right, Right, Left, Left];
|
||||
|
||||
let ranks_arr = ranks
|
||||
.iter()
|
||||
.map(|(mem, _)| mem.distinct().len())
|
||||
.max()
|
||||
.unwrap();
|
||||
.enumerate()
|
||||
.map(|(i, (mem, cfu))| {
|
||||
[
|
||||
format!("#{}", 1 + i + start),
|
||||
cfu.rating
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| "----".to_owned()),
|
||||
cfu.handle.clone(),
|
||||
mem.distinct(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut m = MessageBuilder::new();
|
||||
m.push_line("```");
|
||||
let table = table_formatting(&HEADERS, &ALIGNS, ranks_arr);
|
||||
|
||||
// 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
|
||||
));
|
||||
let content = MessageBuilder::new()
|
||||
.push_line(table)
|
||||
.push_line(format!(
|
||||
"Page **{}/{}**. Last updated **{}**",
|
||||
page + 1,
|
||||
total_pages,
|
||||
last_updated.to_rfc2822()
|
||||
))
|
||||
.build();
|
||||
|
||||
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
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| "----".to_owned()),
|
||||
cfu.handle,
|
||||
mem.distinct(),
|
||||
hw = handle_width,
|
||||
uw = username_width
|
||||
));
|
||||
}
|
||||
|
||||
m.push_line("```");
|
||||
m.push(format!(
|
||||
"Page **{}/{}**. Last updated **{}**",
|
||||
page + 1,
|
||||
total_pages,
|
||||
last_updated.to_rfc2822()
|
||||
));
|
||||
|
||||
msg.edit(ctx, EditMessage::new().content(m.build())).await?;
|
||||
msg.edit(ctx, EditMessage::new().content(content)).await?;
|
||||
Ok(true)
|
||||
})
|
||||
},
|
||||
|
@ -340,79 +327,66 @@ pub(crate) async fn contest_rank_table(
|
|||
return Ok(false);
|
||||
}
|
||||
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 mut header = MessageBuilder::new();
|
||||
// Header
|
||||
header.push(format!(
|
||||
" Rank | {:hw$} | Total | {:hackw$}",
|
||||
"Handle",
|
||||
"Hacks",
|
||||
hw = hw,
|
||||
hackw = hackw
|
||||
));
|
||||
for p in &problems {
|
||||
header.push(format!(" | {:4}", p.index));
|
||||
}
|
||||
let header = header.build();
|
||||
table
|
||||
.push_line(&header)
|
||||
.push_line(format!("{:-<w$}", "", w = header.len()));
|
||||
let score_headers: Vec<&str> = [
|
||||
vec!["Rank", "Handle", "User", "Total", "Hacks"],
|
||||
problems
|
||||
.iter()
|
||||
.map(|p| p.index.as_str())
|
||||
.collect::<Vec<&str>>(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
// Body
|
||||
for (mem, handle, row) in ranks {
|
||||
table.push(format!(
|
||||
"{:>5} | {:<hw$} | {:>5.0} | {:<hackw$}",
|
||||
row.rank,
|
||||
format!("{} ({})", handle, mem.distinct()),
|
||||
row.points,
|
||||
format!(
|
||||
"{}/{}",
|
||||
row.successful_hack_count, row.unsuccessful_hack_count
|
||||
),
|
||||
hw = hw,
|
||||
hackw = hackw
|
||||
));
|
||||
for p in &row.problem_results {
|
||||
table.push(" | ");
|
||||
if p.points > 0.0 {
|
||||
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}", ""));
|
||||
let score_aligns: Vec<Align> = [
|
||||
vec![Right, Left, Left, Right, Right],
|
||||
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!("{}", "----"));
|
||||
}
|
||||
}
|
||||
}
|
||||
table.push_line("");
|
||||
}
|
||||
|
||||
let mut m = MessageBuilder::new();
|
||||
m.push_bold_safe(&contest.name)
|
||||
[
|
||||
vec![
|
||||
format!("{}", row.rank),
|
||||
handle.clone(),
|
||||
mem.distinct(),
|
||||
format!("{}", row.points),
|
||||
format!(
|
||||
"{}/{}",
|
||||
row.successful_hack_count, row.unsuccessful_hack_count
|
||||
),
|
||||
],
|
||||
p_results,
|
||||
]
|
||||
.concat()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let score_table = table_formatting_unsafe(&score_headers, &score_aligns, score_arr);
|
||||
|
||||
let content = MessageBuilder::new()
|
||||
.push_bold_safe(&contest.name)
|
||||
.push(" ")
|
||||
.push_line(contest.url())
|
||||
.push_codeblock(table.build(), None)
|
||||
.push_line(format!("Page **{}/{}**", page + 1, total_pages));
|
||||
msg.edit(ctx, EditMessage::new().content(m.build())).await?;
|
||||
.push_line(score_table)
|
||||
.push_line(format!("Page **{}/{}**", page + 1, total_pages))
|
||||
.build();
|
||||
|
||||
msg.edit(ctx, EditMessage::new().content(content)).await?;
|
||||
Ok(true)
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::db::Roles as DB;
|
||||
use serenity::{
|
||||
builder::EditMessage,
|
||||
framework::standard::{macros::command, Args, CommandResult},
|
||||
|
@ -9,9 +8,13 @@ use serenity::{
|
|||
},
|
||||
utils::MessageBuilder,
|
||||
};
|
||||
use youmubot_prelude::*;
|
||||
|
||||
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")]
|
||||
#[description = "List all available roles in the server."]
|
||||
|
@ -50,59 +53,31 @@ async fn list(ctx: &Context, m: &Message, _: Args) -> CommandResult {
|
|||
if end <= start {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
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()
|
||||
.map(|(r, _)| r.name.len())
|
||||
.max()
|
||||
.unwrap()
|
||||
.max(6);
|
||||
let idw = roles[0].0.id.to_string().len();
|
||||
let dw = roles
|
||||
.iter()
|
||||
.map(|v| v.1.len())
|
||||
.max()
|
||||
.unwrap()
|
||||
.max(" Description ".len());
|
||||
let mut m = MessageBuilder::new();
|
||||
m.push_line("```");
|
||||
.map(|(role, description)| {
|
||||
[
|
||||
role.name.clone(),
|
||||
format!("{}", role.id),
|
||||
description.clone(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Table header
|
||||
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,
|
||||
));
|
||||
let roles_table = table_formatting(&ROLE_HEADERS, &ROLE_ALIGNS, roles_arr);
|
||||
|
||||
for (role, description) in roles.iter() {
|
||||
m.push_line(format!(
|
||||
"{:nw$} | {:idw$} | {:dw$}",
|
||||
role.name,
|
||||
role.id,
|
||||
description,
|
||||
nw = nw,
|
||||
idw = idw,
|
||||
dw = dw,
|
||||
));
|
||||
}
|
||||
m.push_line("```");
|
||||
m.push(format!("Page **{}/{}**", page + 1, pages));
|
||||
let content = MessageBuilder::new()
|
||||
.push_line(roles_table)
|
||||
.push_line(format!("Page **{}/{}**", page + 1, pages))
|
||||
.build();
|
||||
|
||||
msg.edit(ctx, EditMessage::new().content(m.to_string()))
|
||||
.await?;
|
||||
msg.edit(ctx, EditMessage::new().content(content)).await?;
|
||||
Ok(true)
|
||||
})
|
||||
},
|
||||
|
@ -415,7 +390,6 @@ async fn rmrolemessage(ctx: &Context, m: &Message, _args: Args) -> CommandResult
|
|||
}
|
||||
|
||||
mod reaction_watcher {
|
||||
use crate::db::{Role, RoleMessage, Roles};
|
||||
use dashmap::DashMap;
|
||||
use flume::{Receiver, Sender};
|
||||
use serenity::{
|
||||
|
@ -427,8 +401,11 @@ mod reaction_watcher {
|
|||
id::{ChannelId, GuildId, MessageId},
|
||||
},
|
||||
};
|
||||
|
||||
use youmubot_prelude::*;
|
||||
|
||||
use crate::db::{Role, RoleMessage, Roles};
|
||||
|
||||
/// A set of watchers.
|
||||
#[derive(Debug)]
|
||||
pub struct Watchers {
|
||||
|
|
|
@ -2,10 +2,12 @@ pub use beatmapset::display_beatmapset;
|
|||
pub use scores::ScoreListStyle;
|
||||
|
||||
mod scores {
|
||||
use crate::models::{Mode, Score};
|
||||
use serenity::{framework::standard::CommandResult, model::channel::Message};
|
||||
|
||||
use youmubot_prelude::*;
|
||||
|
||||
use crate::models::{Mode, Score};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// The style for the scores list to be displayed.
|
||||
pub enum ScoreListStyle {
|
||||
|
@ -47,13 +49,15 @@ mod scores {
|
|||
}
|
||||
|
||||
pub mod grid {
|
||||
use serenity::builder::EditMessage;
|
||||
use serenity::{framework::standard::CommandResult, model::channel::Message};
|
||||
|
||||
use youmubot_prelude::*;
|
||||
|
||||
use crate::discord::{
|
||||
cache::save_beatmap, BeatmapCache, BeatmapMetaCache, BeatmapWithMode,
|
||||
};
|
||||
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>(
|
||||
scores: Vec<Score>,
|
||||
|
@ -126,12 +130,16 @@ mod scores {
|
|||
pub mod table {
|
||||
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::{Beatmap, BeatmapCache, BeatmapInfo, BeatmapMetaCache};
|
||||
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>(
|
||||
scores: Vec<Score>,
|
||||
|
@ -196,7 +204,8 @@ mod scores {
|
|||
.collect::<stream::FuturesOrdered<_>>()
|
||||
.map(|v| v.ok())
|
||||
.collect::<Vec<_>>();
|
||||
let pp = plays
|
||||
|
||||
let pps = plays
|
||||
.iter()
|
||||
.map(|p| async move {
|
||||
match p.pp.map(|pp| format!("{:.2}", pp)) {
|
||||
|
@ -226,7 +235,8 @@ mod scores {
|
|||
.collect::<stream::FuturesOrdered<_>>()
|
||||
.map(|v| v.unwrap_or_else(|_| "-".to_owned()))
|
||||
.collect::<Vec<String>>();
|
||||
let (beatmaps, pp) = future::join(beatmaps, pp).await;
|
||||
|
||||
let (beatmaps, pps) = future::join(beatmaps, pps).await;
|
||||
|
||||
let ranks = plays
|
||||
.iter()
|
||||
|
@ -274,64 +284,36 @@ mod scores {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pw = pp.iter().map(|v| v.len()).max().unwrap_or(2);
|
||||
/*mods width*/
|
||||
let mw = plays.iter().map(|v| v.mods.str_len()).max().unwrap().max(4);
|
||||
/*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);
|
||||
const SCORE_HEADERS: [&'static str; 6] =
|
||||
["#", "PP", "Acc", "Ranks", "Mods", "Beatmap"];
|
||||
const SCORE_ALIGNS: [Align; 6] = [Right, Right, Right, Right, Right, Left];
|
||||
|
||||
let mut m = serenity::utils::MessageBuilder::new();
|
||||
// Table header
|
||||
m.push_line(format!(
|
||||
" # | {:pw$} | accuracy | {:rw$} | {:mw$} | {:bw$}",
|
||||
"pp",
|
||||
"ranks",
|
||||
"mods",
|
||||
"beatmap",
|
||||
rw = rw,
|
||||
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)),
|
||||
ranks[id],
|
||||
play.mods.to_string_padded(mw),
|
||||
beatmap,
|
||||
rw = rw,
|
||||
pw = pw,
|
||||
bw = bw
|
||||
));
|
||||
}
|
||||
// End
|
||||
let table = m.build().replace("```", "\\`\\`\\`");
|
||||
let mut m = serenity::utils::MessageBuilder::new();
|
||||
m.push_codeblock(table, None).push_line(format!(
|
||||
"Page **{}/{}**",
|
||||
page + 1,
|
||||
self.total_pages()
|
||||
));
|
||||
m.push_line("[?] means pp was predicted by oppai-rs.");
|
||||
msg.edit(ctx, EditMessage::new().content(m.to_string()))
|
||||
.await?;
|
||||
let score_arr = plays
|
||||
.iter()
|
||||
.zip(beatmaps.iter())
|
||||
.zip(ranks.iter().zip(pps.iter()))
|
||||
.enumerate()
|
||||
.map(|(id, ((play, beatmap), (rank, pp)))| {
|
||||
[
|
||||
format!("{}", id + start + 1),
|
||||
format!("{}", pp),
|
||||
format!("{:.2}%", play.accuracy(self.mode)),
|
||||
format!("{}", rank),
|
||||
play.mods.to_string(),
|
||||
beatmap.clone(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let score_table = table_formatting(&SCORE_HEADERS, &SCORE_ALIGNS, score_arr);
|
||||
|
||||
let content = serenity::utils::MessageBuilder::new()
|
||||
.push_line(score_table)
|
||||
.push_line(format!("Page **{}/{}**", page + 1, self.total_pages()))
|
||||
.push_line("[?] means pp was predicted by oppai-rs.")
|
||||
.build();
|
||||
|
||||
msg.edit(ctx, EditMessage::new().content(content)).await?;
|
||||
hourglass.delete(ctx).await?;
|
||||
Ok(true)
|
||||
}
|
||||
|
@ -344,20 +326,22 @@ mod scores {
|
|||
}
|
||||
|
||||
mod beatmapset {
|
||||
use crate::{
|
||||
discord::{
|
||||
cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapCache, BeatmapWithMode,
|
||||
},
|
||||
models::{Beatmap, Mode, Mods},
|
||||
};
|
||||
use serenity::{
|
||||
all::Reaction,
|
||||
builder::{CreateEmbedFooter, EditMessage},
|
||||
model::channel::Message,
|
||||
model::channel::ReactionType,
|
||||
};
|
||||
|
||||
use youmubot_prelude::*;
|
||||
|
||||
use crate::{
|
||||
discord::{
|
||||
cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapCache, BeatmapWithMode,
|
||||
},
|
||||
models::{Beatmap, Mode, Mods},
|
||||
};
|
||||
|
||||
const SHOW_ALL_EMOTE: &str = "🗒️";
|
||||
|
||||
pub async fn display_beatmapset(
|
||||
|
@ -449,24 +433,24 @@ mod beatmapset {
|
|||
}
|
||||
};
|
||||
m.edit(ctx,
|
||||
EditMessage::new().content(self.message.as_str()).embed(
|
||||
crate::discord::embeds::beatmap_embed(
|
||||
map,
|
||||
self.mode.unwrap_or(map.mode),
|
||||
self.mods,
|
||||
info,
|
||||
)
|
||||
.footer( {
|
||||
CreateEmbedFooter::new(format!(
|
||||
"Difficulty {}/{}. To show all difficulties in a single embed (old style), react {}",
|
||||
page + 1,
|
||||
self.maps.len(),
|
||||
SHOW_ALL_EMOTE,
|
||||
))
|
||||
})
|
||||
)
|
||||
EditMessage::new().content(self.message.as_str()).embed(
|
||||
crate::discord::embeds::beatmap_embed(
|
||||
map,
|
||||
self.mode.unwrap_or(map.mode),
|
||||
self.mods,
|
||||
info,
|
||||
)
|
||||
.footer({
|
||||
CreateEmbedFooter::new(format!(
|
||||
"Difficulty {}/{}. To show all difficulties in a single embed (old style), react {}",
|
||||
page + 1,
|
||||
self.maps.len(),
|
||||
SHOW_ALL_EMOTE,
|
||||
))
|
||||
})
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
.await?;
|
||||
save_beatmap(
|
||||
&*ctx.data.read().await,
|
||||
m.channel_id,
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
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::{
|
||||
discord::{
|
||||
display::ScoreListStyle,
|
||||
|
@ -10,17 +23,7 @@ use crate::{
|
|||
request::UserID,
|
||||
};
|
||||
|
||||
use serenity::{
|
||||
builder::EditMessage,
|
||||
framework::standard::{macros::command, Args, CommandResult},
|
||||
model::channel::Message,
|
||||
utils::MessageBuilder,
|
||||
};
|
||||
use youmubot_prelude::{
|
||||
stream::FuturesUnordered,
|
||||
table_format::{table_formatting, Align},
|
||||
*,
|
||||
};
|
||||
use super::{db::OsuSavedUsers, ModeArg, OsuClient};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
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 bm = (bm.0.clone(), bm.1);
|
||||
Box::pin(async move {
|
||||
// username width
|
||||
let uw = scores
|
||||
const SCORE_HEADERS: [&'static str; 8] =
|
||||
["#", "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()
|
||||
.map(|(_, u, _)| u.len())
|
||||
.max()
|
||||
.unwrap_or(8)
|
||||
.max(8);
|
||||
let accuracies = scores
|
||||
.iter()
|
||||
.map(|(_, _, v)| format!("{:.2}%", v.accuracy(bm.1)))
|
||||
.collect::<Vec<_>>();
|
||||
let aw = accuracies.iter().map(|v| v.len()).max().unwrap().max(3);
|
||||
let misses = scores
|
||||
.iter()
|
||||
.map(|(_, _, v)| format!("{}", v.count_miss))
|
||||
.collect::<Vec<_>>();
|
||||
let mw = misses.iter().map(|v| v.len()).max().unwrap().max(4);
|
||||
let ranks = scores
|
||||
.iter()
|
||||
.map(|(_, _, v)| v.rank.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let rw = ranks.iter().map(|v| v.len()).max().unwrap().max(4);
|
||||
let pp_label = match order {
|
||||
OrderBy::PP => "pp",
|
||||
OrderBy::Score => "score",
|
||||
};
|
||||
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() }),
|
||||
.enumerate()
|
||||
.map(|(id, ((official, pp), member, score))| {
|
||||
[
|
||||
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.unwrap()
|
||||
})
|
||||
}
|
||||
},
|
||||
score.mods.to_string(),
|
||||
score.rank.to_string(),
|
||||
format!("{:.2}%", score.accuracy(bm.1)),
|
||||
format!("{}x", score.max_combo),
|
||||
format!("{}", score.count_miss),
|
||||
member.to_string(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let pw = pp.iter().map(|v| v.len()).max().unwrap_or(pp_label.len());
|
||||
/*mods width*/
|
||||
let mdw = scores
|
||||
.iter()
|
||||
.map(|(_, _, v)| v.mods.str_len())
|
||||
.max()
|
||||
.unwrap()
|
||||
.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!(
|
||||
"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 + 1,
|
||||
(total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE,
|
||||
));
|
||||
if let crate::models::ApprovalStatus::Ranked(_) = bm.0.approval {
|
||||
} else if order == OrderBy::PP {
|
||||
content.push_line("PP was calculated by `oppai-rs`, **not** official values.");
|
||||
}
|
||||
|
||||
m.edit(&ctx, EditMessage::new().content(content.build())).await?;
|
||||
let score_table = match order {
|
||||
OrderBy::PP => table_formatting(&PP_HEADERS, &ALIGNS, score_arr),
|
||||
OrderBy::Score => table_formatting(&SCORE_HEADERS, &ALIGNS, score_arr),
|
||||
};
|
||||
let content = MessageBuilder::new()
|
||||
.push_line(score_table)
|
||||
.push_line(format!(
|
||||
"Page **{}**/**{}**. Not seeing your scores? Run `osu check` to update.",
|
||||
page + 1,
|
||||
(total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE,
|
||||
))
|
||||
.push(
|
||||
if let crate::models::ApprovalStatus::Ranked(_) = bm.0.approval {
|
||||
""
|
||||
} else if order == OrderBy::PP {
|
||||
"PP was calculated by `oppai-rs`, **not** official values.\n"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
m.edit(&ctx, EditMessage::new().content(content)).await?;
|
||||
Ok(true)
|
||||
})
|
||||
},
|
||||
|
|
|
@ -17,9 +17,9 @@ impl Align {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn table_formatting<const N: usize, S: AsRef<str> + std::fmt::Debug, Ts: AsRef<[[S; N]]>>(
|
||||
headers: &[&'static str; N],
|
||||
padding: &[Align; N],
|
||||
pub fn table_formatting_unsafe<S: AsRef<str> + std::fmt::Debug, Ss: AsRef<[S]>, Ts: AsRef<[Ss]>>(
|
||||
headers: &[&str],
|
||||
padding: &[Align],
|
||||
table: Ts,
|
||||
) -> String {
|
||||
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.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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue