mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Use grid throughout the commands
This commit is contained in:
parent
ba8b835cc2
commit
f27939546e
4 changed files with 188 additions and 60 deletions
|
@ -1,7 +1,126 @@
|
|||
pub use beatmapset::display_beatmapset;
|
||||
pub use scores::table::display_scores_table;
|
||||
pub use scores::ScoreListStyle;
|
||||
|
||||
mod scores {
|
||||
use crate::models::{Mode, Score};
|
||||
use serenity::{framework::standard::CommandResult, model::channel::Message};
|
||||
use youmubot_prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// The style for the scores list to be displayed.
|
||||
pub enum ScoreListStyle {
|
||||
Table,
|
||||
Grid,
|
||||
}
|
||||
|
||||
impl Default for ScoreListStyle {
|
||||
fn default() -> Self {
|
||||
Self::Table
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ScoreListStyle {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"--table" => Ok(Self::Table),
|
||||
"--grid" => Ok(Self::Grid),
|
||||
_ => Err(Error::msg("unknown value")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScoreListStyle {
|
||||
pub async fn display_scores<'a>(
|
||||
self,
|
||||
scores: Vec<Score>,
|
||||
mode: Mode,
|
||||
ctx: &'a Context,
|
||||
m: &'a Message,
|
||||
) -> CommandResult {
|
||||
match self {
|
||||
ScoreListStyle::Table => table::display_scores_table(scores, mode, ctx, m).await,
|
||||
ScoreListStyle::Grid => grid::display_scores_grid(scores, mode, ctx, m).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod grid {
|
||||
use crate::discord::{
|
||||
cache::save_beatmap, BeatmapCache, BeatmapMetaCache, BeatmapWithMode,
|
||||
};
|
||||
use crate::models::{Mode, Score};
|
||||
use serenity::{framework::standard::CommandResult, model::channel::Message};
|
||||
use youmubot_prelude::*;
|
||||
|
||||
pub async fn display_scores_grid<'a>(
|
||||
scores: Vec<Score>,
|
||||
mode: Mode,
|
||||
ctx: &'a Context,
|
||||
m: &'a Message,
|
||||
) -> CommandResult {
|
||||
if scores.is_empty() {
|
||||
m.reply(&ctx, "No plays found").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
paginate_reply(
|
||||
Paginate { scores, mode },
|
||||
ctx,
|
||||
m,
|
||||
std::time::Duration::from_secs(60),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Paginate {
|
||||
scores: Vec<Score>,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl pagination::Paginate for Paginate {
|
||||
async fn render(&mut self, page: u8, ctx: &Context, msg: &mut Message) -> Result<bool> {
|
||||
let data = ctx.data.read().await;
|
||||
let client = data.get::<crate::discord::OsuClient>().unwrap();
|
||||
let osu = data.get::<BeatmapMetaCache>().unwrap();
|
||||
let beatmap_cache = data.get::<BeatmapCache>().unwrap();
|
||||
let page = page as usize;
|
||||
let score = &self.scores[page];
|
||||
|
||||
let hourglass = msg.react(ctx, '⌛').await?;
|
||||
let mode = self.mode;
|
||||
let beatmap = osu.get_beatmap(score.beatmap_id, mode).await?;
|
||||
let content = beatmap_cache.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let bm = BeatmapWithMode(beatmap, mode);
|
||||
let user = client
|
||||
.user(crate::request::UserID::ID(score.user_id), |f| f)
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("user not found"))?;
|
||||
|
||||
msg.edit(ctx, |e| {
|
||||
e.embed(|e| {
|
||||
crate::discord::embeds::score_embed(score, &bm, &content, &user)
|
||||
.footer(format!("Page {}/{}", page + 1, self.scores.len()))
|
||||
.build(e)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
save_beatmap(&*ctx.data.read().await, msg.channel_id, &bm).await?;
|
||||
|
||||
// End
|
||||
hourglass.delete(ctx).await?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn len(&self) -> Option<usize> {
|
||||
Some(self.scores.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod table {
|
||||
use crate::discord::{Beatmap, BeatmapCache, BeatmapInfo, BeatmapMetaCache};
|
||||
use crate::models::{Mode, Score};
|
||||
|
|
|
@ -169,6 +169,7 @@ pub(crate) struct ScoreEmbedBuilder<'a> {
|
|||
u: &'a User,
|
||||
top_record: Option<u8>,
|
||||
world_record: Option<u16>,
|
||||
footer: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> ScoreEmbedBuilder<'a> {
|
||||
|
@ -180,6 +181,10 @@ impl<'a> ScoreEmbedBuilder<'a> {
|
|||
self.world_record = Some(rank);
|
||||
self
|
||||
}
|
||||
pub fn footer(&mut self, footer: impl Into<String>) -> &mut Self {
|
||||
self.footer = Some(footer.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn score_embed<'a>(
|
||||
|
@ -195,12 +200,13 @@ pub(crate) fn score_embed<'a>(
|
|||
u,
|
||||
top_record: None,
|
||||
world_record: None,
|
||||
footer: None,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ScoreEmbedBuilder<'a> {
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
pub fn build<'b>(&self, m: &'b mut CreateEmbed) -> &'b mut CreateEmbed {
|
||||
pub fn build<'b>(&mut self, m: &'b mut CreateEmbed) -> &'b mut CreateEmbed {
|
||||
let mode = self.bm.mode();
|
||||
let b = &self.bm.0;
|
||||
let s = self.s;
|
||||
|
@ -358,8 +364,12 @@ impl<'a> ScoreEmbedBuilder<'a> {
|
|||
)
|
||||
.field("Map stats", diff.format_info(mode, s.mods, b), false)
|
||||
.timestamp(&s.date);
|
||||
let mut footer = self.footer.take().unwrap_or_else(String::new);
|
||||
if mode.to_oppai_mode().is_none() && s.mods != Mods::NOMOD {
|
||||
m.footer(|f| f.text("Star difficulty does not reflect game mods."));
|
||||
footer += " Star difficulty does not reflect game mods.";
|
||||
}
|
||||
if !footer.is_empty() {
|
||||
m.footer(|f| f.text(footer));
|
||||
}
|
||||
m
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
discord::beatmap_cache::BeatmapMetaCache,
|
||||
discord::display::display_scores_table as list_scores,
|
||||
discord::display::ScoreListStyle,
|
||||
discord::oppai_cache::{BeatmapCache, BeatmapInfo},
|
||||
models::{Beatmap, Mode, Mods, User},
|
||||
request::UserID,
|
||||
|
@ -32,7 +32,7 @@ use db::OsuUser;
|
|||
use db::{OsuLastBeatmap, OsuSavedUsers, OsuUserBests};
|
||||
use embeds::{beatmap_embed, score_embed, user_embed};
|
||||
pub use hook::hook;
|
||||
use server_rank::{LEADERBOARD_COMMAND, SERVER_RANK_COMMAND, UPDATE_LEADERBOARD_COMMAND};
|
||||
use server_rank::{SERVER_RANK_COMMAND, UPDATE_LEADERBOARD_COMMAND};
|
||||
|
||||
/// The osu! client.
|
||||
pub(crate) struct OsuClient;
|
||||
|
@ -108,7 +108,6 @@ pub fn setup(
|
|||
check,
|
||||
top,
|
||||
server_rank,
|
||||
leaderboard,
|
||||
update_leaderboard
|
||||
)]
|
||||
#[default_command(std)]
|
||||
|
@ -315,13 +314,15 @@ impl FromStr for Nth {
|
|||
}
|
||||
|
||||
#[command]
|
||||
#[aliases("rs", "rc")]
|
||||
#[description = "Gets an user's recent play"]
|
||||
#[usage = "#[the nth recent play = --all] / [mode (std, taiko, mania, catch) = std] / [username / user id = your saved id]"]
|
||||
#[usage = "#[the nth recent play = --all] / [style (table or grid) = --table] / [mode (std, taiko, mania, catch) = std] / [username / user id = your saved id]"]
|
||||
#[example = "#1 / taiko / natsukagami"]
|
||||
#[max_args(3)]
|
||||
#[max_args(4)]
|
||||
pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*data, msg).await?;
|
||||
|
||||
|
@ -361,7 +362,7 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
let plays = osu
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||
.await?;
|
||||
list_scores(plays, mode, ctx, msg).await?;
|
||||
style.display_scores(plays, mode, ctx, msg).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -433,9 +434,9 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
|
||||
#[command]
|
||||
#[aliases("c", "chk")]
|
||||
#[usage = "[username or tag = yourself]"]
|
||||
#[usage = "[style (table or grid) = --table] / [username or tag = yourself]"]
|
||||
#[description = "Check your own or someone else's best record on the last beatmap. Also stores the result if possible."]
|
||||
#[max_args(1)]
|
||||
#[max_args(2)]
|
||||
pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let bm = cache::get_beatmap(&*data, msg.channel_id).await?;
|
||||
|
@ -448,6 +449,7 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
Some(bm) => {
|
||||
let b = &bm.0;
|
||||
let m = bm.1;
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||
let username_arg = args.single::<UsernameArg>().ok();
|
||||
let user_id = match username_arg.as_ref() {
|
||||
Some(UsernameArg::Tagged(v)) => Some(*v),
|
||||
|
@ -457,9 +459,6 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
let user = to_user_id_query(username_arg, &*data, msg).await?;
|
||||
|
||||
let osu = data.get::<OsuClient>().unwrap();
|
||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
||||
|
||||
let content = oppai.get_beatmap(b.beatmap_id).await?;
|
||||
|
||||
let user = osu
|
||||
.user(user, |f| f)
|
||||
|
@ -473,22 +472,16 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
msg.reply(&ctx, "No scores found").await?;
|
||||
}
|
||||
|
||||
for score in scores.iter() {
|
||||
msg.channel_id
|
||||
.send_message(&ctx, |c| {
|
||||
c.embed(|m| score_embed(&score, &bm, &content, &user).build(m))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(user_id) = user_id {
|
||||
// Save to database
|
||||
data.get::<OsuUserBests>()
|
||||
.unwrap()
|
||||
.save(user_id, m, scores)
|
||||
.save(user_id, m, scores.clone())
|
||||
.await
|
||||
.pls_ok();
|
||||
}
|
||||
|
||||
style.display_scores(scores, m, ctx, msg).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,12 +490,13 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
|
||||
#[command]
|
||||
#[description = "Get the n-th top record of an user."]
|
||||
#[usage = "[mode (std, taiko, catch, mania)] = std / #[n-th = --all] / [username or user_id = your saved user id]"]
|
||||
#[example = "taiko / #2 / natsukagami"]
|
||||
#[max_args(3)]
|
||||
#[usage = "#[n-th = --all] / [style (table or grid) = --table] / [mode (std, taiko, catch, mania)] = std / [username or user_id = your saved user id]"]
|
||||
#[example = "#2 / taiko / natsukagami"]
|
||||
#[max_args(4)]
|
||||
pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||
let mode = args
|
||||
.single::<ModeArg>()
|
||||
.map(|ModeArg(t)| t)
|
||||
|
@ -555,7 +549,7 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
let plays = osu
|
||||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||
.await?;
|
||||
list_scores(plays, mode, ctx, msg).await?;
|
||||
style.display_scores(plays, mode, ctx, msg).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -5,6 +5,7 @@ use super::{
|
|||
};
|
||||
use crate::{
|
||||
discord::{
|
||||
display::ScoreListStyle,
|
||||
oppai_cache::{BeatmapCache, OppaiAccuracy},
|
||||
BeatmapWithMode,
|
||||
},
|
||||
|
@ -151,23 +152,33 @@ enum OrderBy {
|
|||
Score,
|
||||
}
|
||||
|
||||
impl From<&str> for OrderBy {
|
||||
fn from(s: &str) -> Self {
|
||||
if s == "--score" {
|
||||
impl Default for OrderBy {
|
||||
fn default() -> Self {
|
||||
Self::Score
|
||||
} else {
|
||||
Self::PP
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for OrderBy {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"--score" => Ok(OrderBy::Score),
|
||||
"--pp" => Ok(OrderBy::PP),
|
||||
_ => Err(Error::msg("unknown value")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command("updatelb")]
|
||||
#[description = "Update the leaderboard on the last seen beatmap"]
|
||||
#[usage = "[--score to sort by score, default to sort by pp]"]
|
||||
#[max_args(1)]
|
||||
#[command("leaderboard")]
|
||||
#[aliases("lb", "bmranks", "br", "cc", "updatelb")]
|
||||
#[usage = "[--score to sort by score, default to sort by pp] / [--table to show a table, --grid to show score by score]"]
|
||||
#[description = "See the server's ranks on the last seen beatmap"]
|
||||
#[max_args(2)]
|
||||
#[only_in(guilds)]
|
||||
pub async fn update_leaderboard(ctx: &Context, m: &Message, args: Args) -> CommandResult {
|
||||
let sort_order = OrderBy::from(args.rest());
|
||||
pub async fn update_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||
let sort_order = args.single::<OrderBy>().unwrap_or_default();
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||
|
||||
let guild = m.guild_id.unwrap();
|
||||
let data = ctx.data.read().await;
|
||||
|
@ -246,27 +257,7 @@ pub async fn update_leaderboard(ctx: &Context, m: &Message, args: Args) -> Comma
|
|||
.await
|
||||
.ok();
|
||||
drop(update_lock);
|
||||
show_leaderboard(ctx, m, bm, sort_order).await
|
||||
}
|
||||
|
||||
#[command("leaderboard")]
|
||||
#[aliases("lb", "bmranks", "br", "cc")]
|
||||
#[usage = "[--score to sort by score, default to sort by pp]"]
|
||||
#[description = "See the server's ranks on the last seen beatmap"]
|
||||
#[max_args(1)]
|
||||
#[only_in(guilds)]
|
||||
pub async fn leaderboard(ctx: &Context, m: &Message, args: Args) -> CommandResult {
|
||||
let sort_order = OrderBy::from(args.rest());
|
||||
|
||||
let data = ctx.data.read().await;
|
||||
let bm = match get_beatmap(&*data, m.channel_id).await? {
|
||||
Some(bm) => bm,
|
||||
None => {
|
||||
m.reply(&ctx, "No beatmap queried on this channel.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
show_leaderboard(ctx, m, bm, sort_order).await
|
||||
show_leaderboard(ctx, m, bm, sort_order, style).await
|
||||
}
|
||||
|
||||
async fn show_leaderboard(
|
||||
|
@ -274,6 +265,7 @@ async fn show_leaderboard(
|
|||
m: &Message,
|
||||
bm: BeatmapWithMode,
|
||||
order: OrderBy,
|
||||
style: ScoreListStyle,
|
||||
) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
|
||||
|
@ -381,6 +373,19 @@ async fn show_leaderboard(
|
|||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let ScoreListStyle::Grid = style {
|
||||
style
|
||||
.display_scores(
|
||||
scores.into_iter().map(|(_, _, a)| a).collect(),
|
||||
mode,
|
||||
ctx,
|
||||
m,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
paginate_reply_fn(
|
||||
move |page: u8, ctx: &Context, m: &mut Message| {
|
||||
const ITEMS_PER_PAGE: usize = 5;
|
||||
|
|
Loading…
Add table
Reference in a new issue