mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Lots of tweaks to the osu! embeds (#4)
* Simplify beatmap embed information * Shrink embeds!! * Put ranked status on information * Paint the user embed! * Add gained pp * Use more carefully crafted hooks * 'JSformat' the bpm float
This commit is contained in:
parent
6659bdda57
commit
c6e8bfc43e
4 changed files with 201 additions and 201 deletions
|
@ -7,12 +7,53 @@ use chrono::Utc;
|
||||||
use serenity::{builder::CreateEmbed, utils::MessageBuilder};
|
use serenity::{builder::CreateEmbed, utils::MessageBuilder};
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
fn format_mode(actual: Mode, original: Mode) -> String {
|
/// Writes a number grouped in groups of 3.
|
||||||
if actual == original {
|
fn grouped_number(num: u64) -> String {
|
||||||
format!("{}", actual)
|
let s = num.to_string();
|
||||||
} else {
|
let mut b = MessageBuilder::new();
|
||||||
format!("{} (converted)", actual)
|
let mut i = if s.len() % 3 == 0 { 3 } else { s.len() % 3 };
|
||||||
|
b.push(&s[..i]);
|
||||||
|
while i < s.len() {
|
||||||
|
b.push(",").push(&s[i..i + 3]);
|
||||||
|
i += 3;
|
||||||
}
|
}
|
||||||
|
b.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn beatmap_description(b: &Beatmap) -> String {
|
||||||
|
MessageBuilder::new()
|
||||||
|
.push_bold_line(&b.approval)
|
||||||
|
.push({
|
||||||
|
let link = b.download_link(false);
|
||||||
|
format!(
|
||||||
|
"Download: [[Link]]({}) [[No Video]]({}?noVideo=1) [[Bloodcat]]({})",
|
||||||
|
link,
|
||||||
|
link,
|
||||||
|
b.download_link(true),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.push_line(format!(" [[Beatmapset]]({})", b.beatmapset_link()))
|
||||||
|
.push("Language: ")
|
||||||
|
.push_bold(&b.language)
|
||||||
|
.push(" | Genre: ")
|
||||||
|
.push_bold_line(&b.genre)
|
||||||
|
.push(
|
||||||
|
b.source
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| format!("Source: **{}**\n", v))
|
||||||
|
.unwrap_or_else(|| "".to_owned()),
|
||||||
|
)
|
||||||
|
.push("Tags: ")
|
||||||
|
.push_line(
|
||||||
|
b.tags
|
||||||
|
.iter()
|
||||||
|
.map(|v| MessageBuilder::new().push_mono_safe(v).build())
|
||||||
|
.take(10)
|
||||||
|
.chain(std::iter::once("...".to_owned()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" "),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beatmap_embed<'a>(
|
pub fn beatmap_embed<'a>(
|
||||||
|
@ -27,7 +68,7 @@ pub fn beatmap_embed<'a>(
|
||||||
} else {
|
} else {
|
||||||
format!(" {}", mods)
|
format!(" {}", mods)
|
||||||
};
|
};
|
||||||
let diff = b.difficulty.apply_mods(mods);
|
let diff = b.difficulty.apply_mods(mods, info.map(|v| v.stars as f64));
|
||||||
c.title(
|
c.title(
|
||||||
MessageBuilder::new()
|
MessageBuilder::new()
|
||||||
.push_bold_safe(&b.artist)
|
.push_bold_safe(&b.artist)
|
||||||
|
@ -45,18 +86,8 @@ pub fn beatmap_embed<'a>(
|
||||||
.icon_url(format!("https://a.ppy.sh/{}", b.creator_id))
|
.icon_url(format!("https://a.ppy.sh/{}", b.creator_id))
|
||||||
})
|
})
|
||||||
.url(b.link())
|
.url(b.link())
|
||||||
.thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id))
|
|
||||||
.image(b.cover_url())
|
.image(b.cover_url())
|
||||||
.color(0xffb6c1)
|
.color(0xffb6c1)
|
||||||
.field(
|
|
||||||
"Star Difficulty",
|
|
||||||
format!(
|
|
||||||
"{:.2}⭐",
|
|
||||||
info.map(|v| v.stars as f64).unwrap_or(b.difficulty.stars)
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.fields(Some(("Mods", mods, true)).filter(|_| mods != Mods::NOMOD))
|
|
||||||
.fields(info.map(|info| {
|
.fields(info.map(|info| {
|
||||||
(
|
(
|
||||||
"Calculated pp",
|
"Calculated pp",
|
||||||
|
@ -67,59 +98,8 @@ pub fn beatmap_embed<'a>(
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
.field(
|
.field("Information", diff.format_info(m, mods, b), false)
|
||||||
"Length",
|
.description(beatmap_description(b))
|
||||||
MessageBuilder::new()
|
|
||||||
.push_bold_safe(Duration(diff.total_length))
|
|
||||||
.push(" (")
|
|
||||||
.push_bold_safe(Duration(diff.drain_length))
|
|
||||||
.push(" drain)")
|
|
||||||
.build(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.field("Circle Size", format!("{:.1}", diff.cs), true)
|
|
||||||
.field("Approach Rate", format!("{:.1}", diff.ar), true)
|
|
||||||
.field("Overall Difficulty", format!("{:.1}", diff.od), true)
|
|
||||||
.field("HP Drain", format!("{:.1}", diff.hp), true)
|
|
||||||
.field("BPM", diff.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.beatmapset_id
|
|
||||||
);
|
|
||||||
format!(
|
|
||||||
"Download: [[Link]]({}) [[No Video]]({}?noVideo=1)",
|
|
||||||
link, link
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.push_line(format!(" [[Beatmapset]]({})", b.beatmapset_link()))
|
|
||||||
.push_line(format!(
|
|
||||||
"Short link: `{}`",
|
|
||||||
b.short_link(Some(m), Some(mods))
|
|
||||||
))
|
|
||||||
.push_bold_line(&b.approval)
|
|
||||||
.push("Language: ")
|
|
||||||
.push_bold(&b.language)
|
|
||||||
.push(" | Genre: ")
|
|
||||||
.push_bold(&b.genre)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.footer(|f| {
|
.footer(|f| {
|
||||||
if info.is_none() && mods != Mods::NOMOD {
|
if info.is_none() && mods != Mods::NOMOD {
|
||||||
f.text("Star difficulty not reflecting mods applied.");
|
f.text("Star difficulty not reflecting mods applied.");
|
||||||
|
@ -153,51 +133,12 @@ pub fn beatmapset_embed<'a>(
|
||||||
"https://osu.ppy.sh/beatmapsets/{}",
|
"https://osu.ppy.sh/beatmapsets/{}",
|
||||||
b.beatmapset_id,
|
b.beatmapset_id,
|
||||||
))
|
))
|
||||||
// .thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id))
|
|
||||||
.image(format!(
|
.image(format!(
|
||||||
"https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg",
|
"https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg",
|
||||||
b.beatmapset_id
|
b.beatmapset_id
|
||||||
))
|
))
|
||||||
.color(0xffb6c1)
|
.color(0xffb6c1)
|
||||||
.description(
|
.description(beatmap_description(b))
|
||||||
MessageBuilder::new()
|
|
||||||
.push_line({
|
|
||||||
let link = format!(
|
|
||||||
"https://osu.ppy.sh/beatmapsets/{}/download",
|
|
||||||
b.beatmapset_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.difficulty.total_length))
|
|
||||||
.build(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.field("BPM", b.difficulty.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| {
|
.footer(|f| {
|
||||||
if too_many_diffs {
|
if too_many_diffs {
|
||||||
f.text(format!(
|
f.text(format!(
|
||||||
|
@ -212,27 +153,8 @@ pub fn beatmapset_embed<'a>(
|
||||||
.fields(bs.iter().rev().take(MAX_DIFFS).rev().map(|b: &Beatmap| {
|
.fields(bs.iter().rev().take(MAX_DIFFS).rev().map(|b: &Beatmap| {
|
||||||
(
|
(
|
||||||
format!("[{}]", b.difficulty_name),
|
format!("[{}]", b.difficulty_name),
|
||||||
MessageBuilder::new()
|
b.difficulty
|
||||||
.push(format!(
|
.format_info(m.unwrap_or(b.mode), Mods::NOMOD, b),
|
||||||
"[[Link]]({}) (`{}`)",
|
|
||||||
b.link(),
|
|
||||||
b.short_link(m, None)
|
|
||||||
))
|
|
||||||
.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.difficulty.drain_length)))
|
|
||||||
.build(),
|
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
@ -297,13 +219,31 @@ pub(crate) fn score_embed<'a>(
|
||||||
} else {
|
} else {
|
||||||
pp.map(|v| v.1)
|
pp.map(|v| v.1)
|
||||||
};
|
};
|
||||||
|
let pp_gained = s.pp.map(|full_pp| {
|
||||||
|
top_record
|
||||||
|
.map(|top| {
|
||||||
|
let after_pp = u.pp.unwrap();
|
||||||
|
let effective_pp = full_pp * (0.95f64).powi(top as i32);
|
||||||
|
let before_pp = after_pp - effective_pp;
|
||||||
|
format!(
|
||||||
|
"**pp gained**: **{:.2}**pp (+**{:.2}**pp | {:.2}pp \\➡️ {:.2}pp)",
|
||||||
|
full_pp, effective_pp, before_pp, after_pp
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| format!("**pp gained**: **{:.2}**pp", full_pp))
|
||||||
|
});
|
||||||
let score_line = pp
|
let score_line = pp
|
||||||
.map(|pp| format!("{} | {}", &score_line, pp))
|
.map(|pp| format!("{} | {}", &score_line, pp))
|
||||||
.unwrap_or(score_line);
|
.unwrap_or(score_line);
|
||||||
|
let max_combo = b
|
||||||
|
.difficulty
|
||||||
|
.max_combo
|
||||||
|
.map(|max| format!("**{}x**/{}x", s.max_combo, max))
|
||||||
|
.unwrap_or_else(|| format!("**{}x**", s.max_combo));
|
||||||
let top_record = top_record
|
let top_record = top_record
|
||||||
.map(|v| format!("| #{} top record!", v))
|
.map(|v| format!("| #{} top record!", v))
|
||||||
.unwrap_or("".to_owned());
|
.unwrap_or("".to_owned());
|
||||||
let diff = b.difficulty.apply_mods(s.mods);
|
let diff = b.difficulty.apply_mods(s.mods, Some(stars));
|
||||||
m.author(|f| f.name(&u.username).url(u.link()).icon_url(u.avatar_url()))
|
m.author(|f| f.name(&u.username).url(u.link()).icon_url(u.avatar_url()))
|
||||||
.color(0xffb6c1)
|
.color(0xffb6c1)
|
||||||
.title(format!(
|
.title(format!(
|
||||||
|
@ -318,60 +258,42 @@ pub(crate) fn score_embed<'a>(
|
||||||
score_line,
|
score_line,
|
||||||
top_record
|
top_record
|
||||||
))
|
))
|
||||||
.description(format!("[[Beatmap]]({})", b.link()))
|
.description(format!(
|
||||||
|
r#"**Beatmap**: {} - {} [{}]**{} **
|
||||||
|
**Links**: [[Listing]]({}) [[Download]]({}) [[Bloodcat]]({})
|
||||||
|
**Played on**: {}
|
||||||
|
{}"#,
|
||||||
|
b.artist,
|
||||||
|
b.title,
|
||||||
|
b.difficulty_name,
|
||||||
|
s.mods,
|
||||||
|
b.link(),
|
||||||
|
b.download_link(false),
|
||||||
|
b.download_link(true),
|
||||||
|
s.date.format("%F %T"),
|
||||||
|
pp_gained.as_ref().map(|v| &v[..]).unwrap_or(""),
|
||||||
|
))
|
||||||
.image(b.cover_url())
|
.image(b.cover_url())
|
||||||
.field(
|
.field(
|
||||||
"Beatmap",
|
"Score stats",
|
||||||
format!("{} - {} [{}]", b.artist, b.title, b.difficulty_name),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.field("Rank", &score_line, false)
|
|
||||||
.field(
|
|
||||||
"300s / 100s / 50s / misses",
|
|
||||||
format!(
|
format!(
|
||||||
"**{}** ({}) / **{}** ({}) / **{}** / **{}**",
|
"**{}** | {} | **{:.2}%**",
|
||||||
|
grouped_number(s.score),
|
||||||
|
max_combo,
|
||||||
|
accuracy
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.field(
|
||||||
|
"300s | 100s | 50s | misses",
|
||||||
|
format!(
|
||||||
|
"**{}** ({}) | **{}** ({}) | **{}** | **{}**",
|
||||||
s.count_300, s.count_geki, s.count_100, s.count_katu, s.count_50, s.count_miss
|
s.count_300, s.count_geki, s.count_100, s.count_katu, s.count_50, s.count_miss
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.fields(s.pp.map(|pp| ("pp gained", format!("{:.2}pp", pp), true)))
|
.field("Map stats", diff.format_info(mode, s.mods, b), false)
|
||||||
.field("Mode", mode.to_string(), true)
|
.timestamp(&s.date);
|
||||||
.field(
|
|
||||||
"Map stats",
|
|
||||||
MessageBuilder::new()
|
|
||||||
.push(format!(
|
|
||||||
"[[Link]]({}) (`{}`)",
|
|
||||||
b.link(),
|
|
||||||
b.short_link(Some(mode), Some(s.mods))
|
|
||||||
))
|
|
||||||
.push(", ")
|
|
||||||
.push_bold(format!("{:.2}⭐", stars))
|
|
||||||
.push(", ")
|
|
||||||
.push_bold_line(
|
|
||||||
b.mode.to_string()
|
|
||||||
+ if bm.is_converted() {
|
|
||||||
" (Converted)"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.push("CS")
|
|
||||||
.push_bold(format!("{:.1}", diff.cs))
|
|
||||||
.push(", AR")
|
|
||||||
.push_bold(format!("{:.1}", diff.ar))
|
|
||||||
.push(", OD")
|
|
||||||
.push_bold(format!("{:.1}", diff.od))
|
|
||||||
.push(", HP")
|
|
||||||
.push_bold(format!("{:.1}", diff.hp))
|
|
||||||
.push(", BPM ")
|
|
||||||
.push_bold(format!("{}", diff.bpm.round()))
|
|
||||||
.push(", ⌛ ")
|
|
||||||
.push_bold(format!("{}", Duration(diff.drain_length)))
|
|
||||||
.build(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.timestamp(&s.date)
|
|
||||||
.field("Played on", s.date.format("%F %T"), false);
|
|
||||||
if mode.to_oppai_mode().is_none() && s.mods != Mods::NOMOD {
|
if mode.to_oppai_mode().is_none() && s.mods != Mods::NOMOD {
|
||||||
m.footer(|f| f.text("Star difficulty does not reflect game mods."));
|
m.footer(|f| f.text("Star difficulty does not reflect game mods."));
|
||||||
}
|
}
|
||||||
|
@ -394,31 +316,44 @@ pub(crate) fn user_embed<'a>(
|
||||||
.unwrap_or("Inactive".to_owned()),
|
.unwrap_or("Inactive".to_owned()),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.field("World Rank", format!("#{}", u.rank), true)
|
.field("World Rank", format!("#{}", grouped_number(u.rank)), true)
|
||||||
.field(
|
.field(
|
||||||
"Country Rank",
|
"Country Rank",
|
||||||
format!(":flag_{}: #{}", u.country.to_lowercase(), u.country_rank),
|
format!(
|
||||||
|
":flag_{}: #{}",
|
||||||
|
u.country.to_lowercase(),
|
||||||
|
grouped_number(u.country_rank)
|
||||||
|
),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.field("Accuracy", format!("{:.2}%", u.accuracy), true)
|
.field("Accuracy", format!("{:.2}%", u.accuracy), true)
|
||||||
.field(
|
.field(
|
||||||
"Play count",
|
"Play count / Play time",
|
||||||
format!("{} (play time: {})", u.play_count, Duration(u.played_time)),
|
format!(
|
||||||
|
"{} ({})",
|
||||||
|
grouped_number(u.play_count),
|
||||||
|
Duration(u.played_time)
|
||||||
|
),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.field(
|
.field(
|
||||||
"Ranks",
|
"Ranks",
|
||||||
format!(
|
format!(
|
||||||
"{} SSH | {} SS | {} SH | {} S | {} A",
|
"**{}** SSH | **{}** SS | **{}** SH | **{}** S | **{}** A",
|
||||||
u.count_ssh, u.count_ss, u.count_sh, u.count_s, u.count_a
|
grouped_number(u.count_ssh),
|
||||||
|
grouped_number(u.count_ss),
|
||||||
|
grouped_number(u.count_sh),
|
||||||
|
grouped_number(u.count_s),
|
||||||
|
grouped_number(u.count_a)
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.field(
|
.field(
|
||||||
"Level",
|
format!("Level {:.0}", u.level),
|
||||||
format!(
|
format!(
|
||||||
"Level **{:.0}**: {} total score, {} ranked score",
|
"**{}** total score, **{}** ranked score",
|
||||||
u.level, u.total_score, u.ranked_score
|
grouped_number(u.total_score),
|
||||||
|
grouped_number(u.ranked_score)
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
@ -441,13 +376,14 @@ pub(crate) fn user_embed<'a>(
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
.push("on ")
|
.push("on ")
|
||||||
.push(format!(
|
.push_line(format!(
|
||||||
"[{} - {}]({})",
|
"[{} - {} [{}]]({})**{} **",
|
||||||
MessageBuilder::new().push_bold_safe(&map.artist).build(),
|
MessageBuilder::new().push_bold_safe(&map.artist).build(),
|
||||||
MessageBuilder::new().push_bold_safe(&map.title).build(),
|
MessageBuilder::new().push_bold_safe(&map.title).build(),
|
||||||
map.link()
|
map.difficulty_name,
|
||||||
|
map.link(),
|
||||||
|
v.mods
|
||||||
))
|
))
|
||||||
.push_line(format!(" [{}]", map.difficulty_name))
|
|
||||||
.push(format!(
|
.push(format!(
|
||||||
"{:.1}⭐ | `{}`",
|
"{:.1}⭐ | `{}`",
|
||||||
info.map(|i| i.stars as f64).unwrap_or(map.difficulty.stars),
|
info.map(|i| i.stars as f64).unwrap_or(map.difficulty.stars),
|
||||||
|
|
|
@ -20,13 +20,13 @@ use super::embeds::{beatmap_embed, beatmapset_embed};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref OLD_LINK_REGEX: Regex = Regex::new(
|
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]+))?"
|
r"(?:https?://)?osu\.ppy\.sh/(?P<link_type>s|b)/(?P<id>\d+)(?:[\&\?]m=(?P<mode>\d))?(?:\+(?P<mods>[A-Z]+))?"
|
||||||
).unwrap();
|
).unwrap();
|
||||||
static ref NEW_LINK_REGEX: Regex = Regex::new(
|
static ref NEW_LINK_REGEX: Regex = Regex::new(
|
||||||
r"https?://osu\.ppy\.sh/beatmapsets/(?P<set_id>\d+)/?(?:\#(?P<mode>osu|taiko|fruits|mania)(?:/(?P<beatmap_id>\d+)|/?))?(?:\+(?P<mods>[A-Z]+))?"
|
r"(?:https?://)?osu\.ppy\.sh/beatmapsets/(?P<set_id>\d+)/?(?:\#(?P<mode>osu|taiko|fruits|mania)(?:/(?P<beatmap_id>\d+)|/?))?(?:\+(?P<mods>[A-Z]+))?"
|
||||||
).unwrap();
|
).unwrap();
|
||||||
static ref SHORT_LINK_REGEX: Regex = Regex::new(
|
static ref SHORT_LINK_REGEX: Regex = Regex::new(
|
||||||
r"/b/(?P<id>\d+)(?:/(?P<mode>osu|taiko|fruits|mania))?(?:\+(?P<mods>[A-Z]+))?"
|
r"(?:^|\s)/b/(?P<id>\d+)(?:/(?P<mode>osu|taiko|fruits|mania))?(?:\+(?P<mods>[A-Z]+))?"
|
||||||
).unwrap();
|
).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,11 +122,6 @@ pub fn mania(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Mode);
|
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Mode);
|
||||||
|
|
||||||
impl BeatmapWithMode {
|
impl BeatmapWithMode {
|
||||||
/// Whether this beatmap-with-mode is a converted beatmap.
|
|
||||||
fn is_converted(&self) -> bool {
|
|
||||||
self.0.mode != self.1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mode(&self) -> Mode {
|
fn mode(&self) -> Mode {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@ use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use youmubot_prelude::Duration as YoumuDuration;
|
||||||
|
|
||||||
pub mod mods;
|
pub mod mods;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
pub(crate) mod raw;
|
pub(crate) mod raw;
|
||||||
|
|
||||||
pub use mods::Mods;
|
pub use mods::Mods;
|
||||||
|
use serenity::utils::MessageBuilder;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
pub enum ApprovalStatus {
|
pub enum ApprovalStatus {
|
||||||
|
@ -92,8 +94,11 @@ impl Difficulty {
|
||||||
}
|
}
|
||||||
/// Apply mods to the given difficulty.
|
/// Apply mods to the given difficulty.
|
||||||
/// Note that `stars`, `aim` and `speed` cannot be calculated from this alone.
|
/// Note that `stars`, `aim` and `speed` cannot be calculated from this alone.
|
||||||
pub fn apply_mods(&self, mods: Mods) -> Difficulty {
|
pub fn apply_mods(&self, mods: Mods, updated_stars: Option<f64>) -> Difficulty {
|
||||||
let mut diff = self.clone();
|
let mut diff = Difficulty {
|
||||||
|
stars: updated_stars.unwrap_or(self.stars),
|
||||||
|
..self.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// Apply mods one by one
|
// Apply mods one by one
|
||||||
if mods.contains(Mods::EZ) {
|
if mods.contains(Mods::EZ) {
|
||||||
|
@ -118,6 +123,58 @@ impl Difficulty {
|
||||||
|
|
||||||
diff
|
diff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format the difficulty info into a short summary.
|
||||||
|
pub fn format_info(&self, mode: Mode, mods: Mods, original_beatmap: &Beatmap) -> String {
|
||||||
|
let is_not_ranked = match original_beatmap.approval {
|
||||||
|
ApprovalStatus::Ranked(_) => false,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
let three_lines = is_not_ranked;
|
||||||
|
let bpm = (self.bpm * 100.0).round() / 100.0;
|
||||||
|
MessageBuilder::new()
|
||||||
|
.push(format!(
|
||||||
|
"[[Link]]({}) (`{}`)",
|
||||||
|
original_beatmap.link(),
|
||||||
|
original_beatmap.short_link(Some(mode), Some(mods))
|
||||||
|
))
|
||||||
|
.push(if three_lines { "\n" } else { ", " })
|
||||||
|
.push_bold(format!("{:.2}⭐", self.stars))
|
||||||
|
.push(", ")
|
||||||
|
.push(
|
||||||
|
original_beatmap
|
||||||
|
.difficulty
|
||||||
|
.max_combo
|
||||||
|
.map(|c| format!("max **{}x**, ", c))
|
||||||
|
.unwrap_or_else(|| "".to_owned()),
|
||||||
|
)
|
||||||
|
.push(if is_not_ranked {
|
||||||
|
format!("status **{}**, mode ", original_beatmap.approval)
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
})
|
||||||
|
.push_bold_line(format_mode(mode, original_beatmap.mode))
|
||||||
|
.push("CS")
|
||||||
|
.push_bold(format!("{:.1}", self.cs))
|
||||||
|
.push(", AR")
|
||||||
|
.push_bold(format!("{:.1}", self.ar))
|
||||||
|
.push(", OD")
|
||||||
|
.push_bold(format!("{:.1}", self.od))
|
||||||
|
.push(", HP")
|
||||||
|
.push_bold(format!("{:.1}", self.hp))
|
||||||
|
.push(format!(", BPM**{}**", bpm))
|
||||||
|
.push(", ⌛ ")
|
||||||
|
.push_bold(format!("{}", YoumuDuration(self.drain_length)))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_mode(actual: Mode, original: Mode) -> String {
|
||||||
|
if actual == original {
|
||||||
|
format!("{}", actual)
|
||||||
|
} else {
|
||||||
|
format!("{} (converted)", actual)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||||
|
@ -277,6 +334,18 @@ impl Beatmap {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a direct download link. If `bloodcat` is true, return the bloodcat download link.
|
||||||
|
pub fn download_link(&self, bloodcat: bool) -> String {
|
||||||
|
if bloodcat {
|
||||||
|
format!("https://bloodcat.com/osu/s/{}", self.beatmapset_id)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"https://osu.ppy.sh/beatmapsets/{}/download",
|
||||||
|
self.beatmapset_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a parsable short link.
|
/// Return a parsable short link.
|
||||||
pub fn short_link(&self, override_mode: Option<Mode>, mods: Option<Mods>) -> String {
|
pub fn short_link(&self, override_mode: Option<Mode>, mods: Option<Mods>) -> String {
|
||||||
format!(
|
format!(
|
||||||
|
|
Loading…
Add table
Reference in a new issue