mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Add ranks command
This commit is contained in:
parent
5d034000a7
commit
14efbcf16f
2 changed files with 95 additions and 37 deletions
|
@ -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>,
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
Loading…
Add table
Reference in a new issue