diff --git a/youmubot-osu/src/discord/embeds.rs b/youmubot-osu/src/discord/embeds.rs index 256b0c2..1e2c510 100644 --- a/youmubot-osu/src/discord/embeds.rs +++ b/youmubot-osu/src/discord/embeds.rs @@ -2,6 +2,7 @@ use super::BeatmapWithMode; use crate::{ discord::oppai_cache::{Accuracy, BeatmapContent, BeatmapInfo, BeatmapInfoWithPP}, models::{Beatmap, Difficulty, Mode, Mods, Rank, Score, User}, + UserHeader, }; use serenity::{ all::CreateAttachment, @@ -243,7 +244,7 @@ pub(crate) struct ScoreEmbedBuilder<'a> { s: &'a Score, bm: &'a BeatmapWithMode, content: &'a BeatmapContent, - u: &'a User, + u: UserHeader, top_record: Option, world_record: Option, footer: Option, @@ -268,13 +269,13 @@ pub(crate) fn score_embed<'a>( s: &'a Score, bm: &'a BeatmapWithMode, content: &'a BeatmapContent, - u: &'a User, + u: impl Into, ) -> ScoreEmbedBuilder<'a> { ScoreEmbedBuilder { s, bm, content, - u, + u: u.into(), top_record: None, world_record: None, footer: None, @@ -288,7 +289,7 @@ impl<'a> ScoreEmbedBuilder<'a> { let b = &self.bm.0; let s = self.s; let content = self.content; - let u = self.u; + let u = &self.u; let accuracy = s.accuracy(mode); let info = content.get_info_with(mode, s.mods).ok(); let stars = info diff --git a/youmubot-osu/src/discord/hook.rs b/youmubot-osu/src/discord/hook.rs index 80fbb39..99da795 100644 --- a/youmubot-osu/src/discord/hook.rs +++ b/youmubot-osu/src/discord/hook.rs @@ -1,15 +1,22 @@ use std::str::FromStr; use std::sync::Arc; +use futures_util::stream::FuturesOrdered; +use itertools::Itertools; use lazy_static::lazy_static; use pagination::paginate_from_fn; use regex::Regex; -use serenity::all::EditMessage; -use serenity::{builder::CreateMessage, model::channel::Message, utils::MessageBuilder}; +use serenity::{ + all::{EditMessage, EMBED_MAX_COUNT}, + builder::CreateMessage, + model::channel::Message, + utils::MessageBuilder, +}; use youmubot_prelude::*; -use crate::discord::OsuEnv; +use crate::discord::embeds::score_embed; +use crate::discord::{BeatmapWithMode, OsuEnv}; use crate::{ discord::oppai_cache::BeatmapInfoWithPP, models::{Beatmap, Mode, Mods}, @@ -18,6 +25,7 @@ use crate::{ use super::embeds::beatmap_embed; lazy_static! { + // Beatmap(set) hooks pub(crate) static ref OLD_LINK_REGEX: Regex = Regex::new( r"(?:https?://)?osu\.ppy\.sh/(?Ps|b)/(?P\d+)(?:[\&\?]m=(?P\d))?(?:\+(?P[A-Z]+))?" ).unwrap(); @@ -27,8 +35,81 @@ lazy_static! { pub(crate) static ref SHORT_LINK_REGEX: Regex = Regex::new( r"(?:^|\s|\W)(?P
/b/(?P\d+)(?:/(?Posu|taiko|fruits|mania))?(?:\+(?P[A-Z]+))?)" ).unwrap(); + + // Score hook + pub(crate) static ref SCORE_LINK_REGEX: Regex = Regex::new( + r"(?:https?://)?osu\.ppy\.sh/scores/(?P\d+)" + ).unwrap(); } +/// React to /scores/{id} links. +pub fn score_hook<'a>( + ctx: &'a Context, + msg: &'a Message, +) -> std::pin::Pin> + Send + 'a>> { + Box::pin(async move { + if msg.author.bot { + return Ok(()); + } + + let env = { + let data = ctx.data.read().await; + data.get::().unwrap().clone() + }; + + let scores = SCORE_LINK_REGEX + .captures_iter(&msg.content) + .filter_map(|caps| caps.name("score_id")) + .filter_map(|score_id| score_id.as_str().parse::().ok()) + .map(|id| env.client.score(id)) + .collect::>() + .collect::>() + .await + .into_iter() + .filter_map(|score| score.pls_ok().flatten()); + + let embed_chunks = scores + .map(|score| async move { + let env = { + let data = ctx.data.read().await; + data.get::().unwrap().clone() + }; + let bm = env + .beatmaps + .get_beatmap(score.beatmap_id, score.mode) + .await?; + let content = env.oppai.get_beatmap(score.beatmap_id).await?; + let header = env.client.user_header(score.user_id).await?.unwrap(); + Ok(score_embed(&score, &BeatmapWithMode(bm, score.mode), &content, header).build()) + as Result<_> + }) + .collect::>() + .collect::>() + .await + .into_iter() + .filter_map(|v| v.pls_ok()) + .chunks(EMBED_MAX_COUNT) + .into_iter() + .map(|chunk| chunk.collect::>()) + .collect::>(); + + for embeds in embed_chunks { + msg.channel_id + .send_message( + &ctx, + CreateMessage::new() + .reference_message(msg) + .content("Here are the scores mentioned in the message!") + .embeds(embeds), + ) + .await + .pls_ok(); + } + Ok(()) + }) +} + +/// React to .osz and .osu uploads. pub fn dot_osu_hook<'a>( ctx: &'a Context, msg: &'a Message, diff --git a/youmubot-osu/src/discord/mod.rs b/youmubot-osu/src/discord/mod.rs index 3c1a32b..be4ffc0 100644 --- a/youmubot-osu/src/discord/mod.rs +++ b/youmubot-osu/src/discord/mod.rs @@ -16,7 +16,7 @@ use serenity::{ use db::{OsuLastBeatmap, OsuSavedUsers, OsuUser, OsuUserBests}; use embeds::{beatmap_embed, score_embed, user_embed}; use hook::SHORT_LINK_REGEX; -pub use hook::{dot_osu_hook, hook}; +pub use hook::{dot_osu_hook, hook, score_hook}; use server_rank::{SERVER_RANK_COMMAND, SHOW_LEADERBOARD_COMMAND}; use youmubot_prelude::announcer::AnnouncerHandler; use youmubot_prelude::{stream::FuturesUnordered, *}; @@ -44,7 +44,7 @@ mod server_rank; pub(crate) struct OsuClient; impl TypeMapKey for OsuClient { - type Value = Arc; + type Value = Arc; } /// The environment for osu! app commands. @@ -56,7 +56,7 @@ pub struct OsuEnv { pub(crate) last_beatmaps: OsuLastBeatmap, pub(crate) user_bests: OsuUserBests, // clients - pub(crate) client: Arc, + pub(crate) client: Arc, pub(crate) oppai: BeatmapCache, pub(crate) beatmaps: BeatmapMetaCache, }