diff --git a/youmubot-osu/src/models/mod.rs b/youmubot-osu/src/models/mod.rs index adcdc53..8c746ec 100644 --- a/youmubot-osu/src/models/mod.rs +++ b/youmubot-osu/src/models/mod.rs @@ -30,7 +30,7 @@ impl fmt::Display for ApprovalStatus { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Difficulty { pub stars: f64, pub aim: Option, @@ -118,7 +118,7 @@ impl fmt::Display for Mode { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Beatmap { // Beatmapset info pub approval: ApprovalStatus, @@ -154,6 +154,13 @@ pub struct Beatmap { const NEW_MODE_NAMES: [&'static str; 4] = ["osu", "taiko", "fruits", "mania"]; impl Beatmap { + pub fn beatmapset_link(&self) -> String { + format!( + "https://osu.ppy.sh/beatmapsets/{}#{}", + self.beatmapset_id, NEW_MODE_NAMES[self.mode as usize] + ) + } + /// Gets a link pointing to the beatmap, in the new format. pub fn link(&self) -> String { format!( @@ -171,7 +178,7 @@ impl Beatmap { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct UserEvent { pub display_html: String, pub beatmap_id: u64, @@ -180,7 +187,7 @@ pub struct UserEvent { pub epic_factor: u8, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct User { pub id: u64, pub username: String, @@ -255,7 +262,7 @@ impl fmt::Display for Rank { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Score { pub id: Option, // No id if you fail pub user_id: u64, diff --git a/youmubot/src/commands/osu/cache.rs b/youmubot/src/commands/osu/cache.rs index 614bcce..e44bb00 100644 --- a/youmubot/src/commands/osu/cache.rs +++ b/youmubot/src/commands/osu/cache.rs @@ -19,7 +19,7 @@ pub(crate) fn save_beatmap( .into(); let mut db = db.borrow_mut()?; - db.insert(channel_id, (bm.0.beatmap_id, bm.mode())); + db.insert(channel_id, (bm.0.clone(), bm.mode())); Ok(()) } @@ -28,9 +28,12 @@ pub(crate) fn save_beatmap( pub(crate) fn get_beatmap( data: &ShareMap, channel_id: ChannelId, -) -> Result, Error> { +) -> Result, Error> { let db = data.get::().expect("DB is implemented"); let db = db.borrow_data()?; - Ok(db.get(&channel_id).cloned()) + Ok(db + .get(&channel_id) + .cloned() + .map(|(a, b)| BeatmapWithMode(a, b))) } diff --git a/youmubot/src/commands/osu/embeds.rs b/youmubot/src/commands/osu/embeds.rs new file mode 100644 index 0000000..84995e7 --- /dev/null +++ b/youmubot/src/commands/osu/embeds.rs @@ -0,0 +1,348 @@ +use super::BeatmapWithMode; +use crate::commands::args::Duration; +use chrono::Utc; +use serenity::{builder::CreateEmbed, utils::MessageBuilder}; +use youmubot_osu::models::{Beatmap, Mode, Rank, Score, User}; + +fn format_mode(actual: Mode, original: Mode) -> String { + if actual == original { + format!("{}", actual) + } else { + format!("{} (converted)", actual) + } +} + +pub fn beatmap_embed<'a>(b: &'_ Beatmap, m: Mode, c: &'a mut CreateEmbed) -> &'a mut CreateEmbed { + c.title( + MessageBuilder::new() + .push_bold_safe(&b.artist) + .push(" - ") + .push_bold_safe(&b.title) + .push(" [") + .push_bold_safe(&b.difficulty_name) + .push("]") + .build(), + ) + .author(|a| { + a.name(&b.creator) + .url(format!("https://osu.ppy.sh/users/{}", b.creator_id)) + .icon_url(format!("https://a.ppy.sh/{}", b.creator_id)) + }) + .url(b.link()) + .thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id)) + .image(b.cover_url()) + .color(0xffb6c1) + .field( + "Star Difficulty", + format!("{:.2}⭐", b.difficulty.stars), + false, + ) + .field( + "Length", + MessageBuilder::new() + .push_bold_safe(Duration(b.total_length)) + .push(" (") + .push_bold_safe(Duration(b.drain_length)) + .push(" drain)") + .build(), + false, + ) + .field("Circle Size", format!("{:.1}", b.difficulty.cs), true) + .field("Approach Rate", format!("{:.1}", b.difficulty.ar), true) + .field( + "Overall Difficulty", + format!("{:.1}", b.difficulty.od), + true, + ) + .field("HP Drain", format!("{:.1}", b.difficulty.hp), true) + .field("BPM", b.bpm.round(), true) + .fields(b.difficulty.max_combo.map(|v| ("Max combo", v, true))) + .field("Mode", format_mode(m, b.mode), true) + .fields(b.source.as_ref().map(|v| ("Source", v, true))) + .field( + "Tags", + b.tags + .iter() + .map(|v| MessageBuilder::new().push_mono_safe(v).build()) + .take(10) + .chain(std::iter::once("...".to_owned())) + .collect::>() + .join(" "), + false, + ) + .description( + MessageBuilder::new() + .push({ + let link = format!("https://osu.ppy.sh/beatmapsets/{}/download", b.beatmap_id); + format!( + "Download: [[Link]]({}) [[No Video]]({}?noVideo=1)", + link, link + ) + }) + .push_line(b.beatmapset_link()) + .push_line(&b.approval) + .push("Language: ") + .push_bold(&b.language) + .push(" | Genre: ") + .push_bold(&b.genre) + .build(), + ) +} + +const MAX_DIFFS: usize = 25 - 4; + +pub fn beatmapset_embed<'a>( + bs: &'_ [Beatmap], + m: Option, + c: &'a mut CreateEmbed, +) -> &'a mut CreateEmbed { + let too_many_diffs = bs.len() > MAX_DIFFS; + let b: &Beatmap = &bs[0]; + c.title( + MessageBuilder::new() + .push_bold_safe(&b.artist) + .push(" - ") + .push_bold_safe(&b.title) + .build(), + ) + .author(|a| { + a.name(&b.creator) + .url(format!("https://osu.ppy.sh/users/{}", b.creator_id)) + .icon_url(format!("https://a.ppy.sh/{}", b.creator_id)) + }) + .url(format!( + "https://osu.ppy.sh/beatmapsets/{}", + b.beatmapset_id, + )) + // .thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id)) + .image(format!( + "https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg", + b.beatmapset_id + )) + .color(0xffb6c1) + .description( + MessageBuilder::new() + .push_line({ + let link = format!("https://osu.ppy.sh/beatmapsets/{}/download", b.beatmap_id); + format!( + "Download: [[Link]]({}) [[No Video]]({}?noVideo=1)", + link, link + ) + }) + .push_line(&b.approval) + .push("Language: ") + .push_bold(&b.language) + .push(" | Genre: ") + .push_bold(&b.genre) + .build(), + ) + .field( + "Length", + MessageBuilder::new() + .push_bold_safe(Duration(b.total_length)) + .build(), + true, + ) + .field("BPM", b.bpm.round(), true) + .fields(b.source.as_ref().map(|v| ("Source", v, false))) + .field( + "Tags", + b.tags + .iter() + .map(|v| MessageBuilder::new().push_mono_safe(v).build()) + .take(10) + .chain(std::iter::once("...".to_owned())) + .collect::>() + .join(" "), + false, + ) + .footer(|f| { + if too_many_diffs { + f.text(format!( + "This map has {} diffs, we are showing the last {}.", + bs.len(), + MAX_DIFFS + )) + } else { + f + } + }) + .fields(bs.iter().rev().take(MAX_DIFFS).rev().map(|b: &Beatmap| { + ( + format!("[{}]", b.difficulty_name), + MessageBuilder::new() + .push(format!("[[Link]]({})", b.link())) + .push(", ") + .push_bold(format!("{:.2}⭐", b.difficulty.stars)) + .push(", ") + .push_bold_line(format_mode(m.unwrap_or(b.mode), b.mode)) + .push("CS") + .push_bold(format!("{:.1}", b.difficulty.cs)) + .push(", AR") + .push_bold(format!("{:.1}", b.difficulty.ar)) + .push(", OD") + .push_bold(format!("{:.1}", b.difficulty.od)) + .push(", HP") + .push_bold(format!("{:.1}", b.difficulty.hp)) + .push(", ⌛ ") + .push_bold(format!("{}", Duration(b.drain_length))) + .build(), + false, + ) + })) +} + +pub(crate) fn score_embed<'a>( + s: &Score, + bm: &BeatmapWithMode, + u: &User, + top_record: Option, + m: &'a mut CreateEmbed, +) -> &'a mut CreateEmbed { + let mode = bm.mode(); + let b = &bm.0; + let accuracy = s.accuracy(mode); + let score_line = match &s.rank { + Rank::SS | Rank::SSH => format!("SS"), + _ if s.perfect => format!("{:2}% FC", accuracy), + Rank::F => format!("{:.2}% {} combo [FAILED]", accuracy, s.max_combo), + v => format!("{:.2}% {} combo {} rank", accuracy, s.max_combo, v), + }; + let score_line = + s.pp.map(|pp| format!("{} | {:2}pp", &score_line, pp)) + .unwrap_or(score_line); + let top_record = top_record + .map(|v| format!("| #{} top record!", v)) + .unwrap_or("".to_owned()); + m.author(|f| f.name(&u.username).url(u.link()).icon_url(u.avatar_url())) + .color(0xffb6c1) + .title(format!( + "{} | {} - {} [{}] {} ({:.2}\\*) by {} | {} {}", + u.username, + b.artist, + b.title, + b.difficulty_name, + s.mods, + b.difficulty.stars, + b.creator, + score_line, + top_record + )) + .description(format!("[[Beatmap]]({})", b.link())) + .image(b.cover_url()) + .field( + "Beatmap", + format!("{} - {} [{}]", b.artist, b.title, b.difficulty_name), + false, + ) + .field("Rank", &score_line, false) + .fields(s.pp.map(|pp| ("pp gained", format!("{:2}pp", pp), true))) + .field("Creator", &b.creator, true) + .field("Mode", mode.to_string(), true) + .field( + "Map stats", + MessageBuilder::new() + .push(format!("[[Link]]({})", b.link())) + .push(", ") + .push_bold(format!("{:.2}⭐", b.difficulty.stars)) + .push(", ") + .push_bold_line( + b.mode.to_string() + + if bm.is_converted() { + " (Converted)" + } else { + "" + }, + ) + .push("CS") + .push_bold(format!("{:.1}", b.difficulty.cs)) + .push(", AR") + .push_bold(format!("{:.1}", b.difficulty.ar)) + .push(", OD") + .push_bold(format!("{:.1}", b.difficulty.od)) + .push(", HP") + .push_bold(format!("{:.1}", b.difficulty.hp)) + .push(", ⌛ ") + .push_bold(format!("{}", Duration(b.drain_length))) + .build(), + false, + ) + .field("Played on", s.date.format("%F %T"), false) +} + +pub(crate) fn user_embed<'a>( + u: User, + best: Option<(Score, BeatmapWithMode)>, + m: &'a mut CreateEmbed, +) -> &'a mut CreateEmbed { + m.title(u.username) + .url(format!("https://osu.ppy.sh/users/{}", u.id)) + .color(0xffb6c1) + .thumbnail(format!("https://a.ppy.sh/{}", u.id)) + .description(format!("Member since **{}**", u.joined.format("%F %T"))) + .field( + "Performance Points", + u.pp.map(|v| format!("{:.2}pp", v)) + .unwrap_or("Inactive".to_owned()), + false, + ) + .field("World Rank", format!("#{}", u.rank), true) + .field( + "Country Rank", + format!(":flag_{}: #{}", u.country.to_lowercase(), u.country_rank), + true, + ) + .field("Accuracy", format!("{:.2}%", u.accuracy), true) + .field( + "Play count", + format!("{} (play time: {})", u.play_count, Duration(u.played_time)), + false, + ) + .field( + "Ranks", + format!( + "{} SSH | {} SS | {} SH | {} S | {} A", + u.count_ssh, u.count_ss, u.count_sh, u.count_s, u.count_a + ), + false, + ) + .field( + "Level", + format!( + "Level **{:.0}**: {} total score, {} ranked score", + u.level, u.total_score, u.ranked_score + ), + false, + ) + .fields(best.map(|(v, map)| { + let map = map.0; + ( + "Best Record", + MessageBuilder::new() + .push_bold(format!( + "{:.2}pp", + v.pp.unwrap() /*Top record should have pp*/ + )) + .push(" - ") + .push_line(format!( + "{:.1} ago", + Duration( + (Utc::now() - v.date) + .to_std() + .unwrap_or(std::time::Duration::from_secs(1)) + ) + )) + .push("on ") + .push(format!( + "[{} - {}]({})", + MessageBuilder::new().push_bold_safe(&map.artist).build(), + MessageBuilder::new().push_bold_safe(&map.title).build(), + map.link() + )) + .push(format!(" [{}]", map.difficulty_name)) + .push(format!(" ({:.1}⭐)", map.difficulty.stars)) + .build(), + false, + ) + })) +} diff --git a/youmubot/src/commands/osu/hook.rs b/youmubot/src/commands/osu/hook.rs index 4db3d8a..b109262 100644 --- a/youmubot/src/commands/osu/hook.rs +++ b/youmubot/src/commands/osu/hook.rs @@ -1,9 +1,8 @@ -use crate::commands::args::Duration; use crate::http; use lazy_static::lazy_static; use regex::Regex; use serenity::{ - builder::{CreateEmbed, CreateMessage}, + builder::CreateMessage, framework::standard::{CommandError as Error, CommandResult}, model::channel::Message, prelude::*, @@ -14,6 +13,8 @@ use youmubot_osu::{ request::BeatmapRequestKind, }; +use super::embeds::{beatmap_embed, beatmapset_embed}; + lazy_static! { static ref OLD_LINK_REGEX: Regex = Regex::new( r"https?://osu\.ppy\.sh/(?Ps|b)/(?P\d+)(?:[\&\?]m=(?P\d))?(?:\+(?P[A-Z]+))?" @@ -207,207 +208,3 @@ fn handle_beatmapset<'a, 'b>( ) .embed(|b| beatmapset_embed(&beatmaps, mode, b)) } - -const NEW_MODE_NAMES: [&'static str; 4] = ["osu", "taiko", "fruits", "mania"]; - -fn format_mode(actual: Mode, original: Mode) -> String { - if actual == original { - format!("{}", actual) - } else { - format!("{} (converted)", actual) - } -} - -fn beatmap_embed<'a>(b: &'_ Beatmap, m: Mode, c: &'a mut CreateEmbed) -> &'a mut CreateEmbed { - c.title( - MessageBuilder::new() - .push_bold_safe(&b.artist) - .push(" - ") - .push_bold_safe(&b.title) - .push(" [") - .push_bold_safe(&b.difficulty_name) - .push("]") - .build(), - ) - .author(|a| { - a.name(&b.creator) - .url(format!("https://osu.ppy.sh/users/{}", b.creator_id)) - .icon_url(format!("https://a.ppy.sh/{}", b.creator_id)) - }) - .url(format!( - "https://osu.ppy.sh/beatmapsets/{}/#{}/{}", - b.beatmapset_id, NEW_MODE_NAMES[b.mode as usize], b.beatmap_id - )) - .thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id)) - .image(format!( - "https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg", - b.beatmapset_id - )) - .color(0xffb6c1) - .field( - "Star Difficulty", - format!("{:.2}⭐", b.difficulty.stars), - false, - ) - .field( - "Length", - MessageBuilder::new() - .push_bold_safe(Duration(b.total_length)) - .push(" (") - .push_bold_safe(Duration(b.drain_length)) - .push(" drain)") - .build(), - false, - ) - .field("Circle Size", format!("{:.1}", b.difficulty.cs), true) - .field("Approach Rate", format!("{:.1}", b.difficulty.ar), true) - .field( - "Overall Difficulty", - format!("{:.1}", b.difficulty.od), - true, - ) - .field("HP Drain", format!("{:.1}", b.difficulty.hp), true) - .field("BPM", b.bpm.round(), true) - .fields(b.difficulty.max_combo.map(|v| ("Max combo", v, true))) - .field("Mode", format_mode(m, b.mode), true) - .fields(b.source.as_ref().map(|v| ("Source", v, true))) - .field( - "Tags", - b.tags - .iter() - .map(|v| MessageBuilder::new().push_mono_safe(v).build()) - .take(10) - .chain(std::iter::once("...".to_owned())) - .collect::>() - .join(" "), - false, - ) - .description( - MessageBuilder::new() - .push({ - let link = format!("https://osu.ppy.sh/beatmapsets/{}/download", b.beatmap_id); - format!( - "Download: [[Link]]({}) [[No Video]]({}?noVideo=1)", - link, link - ) - }) - .push_line(format!( - " [[Beatmapset]](https://osu.ppy.sh/beatmapsets/{}/#{})", - b.beatmapset_id, NEW_MODE_NAMES[b.mode as usize], - )) - .push_line(&b.approval) - .push("Language: ") - .push_bold(&b.language) - .push(" | Genre: ") - .push_bold(&b.genre) - .build(), - ) -} - -const MAX_DIFFS: usize = 25 - 4; - -fn beatmapset_embed<'a>( - bs: &'_ [Beatmap], - m: Option, - c: &'a mut CreateEmbed, -) -> &'a mut CreateEmbed { - let too_many_diffs = bs.len() > MAX_DIFFS; - let b: &Beatmap = &bs[0]; - c.title( - MessageBuilder::new() - .push_bold_safe(&b.artist) - .push(" - ") - .push_bold_safe(&b.title) - .build(), - ) - .author(|a| { - a.name(&b.creator) - .url(format!("https://osu.ppy.sh/users/{}", b.creator_id)) - .icon_url(format!("https://a.ppy.sh/{}", b.creator_id)) - }) - .url(format!( - "https://osu.ppy.sh/beatmapsets/{}", - b.beatmapset_id, - )) - // .thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id)) - .image(format!( - "https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg", - b.beatmapset_id - )) - .color(0xffb6c1) - .description( - MessageBuilder::new() - .push_line({ - let link = format!("https://osu.ppy.sh/beatmapsets/{}/download", b.beatmap_id); - format!( - "Download: [[Link]]({}) [[No Video]]({}?noVideo=1)", - link, link - ) - }) - .push_line(&b.approval) - .push("Language: ") - .push_bold(&b.language) - .push(" | Genre: ") - .push_bold(&b.genre) - .build(), - ) - .field( - "Length", - MessageBuilder::new() - .push_bold_safe(Duration(b.total_length)) - .build(), - true, - ) - .field("BPM", b.bpm.round(), true) - .fields(b.source.as_ref().map(|v| ("Source", v, false))) - .field( - "Tags", - b.tags - .iter() - .map(|v| MessageBuilder::new().push_mono_safe(v).build()) - .take(10) - .chain(std::iter::once("...".to_owned())) - .collect::>() - .join(" "), - false, - ) - .footer(|f| { - if too_many_diffs { - f.text(format!( - "This map has {} diffs, we are showing the last {}.", - bs.len(), - MAX_DIFFS - )) - } else { - f - } - }) - .fields(bs.iter().rev().take(MAX_DIFFS).rev().map(|b: &Beatmap| { - ( - format!("[{}]", b.difficulty_name), - MessageBuilder::new() - .push(format!( - "[[Link]](https://osu.ppy.sh/beatmapsets/{}/#{}/{})", - b.beatmapset_id, - NEW_MODE_NAMES[m.unwrap_or(b.mode) as usize], - b.beatmap_id - )) - .push(", ") - .push_bold(format!("{:.2}⭐", b.difficulty.stars)) - .push(", ") - .push_bold_line(format_mode(m.unwrap_or(b.mode), b.mode)) - .push("CS") - .push_bold(format!("{:.1}", b.difficulty.cs)) - .push(", AR") - .push_bold(format!("{:.1}", b.difficulty.ar)) - .push(", OD") - .push_bold(format!("{:.1}", b.difficulty.od)) - .push(", HP") - .push_bold(format!("{:.1}", b.difficulty.hp)) - .push(", ⌛ ") - .push_bold(format!("{}", Duration(b.drain_length))) - .build(), - false, - ) - })) -} diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot/src/commands/osu/mod.rs index d3917be..09c045e 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot/src/commands/osu/mod.rs @@ -1,9 +1,6 @@ -use crate::commands::args::Duration; use crate::db::{DBWriteGuard, OsuSavedUsers}; use crate::http; -use chrono::Utc; use serenity::{ - builder::CreateEmbed, framework::standard::{ macros::{command, group}, Args, CommandError as Error, CommandResult, @@ -12,6 +9,7 @@ use serenity::{ prelude::*, utils::MessageBuilder, }; +use std::str::FromStr; use youmubot_osu::{ models::{Beatmap, Mode, Rank, Score, User}, request::{BeatmapRequestKind, UserID}, @@ -19,10 +17,11 @@ use youmubot_osu::{ }; mod cache; +pub(crate) mod embeds; mod hook; +use embeds::{beatmap_embed, score_embed, user_embed}; pub use hook::hook; -use std::str::FromStr; group!({ name: "osu", @@ -30,7 +29,7 @@ group!({ prefix: "osu", description: "osu! related commands.", }, - commands: [std, taiko, catch, mania, save, recent], + commands: [std, taiko, catch, mania, save, recent, last], }); #[command] @@ -215,6 +214,32 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult Ok(()) } +#[command] +#[description = "Show information from the last queried beatmap."] +#[num_args(0)] +pub fn last(ctx: &mut Context, msg: &Message, _: Args) -> CommandResult { + let mut data = ctx.data.write(); + + let b = cache::get_beatmap(&mut *data, msg.channel_id)?; + + match b { + Some(BeatmapWithMode(b, m)) => { + msg.channel_id.send_message(&ctx, |f| { + f.content(format!( + "{}: here is the beatmap you requested!", + msg.author + )) + .embed(|c| beatmap_embed(&b, m, c)) + })?; + } + None => { + msg.reply(&ctx, "No beatmap was queried on this channel.")?; + } + } + + Ok(()) +} + fn get_user(ctx: &mut Context, msg: &Message, args: Args, mode: Mode) -> CommandResult { let mut data = ctx.data.write(); let username = match args.remains() { @@ -262,158 +287,3 @@ fn get_user(ctx: &mut Context, msg: &Message, args: Args, mode: Mode) -> Command }?; Ok(()) } - -fn score_embed<'a>( - s: &Score, - bm: &BeatmapWithMode, - u: &User, - top_record: Option, - m: &'a mut CreateEmbed, -) -> &'a mut CreateEmbed { - let mode = bm.mode(); - let b = &bm.0; - let accuracy = s.accuracy(mode); - let score_line = match &s.rank { - Rank::SS | Rank::SSH => format!("SS"), - _ if s.perfect => format!("{:2}% FC", accuracy), - Rank::F => format!("{:.2}% {} combo [FAILED]", accuracy, s.max_combo), - v => format!("{:.2}% {} combo {} rank", accuracy, s.max_combo, v), - }; - let score_line = - s.pp.map(|pp| format!("{} | {:2}pp", &score_line, pp)) - .unwrap_or(score_line); - let top_record = top_record - .map(|v| format!("| #{} top record!", v)) - .unwrap_or("".to_owned()); - m.author(|f| f.name(&u.username).url(u.link()).icon_url(u.avatar_url())) - .color(0xffb6c1) - .title(format!( - "{} | {} - {} [{}] {} ({:.2}\\*) by {} | {} {}", - u.username, - b.artist, - b.title, - b.difficulty_name, - s.mods, - b.difficulty.stars, - b.creator, - score_line, - top_record - )) - .description(format!("[[Beatmap]]({})", b.link())) - .image(b.cover_url()) - .field( - "Beatmap", - format!("{} - {} [{}]", b.artist, b.title, b.difficulty_name), - false, - ) - .field("Rank", &score_line, false) - .fields(s.pp.map(|pp| ("pp gained", format!("{:2}pp", pp), true))) - .field("Creator", &b.creator, true) - .field("Mode", mode.to_string(), true) - .field( - "Map stats", - MessageBuilder::new() - .push(format!("[[Link]]({})", b.link())) - .push(", ") - .push_bold(format!("{:.2}⭐", b.difficulty.stars)) - .push(", ") - .push_bold_line( - b.mode.to_string() - + if bm.is_converted() { - " (Converted)" - } else { - "" - }, - ) - .push("CS") - .push_bold(format!("{:.1}", b.difficulty.cs)) - .push(", AR") - .push_bold(format!("{:.1}", b.difficulty.ar)) - .push(", OD") - .push_bold(format!("{:.1}", b.difficulty.od)) - .push(", HP") - .push_bold(format!("{:.1}", b.difficulty.hp)) - .push(", ⌛ ") - .push_bold(format!("{}", Duration(b.drain_length))) - .build(), - false, - ) - .field("Played on", s.date.format("%F %T"), false) -} - -fn user_embed<'a>( - u: User, - best: Option<(Score, BeatmapWithMode)>, - m: &'a mut CreateEmbed, -) -> &'a mut CreateEmbed { - m.title(u.username) - .url(format!("https://osu.ppy.sh/users/{}", u.id)) - .color(0xffb6c1) - .thumbnail(format!("https://a.ppy.sh/{}", u.id)) - .description(format!("Member since **{}**", u.joined.format("%F %T"))) - .field( - "Performance Points", - u.pp.map(|v| format!("{:.2}pp", v)) - .unwrap_or("Inactive".to_owned()), - false, - ) - .field("World Rank", format!("#{}", u.rank), true) - .field( - "Country Rank", - format!(":flag_{}: #{}", u.country.to_lowercase(), u.country_rank), - true, - ) - .field("Accuracy", format!("{:.2}%", u.accuracy), true) - .field( - "Play count", - format!("{} (play time: {})", u.play_count, Duration(u.played_time)), - false, - ) - .field( - "Ranks", - format!( - "{} SSH | {} SS | {} SH | {} S | {} A", - u.count_ssh, u.count_ss, u.count_sh, u.count_s, u.count_a - ), - false, - ) - .field( - "Level", - format!( - "Level **{:.0}**: {} total score, {} ranked score", - u.level, u.total_score, u.ranked_score - ), - false, - ) - .fields(best.map(|(v, map)| { - let map = map.0; - ( - "Best Record", - MessageBuilder::new() - .push_bold(format!( - "{:.2}pp", - v.pp.unwrap() /*Top record should have pp*/ - )) - .push(" - ") - .push_line(format!( - "{:.1} ago", - Duration( - (Utc::now() - v.date) - .to_std() - .unwrap_or(std::time::Duration::from_secs(1)) - ) - )) - .push("on ") - .push(format!( - "[{} - {}]({})", - MessageBuilder::new().push_bold_safe(&map.artist).build(), - MessageBuilder::new().push_bold_safe(&map.title).build(), - map.link() - )) - .push(format!(" [{}]", map.difficulty_name)) - .push(format!(" ({:.1}⭐)", map.difficulty.stars)) - .build(), - false, - ) - })) -} diff --git a/youmubot/src/db/mod.rs b/youmubot/src/db/mod.rs index ce8cb80..2205df2 100644 --- a/youmubot/src/db/mod.rs +++ b/youmubot/src/db/mod.rs @@ -10,7 +10,7 @@ use serenity::{ }; use std::collections::HashMap; use std::path::{Path, PathBuf}; -use youmubot_osu::models::Mode; +use youmubot_osu::models::{Beatmap, Mode}; /// GuildMap defines the guild-map type. /// It is basically a HashMap from a GuildId to a data structure. @@ -40,7 +40,7 @@ pub type SoftBans = DB>; pub type OsuSavedUsers = DB>; /// Save each channel's last requested beatmap. -pub type OsuLastBeatmap = DB>; +pub type OsuLastBeatmap = DB>; /// Sets up all databases in the client. pub fn setup_db(client: &mut Client) -> Result<(), Error> {