mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-16 07:18:54 +00:00
Move embeds to one file, implement last
This commit is contained in:
parent
596d632e5a
commit
6de9dcf669
6 changed files with 401 additions and 376 deletions
|
@ -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<f64>,
|
||||
|
@ -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<u64>, // No id if you fail
|
||||
pub user_id: u64,
|
||||
|
|
|
@ -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<Option<(u64, Mode)>, Error> {
|
||||
) -> Result<Option<BeatmapWithMode>, Error> {
|
||||
let db = data.get::<OsuLastBeatmap>().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)))
|
||||
}
|
||||
|
|
348
youmubot/src/commands/osu/embeds.rs
Normal file
348
youmubot/src/commands/osu/embeds.rs
Normal file
|
@ -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::<Vec<_>>()
|
||||
.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<Mode>,
|
||||
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::<Vec<_>>()
|
||||
.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<u8>,
|
||||
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,
|
||||
)
|
||||
}))
|
||||
}
|
|
@ -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/(?P<link_type>s|b)/(?P<id>\d+)(?:[\&\?]m=(?P<mode>\d))?(?:\+(?P<mods>[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::<Vec<_>>()
|
||||
.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<Mode>,
|
||||
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::<Vec<_>>()
|
||||
.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,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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<u8>,
|
||||
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,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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<GuildMap<ServerSoftBans>>;
|
|||
pub type OsuSavedUsers = DB<HashMap<UserId, u64>>;
|
||||
|
||||
/// Save each channel's last requested beatmap.
|
||||
pub type OsuLastBeatmap = DB<HashMap<ChannelId, (u64, Mode)>>;
|
||||
pub type OsuLastBeatmap = DB<HashMap<ChannelId, (Beatmap, Mode)>>;
|
||||
|
||||
/// Sets up all databases in the client.
|
||||
pub fn setup_db(client: &mut Client) -> Result<(), Error> {
|
||||
|
|
Loading…
Add table
Reference in a new issue