Add short link references in embeds

This commit is contained in:
Natsu Kagami 2020-06-14 21:40:36 -04:00
parent 5c270db9cb
commit cafa65581c
Signed by: nki
GPG key ID: 73376E117CD20735
4 changed files with 140 additions and 27 deletions

View file

@ -109,7 +109,11 @@ pub fn beatmap_embed<'a>(
) )
}) })
.push_line(format!(" [[Beatmapset]]({})", b.beatmapset_link())) .push_line(format!(" [[Beatmapset]]({})", b.beatmapset_link()))
.push_line(&b.approval) .push_line(format!(
"Short link: `{}`",
b.short_link(Some(m), Some(mods))
))
.push_bold_line(&b.approval)
.push("Language: ") .push("Language: ")
.push_bold(&b.language) .push_bold(&b.language)
.push(" | Genre: ") .push(" | Genre: ")
@ -209,7 +213,11 @@ pub fn beatmapset_embed<'a>(
( (
format!("[{}]", b.difficulty_name), format!("[{}]", b.difficulty_name),
MessageBuilder::new() MessageBuilder::new()
.push(format!("[[Link]]({})", b.link())) .push(format!(
"[[Link]]({}) (`{}`)",
b.link(),
b.short_link(m, None)
))
.push(", ") .push(", ")
.push_bold(format!("{:.2}", b.difficulty.stars)) .push_bold(format!("{:.2}", b.difficulty.stars))
.push(", ") .push(", ")
@ -311,7 +319,11 @@ pub(crate) fn score_embed<'a>(
.field( .field(
"Map stats", "Map stats",
MessageBuilder::new() MessageBuilder::new()
.push(format!("[[Link]]({})", b.link())) .push(format!(
"[[Link]]({}) (`{}`)",
b.link(),
b.short_link(Some(mode), Some(s.mods))
))
.push(", ") .push(", ")
.push_bold(format!("{:.2}", stars)) .push_bold(format!("{:.2}", stars))
.push(", ") .push(", ")
@ -346,7 +358,7 @@ pub(crate) fn score_embed<'a>(
pub(crate) fn user_embed<'a>( pub(crate) fn user_embed<'a>(
u: User, u: User,
best: Option<(Score, BeatmapWithMode)>, best: Option<(Score, BeatmapWithMode, Option<BeatmapInfo>)>,
m: &'a mut CreateEmbed, m: &'a mut CreateEmbed,
) -> &'a mut CreateEmbed { ) -> &'a mut CreateEmbed {
m.title(u.username) m.title(u.username)
@ -388,8 +400,8 @@ pub(crate) fn user_embed<'a>(
), ),
false, false,
) )
.fields(best.map(|(v, map)| { .fields(best.map(|(v, map, info)| {
let map = map.0; let BeatmapWithMode(map, mode) = map;
( (
"Best Record", "Best Record",
MessageBuilder::new() MessageBuilder::new()
@ -413,8 +425,12 @@ pub(crate) fn user_embed<'a>(
MessageBuilder::new().push_bold_safe(&map.title).build(), MessageBuilder::new().push_bold_safe(&map.title).build(),
map.link() map.link()
)) ))
.push(format!(" [{}]", map.difficulty_name)) .push_line(format!(" [{}]", map.difficulty_name))
.push(format!(" ({:.1}⭐)", map.difficulty.stars)) .push(format!(
"{:.1}⭐ | `{}`",
info.map(|i| i.stars as f64).unwrap_or(map.difficulty.stars),
map.short_link(Some(mode), Some(v.mods))
))
.build(), .build(),
false, false,
) )

View file

@ -1,5 +1,6 @@
use super::OsuClient; use super::OsuClient;
use crate::{ use crate::{
discord::beatmap_cache::BeatmapMetaCache,
discord::oppai_cache::{BeatmapCache, BeatmapInfo}, discord::oppai_cache::{BeatmapCache, BeatmapInfo},
models::{Beatmap, Mode, Mods}, models::{Beatmap, Mode, Mods},
request::BeatmapRequestKind, request::BeatmapRequestKind,
@ -24,6 +25,9 @@ lazy_static! {
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(
r"/b/(?P<id>\d+)(?:/(?P<mode>osu|taiko|fruits|mania))?(?:\+(?P<mods>[A-Z]+))?"
).unwrap();
} }
pub fn hook(ctx: &mut Context, msg: &Message) -> () { pub fn hook(ctx: &mut Context, msg: &Message) -> () {
@ -33,8 +37,13 @@ pub fn hook(ctx: &mut Context, msg: &Message) -> () {
let mut v = move || -> CommandResult { let mut v = move || -> CommandResult {
let old_links = handle_old_links(ctx, &msg.content)?; let old_links = handle_old_links(ctx, &msg.content)?;
let new_links = handle_new_links(ctx, &msg.content)?; let new_links = handle_new_links(ctx, &msg.content)?;
let short_links = handle_short_links(ctx, &msg, &msg.content)?;
let mut last_beatmap = None; let mut last_beatmap = None;
for l in old_links.into_iter().chain(new_links.into_iter()) { for l in old_links
.into_iter()
.chain(new_links.into_iter())
.chain(short_links.into_iter())
{
if let Err(v) = msg.channel_id.send_message(&ctx, |m| match l.embed { if let Err(v) = msg.channel_id.send_message(&ctx, |m| match l.embed {
EmbedType::Beatmap(b, info, mods) => { EmbedType::Beatmap(b, info, mods) => {
let t = handle_beatmap(&b, info, l.link, l.mode, mods, m); let t = handle_beatmap(&b, info, l.link, l.mode, mods, m);
@ -137,15 +146,9 @@ fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPri
let mut to_prints: Vec<ToPrint<'a>> = Vec::new(); let mut to_prints: Vec<ToPrint<'a>> = Vec::new();
let cache = ctx.data.get_cloned::<BeatmapCache>(); let cache = ctx.data.get_cloned::<BeatmapCache>();
for capture in NEW_LINK_REGEX.captures_iter(content) { for capture in NEW_LINK_REGEX.captures_iter(content) {
let mode = capture.name("mode").and_then(|v| { let mode = capture
Some(match v.as_str() { .name("mode")
"osu" => Mode::Std, .and_then(|v| Mode::parse_from_new_site(v.as_str()));
"taiko" => Mode::Taiko,
"fruits" => Mode::Catch,
"mania" => Mode::Mania,
_ => return None,
})
});
let link = capture.get(0).unwrap().as_str(); let link = capture.get(0).unwrap().as_str();
let req = match capture.name("beatmap_id") { let req = match capture.name("beatmap_id") {
Some(ref v) => BeatmapRequestKind::Beatmap(v.as_str().parse()?), Some(ref v) => BeatmapRequestKind::Beatmap(v.as_str().parse()?),
@ -163,8 +166,7 @@ fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPri
// collect beatmap info // collect beatmap info
let mods = capture let mods = capture
.name("mods") .name("mods")
.map(|v| Mods::from_str(v.as_str()).ok()) .and_then(|v| Mods::from_str(v.as_str()).ok())
.flatten()
.unwrap_or(Mods::NOMOD); .unwrap_or(Mods::NOMOD);
let info = mode let info = mode
.unwrap_or(beatmap.mode) .unwrap_or(beatmap.mode)
@ -192,6 +194,55 @@ fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPri
Ok(to_prints) Ok(to_prints)
} }
fn handle_short_links<'a>(
ctx: &mut Context,
msg: &Message,
content: &'a str,
) -> Result<Vec<ToPrint<'a>>, Error> {
if let Some(guild_id) = msg.guild_id {
if announcer::announcer_of(ctx, crate::discord::announcer::ANNOUNCER_KEY, guild_id)?
!= Some(msg.channel_id)
{
// Disable if we are not in the server's announcer channel
return Ok(vec![]);
}
}
let osu = ctx.data.get_cloned::<BeatmapMetaCache>();
let cache = ctx.data.get_cloned::<BeatmapCache>();
Ok(SHORT_LINK_REGEX
.captures_iter(content)
.map(|capture| -> Result<_, Error> {
let mode = capture
.name("mode")
.and_then(|v| Mode::parse_from_new_site(v.as_str()));
let id: u64 = capture.name("id").unwrap().as_str().parse()?;
let beatmap = match mode {
Some(mode) => osu.get_beatmap(id, mode),
None => osu.get_beatmap_default(id),
}?;
let mods = capture
.name("mods")
.and_then(|v| Mods::from_str(v.as_str()).ok())
.unwrap_or(Mods::NOMOD);
let info = mode
.unwrap_or(beatmap.mode)
.to_oppai_mode()
.and_then(|mode| {
cache
.get_beatmap(beatmap.beatmap_id)
.and_then(|b| b.get_info_with(Some(mode), mods))
.ok()
});
Ok(ToPrint {
embed: EmbedType::Beatmap(beatmap, info, mods),
link: capture.get(0).unwrap().as_str(),
mode,
})
})
.filter_map(|v| v.ok())
.collect())
}
fn handle_beatmap<'a, 'b>( fn handle_beatmap<'a, 'b>(
beatmap: &Beatmap, beatmap: &Beatmap,
info: Option<BeatmapInfo>, info: Option<BeatmapInfo>,

View file

@ -272,8 +272,12 @@ fn list_plays(plays: &[Score], mode: Mode, ctx: Context, m: &Message) -> Command
.map(|info| info.stars as f64) .map(|info| info.stars as f64)
.unwrap_or(b.difficulty.stars); .unwrap_or(b.difficulty.stars);
format!( format!(
"[{:.1}*] {} - {} [{}] (#{})", "[{:.1}*] {} - {} [{}] ({})",
stars, b.artist, b.title, b.difficulty_name, b.beatmap_id stars,
b.artist,
b.title,
b.difficulty_name,
b.short_link(Some(mode), Some(plays[i].mods)),
) )
} else { } else {
"FETCH_FAILED".to_owned() "FETCH_FAILED".to_owned()
@ -554,18 +558,26 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
fn get_user(ctx: &mut Context, msg: &Message, mut args: Args, mode: Mode) -> CommandResult { fn get_user(ctx: &mut Context, msg: &Message, mut args: Args, mode: Mode) -> CommandResult {
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*ctx.data.read(), msg)?; let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*ctx.data.read(), msg)?;
let osu = ctx.data.get_cloned::<OsuClient>(); let osu = ctx.data.get_cloned::<OsuClient>();
let cache = ctx.data.get_cloned::<BeatmapMetaCache>();
let user = osu.user(user, |f| f.mode(mode))?; let user = osu.user(user, |f| f.mode(mode))?;
let oppai = ctx.data.get_cloned::<BeatmapCache>();
match user { match user {
Some(u) => { Some(u) => {
let best = osu let best = osu
.user_best(UserID::ID(u.id), |f| f.limit(1).mode(mode))? .user_best(UserID::ID(u.id), |f| f.limit(1).mode(mode))?
.into_iter() .into_iter()
.next() .next()
.map(|m| { .map(|m| -> Result<_, Error> {
osu.beatmaps(BeatmapRequestKind::Beatmap(m.beatmap_id), |f| { let beatmap = cache.get_beatmap(m.beatmap_id, mode)?;
f.mode(mode, true) let info = mode
}) .to_oppai_mode()
.map(|map| (m, BeatmapWithMode(map.into_iter().next().unwrap(), mode))) .map(|mode| -> Result<_, Error> {
Ok(oppai
.get_beatmap(m.beatmap_id)?
.get_info_with(Some(mode), m.mods)?)
})
.transpose()?;
Ok((m, BeatmapWithMode(beatmap, mode), info))
}) })
.transpose()?; .transpose()?;
msg.channel_id.send_message(&ctx, |m| { msg.channel_id.send_message(&ctx, |m| {

View file

@ -198,6 +198,27 @@ impl Mode {
_ => return None, _ => return None,
}) })
} }
/// Parse from the new site's convention.
pub fn parse_from_new_site(s: &str) -> Option<Self> {
Some(match s {
"osu" => Mode::Std,
"taiko" => Mode::Taiko,
"fruits" => Mode::Catch,
"mania" => Mode::Mania,
_ => return None,
})
}
/// Returns the mode string in the new convention.
pub fn to_str_new_site(&self) -> &'static str {
match self {
Mode::Std => "osu",
Mode::Taiko => "taiko",
Mode::Catch => "fruits",
Mode::Mania => "mania",
}
}
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -249,6 +270,19 @@ impl Beatmap {
) )
} }
/// Return a parsable short link.
pub fn short_link(&self, override_mode: Option<Mode>, mods: Option<Mods>) -> String {
format!(
"/b/{}{}{}",
self.beatmap_id,
match override_mode {
Some(mode) if mode != self.mode => format!("/{}", mode.to_str_new_site()),
_ => "".to_owned(),
},
mods.map(|m| format!("{}", m)).unwrap_or("".to_owned()),
)
}
/// Link to the cover image of the beatmap. /// Link to the cover image of the beatmap.
pub fn cover_url(&self) -> String { pub fn cover_url(&self) -> String {
format!( format!(