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 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)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue