osu: Streamify hook

This commit is contained in:
Natsu Kagami 2020-09-07 14:16:13 -04:00
parent 26074ae257
commit da2997d8f9
Signed by: nki
GPG key ID: 73376E117CD20735

View file

@ -7,12 +7,7 @@ use crate::{
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use serenity::{ use serenity::{builder::CreateMessage, model::channel::Message, utils::MessageBuilder};
builder::CreateMessage,
framework::standard::{CommandError as Error, CommandResult},
model::channel::Message,
utils::MessageBuilder,
};
use std::str::FromStr; use std::str::FromStr;
use youmubot_prelude::*; use youmubot_prelude::*;
@ -30,43 +25,49 @@ lazy_static! {
).unwrap(); ).unwrap();
} }
pub fn hook(ctx: &mut Context, msg: &Message) -> () { pub async fn hook(ctx: &Context, msg: &Message) -> Result<()> {
if msg.author.bot { if msg.author.bot {
return; return Ok(());
} }
let mut v = move || -> CommandResult { let (old_links, new_links, short_links) = (
let old_links = handle_old_links(ctx, &msg.content)?; handle_old_links(ctx, &msg.content),
let new_links = handle_new_links(ctx, &msg.content)?; handle_new_links(ctx, &msg.content),
let short_links = handle_short_links(ctx, &msg, &msg.content)?; handle_short_links(ctx, &msg, &msg.content),
let mut last_beatmap = None; );
for l in old_links let last_beatmap = stream::select(old_links, stream::select(new_links, short_links))
.into_iter() .then(|l| async move {
.chain(new_links.into_iter()) let mut bm: Option<super::BeatmapWithMode> = None;
.chain(short_links.into_iter()) 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); let mode = l.mode.unwrap_or(b.mode);
let mode = l.mode.unwrap_or(b.mode); bm = Some(super::BeatmapWithMode(b, mode));
last_beatmap = Some(super::BeatmapWithMode(b, mode)); t
t }
EmbedType::Beatmapset(b) => handle_beatmapset(b, l.link, l.mode, m),
})
.await?;
let r: Result<_> = Ok(bm);
r
})
.filter_map(|v| async move {
match v {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
None
} }
EmbedType::Beatmapset(b) => handle_beatmapset(b, l.link, l.mode, m),
}) {
println!("Error in osu! hook: {:?}", v)
} }
} })
// Save the beatmap for query later. .fold(None, |_, v| async move { Some(v) })
if let Some(t) = last_beatmap { .await;
if let Err(v) = super::cache::save_beatmap(&*ctx.data.read(), msg.channel_id, &t) {
dbg!(v); // Save the beatmap for query later.
} if let Some(t) = last_beatmap {
} super::cache::save_beatmap(&*ctx.data.read().await, msg.channel_id, &t)?;
Ok(())
};
if let Err(v) = v() {
println!("Error in osu! hook: {:?}", v)
} }
Ok(())
} }
enum EmbedType { enum EmbedType {
@ -80,37 +81,47 @@ struct ToPrint<'a> {
mode: Option<Mode>, mode: Option<Mode>,
} }
fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPrint<'a>>, Error> { fn handle_old_links<'a>(
let osu = ctx.data.get_cloned::<OsuClient>(); ctx: &'a Context,
let mut to_prints: Vec<ToPrint<'a>> = Vec::new(); content: &'a str,
let cache = ctx.data.get_cloned::<BeatmapCache>(); ) -> impl stream::Stream<Item = ToPrint<'a>> + 'a {
for capture in OLD_LINK_REGEX.captures_iter(content) { OLD_LINK_REGEX
let req_type = capture.name("link_type").unwrap().as_str(); .captures_iter(content)
let req = match req_type { .map(move |capture| async move {
"b" => BeatmapRequestKind::Beatmap(capture["id"].parse()?), let data = ctx.data.read().await;
"s" => BeatmapRequestKind::Beatmapset(capture["id"].parse()?), let osu = data.get::<OsuClient>().unwrap();
_ => continue, let cache = data.get::<BeatmapCache>().unwrap();
}; let req_type = capture.name("link_type").unwrap().as_str();
let mode = capture let req = match req_type {
.name("mode") "b" => BeatmapRequestKind::Beatmap(capture["id"].parse()?),
.map(|v| v.as_str().parse()) "s" => BeatmapRequestKind::Beatmapset(capture["id"].parse()?),
.transpose()? _ => unreachable!(),
.and_then(|v| { };
Some(match v { let mode = capture
0 => Mode::Std, .name("mode")
1 => Mode::Taiko, .map(|v| v.as_str().parse())
2 => Mode::Catch, .transpose()?
3 => Mode::Mania, .and_then(|v| {
_ => return None, Some(match v {
0 => Mode::Std,
1 => Mode::Taiko,
2 => Mode::Catch,
3 => Mode::Mania,
_ => return None,
})
});
let beatmaps = osu
.beatmaps(req, |v| match mode {
Some(m) => v.mode(m, true),
None => v,
}) })
}); .await?;
let beatmaps = osu.beatmaps(req, |v| match mode { if beatmaps.is_empty() {
Some(m) => v.mode(m, true), return Ok(None);
None => v, }
})?; let r: Result<_> = Ok(match req_type {
match req_type { "b" => {
"b" => { let b = beatmaps.into_iter().next().unwrap();
for b in beatmaps.into_iter() {
// collect beatmap info // collect beatmap info
let mods = capture let mods = capture
.name("mods") .name("mods")
@ -123,46 +134,65 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPri
.and_then(|b| b.get_info_with(Some(mode), mods)) .and_then(|b| b.get_info_with(Some(mode), mods))
.ok() .ok()
}); });
to_prints.push(ToPrint { Some(ToPrint {
embed: EmbedType::Beatmap(b, info, mods), embed: EmbedType::Beatmap(b, info, mods),
link: capture.get(0).unwrap().as_str(), link: capture.get(0).unwrap().as_str(),
mode, mode,
}) })
} }
} "s" => Some(ToPrint {
"s" => to_prints.push(ToPrint { embed: EmbedType::Beatmapset(beatmaps),
embed: EmbedType::Beatmapset(beatmaps), link: capture.get(0).unwrap().as_str(),
link: capture.get(0).unwrap().as_str(), mode,
mode, }),
}), _ => None,
_ => (), });
} r
} })
Ok(to_prints) .collect::<stream::FuturesUnordered<_>>()
.filter_map(|v| {
future::ready(match v {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
None
}
})
})
} }
fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPrint<'a>>, Error> { fn handle_new_links<'a>(
let osu = ctx.data.get_cloned::<OsuClient>(); ctx: &'a Context,
let mut to_prints: Vec<ToPrint<'a>> = Vec::new(); content: &'a str,
let cache = ctx.data.get_cloned::<BeatmapCache>(); ) -> impl stream::Stream<Item = ToPrint<'a>> + 'a {
for capture in NEW_LINK_REGEX.captures_iter(content) { NEW_LINK_REGEX
let mode = capture .captures_iter(content)
.name("mode") .map(|capture| async move {
.and_then(|v| Mode::parse_from_new_site(v.as_str())); let data = ctx.data.read().await;
let link = capture.get(0).unwrap().as_str(); let osu = data.get::<OsuClient>().unwrap();
let req = match capture.name("beatmap_id") { let cache = data.get::<BeatmapCache>().unwrap();
Some(ref v) => BeatmapRequestKind::Beatmap(v.as_str().parse()?), let mode = capture
None => { .name("mode")
BeatmapRequestKind::Beatmapset(capture.name("set_id").unwrap().as_str().parse()?) .and_then(|v| Mode::parse_from_new_site(v.as_str()));
let link = capture.get(0).unwrap().as_str();
let req = match capture.name("beatmap_id") {
Some(ref v) => BeatmapRequestKind::Beatmap(v.as_str().parse()?),
None => BeatmapRequestKind::Beatmapset(
capture.name("set_id").unwrap().as_str().parse()?,
),
};
let beatmaps = osu
.beatmaps(req, |v| match mode {
Some(m) => v.mode(m, true),
None => v,
})
.await?;
if beatmaps.is_empty() {
return Ok(None);
} }
}; let r: Result<_> = Ok(match capture.name("beatmap_id") {
let beatmaps = osu.beatmaps(req, |v| match mode { Some(_) => {
Some(m) => v.mode(m, true), let beatmap = beatmaps.into_iter().next().unwrap();
None => v,
})?;
match capture.name("beatmap_id") {
Some(_) => {
for beatmap in beatmaps.into_iter() {
// collect beatmap info // collect beatmap info
let mods = capture let mods = capture
.name("mods") .name("mods")
@ -177,48 +207,59 @@ fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPri
.and_then(|b| b.get_info_with(Some(mode), mods)) .and_then(|b| b.get_info_with(Some(mode), mods))
.ok() .ok()
}); });
to_prints.push(ToPrint { Some(ToPrint {
embed: EmbedType::Beatmap(beatmap, info, mods), embed: EmbedType::Beatmap(beatmap, info, mods),
link, link,
mode, mode,
}) })
} }
} None => Some(ToPrint {
None => to_prints.push(ToPrint { embed: EmbedType::Beatmapset(beatmaps),
embed: EmbedType::Beatmapset(beatmaps), link,
link, mode,
mode, }),
}), });
} r
} })
Ok(to_prints) .collect::<stream::FuturesUnordered<_>>()
.filter_map(|v| {
future::ready(match v {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
None
}
})
})
} }
fn handle_short_links<'a>( fn handle_short_links<'a>(
ctx: &mut Context, ctx: &'a Context,
msg: &Message, msg: &'a Message,
content: &'a str, content: &'a str,
) -> Result<Vec<ToPrint<'a>>, Error> { ) -> impl stream::Stream<Item = ToPrint<'a>> + 'a {
if let Some(guild_id) = msg.guild_id { SHORT_LINK_REGEX
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) .captures_iter(content)
.map(|capture| -> Result<_, Error> { .map(|capture| async move {
if let Some(guild_id) = msg.guild_id {
if announcer::announcer_of(ctx, crate::discord::announcer::ANNOUNCER_KEY, guild_id)
.await?
!= Some(msg.channel_id)
{
// Disable if we are not in the server's announcer channel
return Err(Error::msg("not in server announcer channel"));
}
}
let data = ctx.data.read().await;
let osu = data.get::<BeatmapMetaCache>().unwrap();
let cache = data.get::<BeatmapCache>().unwrap();
let mode = capture let mode = capture
.name("mode") .name("mode")
.and_then(|v| Mode::parse_from_new_site(v.as_str())); .and_then(|v| Mode::parse_from_new_site(v.as_str()));
let id: u64 = capture.name("id").unwrap().as_str().parse()?; let id: u64 = capture.name("id").unwrap().as_str().parse()?;
let beatmap = match mode { let beatmap = match mode {
Some(mode) => osu.get_beatmap(id, mode), Some(mode) => osu.get_beatmap(id, mode).await,
None => osu.get_beatmap_default(id), None => osu.get_beatmap_default(id).await,
}?; }?;
let mods = capture let mods = capture
.name("mods") .name("mods")
@ -233,14 +274,23 @@ fn handle_short_links<'a>(
.and_then(|b| b.get_info_with(Some(mode), mods)) .and_then(|b| b.get_info_with(Some(mode), mods))
.ok() .ok()
}); });
Ok(ToPrint { let r: Result<_> = Ok(ToPrint {
embed: EmbedType::Beatmap(beatmap, info, mods), embed: EmbedType::Beatmap(beatmap, info, mods),
link: capture.get(0).unwrap().as_str(), link: capture.get(0).unwrap().as_str(),
mode, mode,
});
r
})
.collect::<stream::FuturesUnordered<_>>()
.filter_map(|v| {
future::ready(match v {
Ok(v) => Some(v),
Err(e) => {
eprintln!("{}", e);
None
}
}) })
}) })
.filter_map(|v| v.ok())
.collect())
} }
fn handle_beatmap<'a, 'b>( fn handle_beatmap<'a, 'b>(