Add ranks command

This commit is contained in:
Natsu Kagami 2024-12-31 08:02:45 +01:00
parent 5d034000a7
commit 14efbcf16f
Signed by: nki
GPG key ID: 55A032EB38B49ADB
2 changed files with 95 additions and 37 deletions

View file

@ -19,7 +19,8 @@ use serenity::all::User;
"save", "save",
"forcesave", "forcesave",
"beatmap", "beatmap",
"check" "check",
"ranks"
) )
)] )]
pub async fn osu<U: HasOsuEnv>(_ctx: CmdContext<'_, U>) -> Result<()> { pub async fn osu<U: HasOsuEnv>(_ctx: CmdContext<'_, U>) -> Result<()> {
@ -522,6 +523,33 @@ async fn check<U: HasOsuEnv>(
Ok(()) Ok(())
} }
/// Display the rankings of members in the server.
#[poise::command(slash_command, guild_only)]
async fn ranks<U: HasOsuEnv>(
ctx: CmdContext<'_, U>,
#[description = "Sort users by"] sort: Option<server_rank::RankQuery>,
#[description = "Reverse the ordering"] reverse: Option<bool>,
#[description = "The gamemode for the rankings"] mode: Option<Mode>,
) -> Result<()> {
let env = ctx.data().osu_env();
let guild = ctx.partial_guild().await.unwrap();
ctx.defer().await?;
server_rank::do_server_ranks(
ctx.clone().serenity_context(),
env,
&guild,
mode,
sort,
reverse.unwrap_or(false),
|s| async move {
let m = ctx.reply(s).await?;
Ok(m.into_message().await?)
},
)
.await?;
Ok(())
}
fn arg_from_username_or_discord( fn arg_from_username_or_discord(
username: Option<String>, username: Option<String>,
discord_name: Option<User>, discord_name: Option<User>,

View file

@ -2,6 +2,7 @@ use std::{
borrow::Cow, borrow::Cow,
cmp::Ordering, cmp::Ordering,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
future::Future,
str::FromStr, str::FromStr,
sync::Arc, sync::Arc,
}; };
@ -9,7 +10,7 @@ use std::{
use chrono::DateTime; use chrono::DateTime;
use pagination::paginate_with_first_message; use pagination::paginate_with_first_message;
use serenity::{ use serenity::{
all::{GuildId, Member}, all::{GuildId, Member, PartialGuild},
builder::EditMessage, builder::EditMessage,
framework::standard::{macros::command, Args, CommandResult}, framework::standard::{macros::command, Args, CommandResult},
model::channel::Message, model::channel::Message,
@ -32,15 +33,16 @@ use crate::{
use super::{ModeArg, OsuEnv}; use super::{ModeArg, OsuEnv};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, poise::ChoiceParameter)]
enum RankQuery { pub(crate) enum RankQuery {
#[default] #[default]
PP, PP,
#[name = "Total PP"]
TotalPP, TotalPP,
#[name = "Weighted Map Length"]
MapLength, MapLength,
MapAge { #[name = "Map Age"]
newest_first: bool, MapAge,
},
} }
impl RankQuery { impl RankQuery {
@ -49,13 +51,13 @@ impl RankQuery {
RankQuery::PP => "pp", RankQuery::PP => "pp",
RankQuery::TotalPP => "Total pp", RankQuery::TotalPP => "Total pp",
RankQuery::MapLength => "Map length", RankQuery::MapLength => "Map length",
RankQuery::MapAge { newest_first: _ } => "Map age", RankQuery::MapAge => "Map age",
} }
} }
fn pass_pp_limit(&self, mode: Mode, ou: &OsuUser) -> bool { fn pass_pp_limit(&self, mode: Mode, ou: &OsuUser) -> bool {
match self { match self {
RankQuery::PP | RankQuery::TotalPP => true, RankQuery::PP | RankQuery::TotalPP => true,
RankQuery::MapAge { newest_first: _ } | RankQuery::MapLength => { RankQuery::MapAge | RankQuery::MapLength => {
ou.modes.get(&mode).is_some_and(|v| v.pp >= 500.0) ou.modes.get(&mode).is_some_and(|v| v.pp >= 500.0)
} }
} }
@ -81,7 +83,7 @@ impl RankQuery {
format!("{}m{:05.2}s", minutes, seconds).into() format!("{}m{:05.2}s", minutes, seconds).into()
}) })
.unwrap_or_else(|| "-".into()), .unwrap_or_else(|| "-".into()),
RankQuery::MapAge { newest_first: _ } => ou RankQuery::MapAge => ou
.modes .modes
.get(&mode) .get(&mode)
.and_then(|v| DateTime::from_timestamp(v.map_age, 0)) .and_then(|v| DateTime::from_timestamp(v.map_age, 0))
@ -99,10 +101,7 @@ impl FromStr for RankQuery {
"pp" => Ok(RankQuery::PP), "pp" => Ok(RankQuery::PP),
"total" | "total-pp" => Ok(RankQuery::TotalPP), "total" | "total-pp" => Ok(RankQuery::TotalPP),
"map-length" => Ok(RankQuery::MapLength), "map-length" => Ok(RankQuery::MapLength),
"age" | "map-age" => Ok(RankQuery::MapAge { newest_first: true }), "age" | "map-age" => Ok(RankQuery::MapAge),
"old" | "age-old" | "map-age-old" => Ok(RankQuery::MapAge {
newest_first: false,
}),
_ => Err(format!("not a query: {}", s)), _ => Err(format!("not a query: {}", s)),
} }
} }
@ -115,10 +114,38 @@ impl FromStr for RankQuery {
#[only_in(guilds)] #[only_in(guilds)]
pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandResult { pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone(); let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
let mode = args.find::<ModeArg>().map(|v| v.0).unwrap_or(Mode::Std); let mode = args.find::<ModeArg>().map(|v| v.0).ok();
let query = args.find::<RankQuery>().unwrap_or_default(); let query = args.find::<RankQuery>().ok();
let guild = m.guild_id.expect("Guild-only command"); let guild = m
.guild_id
.expect("Guild-only command")
.to_partial_guild(&ctx)
.await?;
let ctxc = ctx.clone();
do_server_ranks(ctx, &env, &guild, mode, query, false, |msg| async {
let m = m.reply(&ctxc, msg).await?;
Ok(m) as Result<_>
})
.await?;
Ok(())
}
pub(crate) async fn do_server_ranks<T>(
ctx: &Context,
env: &OsuEnv,
guild: &PartialGuild,
mode: Option<Mode>,
query: Option<RankQuery>,
reverse: bool,
mk_initial_message: impl FnOnce(String) -> T,
) -> Result<()>
where
T: Future<Output = Result<Message>>,
{
let mode = mode.unwrap_or(Mode::Std);
let query = query.unwrap_or(RankQuery::PP);
let mut users = env let mut users = env
.saved_users .saved_users
.all() .all()
@ -130,7 +157,7 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
let mut users = env let mut users = env
.prelude .prelude
.members .members
.query_members(&ctx, guild) .query_members(&ctx, guild.id)
.await? .await?
.iter() .iter()
.filter_map(|m| users.remove(&m.user.id).map(|ou| (m.clone(), ou))) .filter_map(|m| users.remove(&m.user.id).map(|ou| (m.clone(), ou)))
@ -173,35 +200,41 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
.unwrap() .unwrap()
.reverse() .reverse()
}), }),
RankQuery::MapAge { newest_first } => Box::new(move |(_, a), (_, b)| { RankQuery::MapAge => Box::new(move |(_, a), (_, b)| {
let r = a a.modes
.modes
.get(&mode) .get(&mode)
.map(|v| v.map_age) .map(|v| v.map_age)
.partial_cmp(&b.modes.get(&mode).map(|v| v.map_age)) .partial_cmp(&b.modes.get(&mode).map(|v| v.map_age))
.unwrap(); .unwrap()
if newest_first {
r.reverse()
} else {
r
}
}), }),
}; };
users.sort_unstable_by(sort_fn); users.sort_unstable_by(sort_fn);
if reverse {
users.reverse();
}
if users.is_empty() { if users.is_empty() {
m.reply(&ctx, "No saved users in the current server...") mk_initial_message("No saved users in the current server...".to_owned()).await?;
.await?;
return Ok(()); return Ok(());
} }
let header = format!(
"Rankings for **{}**, ordered by _{}{}_",
guild.name,
query.col_name(),
if reverse { " (reversed)" } else { "" },
);
let msg = mk_initial_message(header.clone()).await?;
const ITEMS_PER_PAGE: usize = 10; const ITEMS_PER_PAGE: usize = 10;
let users = Arc::new(users); let users = Arc::new(users);
let last_update = last_update.unwrap(); let last_update = last_update.unwrap();
let total_len = users.len(); let total_len = users.len();
let total_pages = (total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE; let total_pages = (total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE;
paginate_reply( paginate_with_first_message(
paginate_from_fn(move |page: u8, ctx: &Context, m: &mut Message| { paginate_from_fn(move |page: u8, ctx: &Context, m: &mut Message| {
let header = header.clone();
use Align::*; use Align::*;
let users = users.clone(); let users = users.clone();
Box::pin(async move { Box::pin(async move {
@ -212,7 +245,7 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
} }
let users = &users[start..end]; let users = &users[start..end];
let table = match query { let table = match query {
RankQuery::MapAge { newest_first: _ } | RankQuery::MapLength => { RankQuery::MapAge | RankQuery::MapLength => {
let headers = ["#", query.col_name(), "pp", "Username", "Member"]; let headers = ["#", query.col_name(), "pp", "Username", "Member"];
const ALIGNS: [Align; 5] = [Right, Right, Right, Left, Left]; const ALIGNS: [Align; 5] = [Right, Right, Right, Left, Left];
@ -244,11 +277,7 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
format!("{}", 1 + i + start), format!("{}", 1 + i + start),
RankQuery::PP.extract_row(mode, ou).to_string(), RankQuery::PP.extract_row(mode, ou).to_string(),
RankQuery::MapLength.extract_row(mode, ou).to_string(), RankQuery::MapLength.extract_row(mode, ou).to_string(),
(RankQuery::MapAge { RankQuery::MapAge.extract_row(mode, ou).to_string(),
newest_first: false,
})
.extract_row(mode, ou)
.to_string(),
ou.username.to_string(), ou.username.to_string(),
mem.distinct(), mem.distinct(),
] ]
@ -276,6 +305,7 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
} }
}; };
let content = MessageBuilder::new() let content = MessageBuilder::new()
.push_line(header)
.push_line(table) .push_line(table)
.push_line(format!( .push_line(format!(
"Page **{}**/**{}**. Last updated: {}", "Page **{}**/**{}**. Last updated: {}",
@ -290,7 +320,7 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
}) })
.with_page_count(total_pages), .with_page_count(total_pages),
ctx, ctx,
m, msg,
std::time::Duration::from_secs(60), std::time::Duration::from_secs(60),
) )
.await?; .await?;