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);
last_beatmap = Some(super::BeatmapWithMode(b, mode)); bm = Some(super::BeatmapWithMode(b, mode));
t t
} }
EmbedType::Beatmapset(b) => handle_beatmapset(b, l.link, l.mode, m), EmbedType::Beatmapset(b) => handle_beatmapset(b, l.link, l.mode, m),
}) { })
println!("Error in osu! hook: {:?}", v) .await?;
let r: Result<_> = Ok(bm);
r
})
.filter_map(|v| async move {
match v {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
None
} }
} }
})
.fold(None, |_, v| async move { Some(v) })
.await;
// Save the beatmap for query later. // Save the beatmap for query later.
if let Some(t) = last_beatmap { if let Some(t) = last_beatmap {
if let Err(v) = super::cache::save_beatmap(&*ctx.data.read(), msg.channel_id, &t) { super::cache::save_beatmap(&*ctx.data.read().await, msg.channel_id, &t)?;
dbg!(v);
}
} }
Ok(()) Ok(())
};
if let Err(v) = v() {
println!("Error in osu! hook: {:?}", v)
}
} }
enum EmbedType { enum EmbedType {
@ -80,16 +81,21 @@ 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
.captures_iter(content)
.map(move |capture| async move {
let data = ctx.data.read().await;
let osu = data.get::<OsuClient>().unwrap();
let cache = data.get::<BeatmapCache>().unwrap();
let req_type = capture.name("link_type").unwrap().as_str(); let req_type = capture.name("link_type").unwrap().as_str();
let req = match req_type { let req = match req_type {
"b" => BeatmapRequestKind::Beatmap(capture["id"].parse()?), "b" => BeatmapRequestKind::Beatmap(capture["id"].parse()?),
"s" => BeatmapRequestKind::Beatmapset(capture["id"].parse()?), "s" => BeatmapRequestKind::Beatmapset(capture["id"].parse()?),
_ => continue, _ => unreachable!(),
}; };
let mode = capture let mode = capture
.name("mode") .name("mode")
@ -104,13 +110,18 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPri
_ => return None, _ => return None,
}) })
}); });
let beatmaps = osu.beatmaps(req, |v| match mode { let beatmaps = osu
.beatmaps(req, |v| match mode {
Some(m) => v.mode(m, true), Some(m) => v.mode(m, true),
None => v, None => v,
})?; })
match req_type { .await?;
if beatmaps.is_empty() {
return Ok(None);
}
let r: Result<_> = Ok(match req_type {
"b" => { "b" => {
for b in beatmaps.into_iter() { let b = beatmaps.into_iter().next().unwrap();
// 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
})
.collect::<stream::FuturesUnordered<_>>()
.filter_map(|v| {
future::ready(match v {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
None
} }
} })
Ok(to_prints) })
} }
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
.captures_iter(content)
.map(|capture| async move {
let data = ctx.data.read().await;
let osu = data.get::<OsuClient>().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 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()?),
None => { None => BeatmapRequestKind::Beatmapset(
BeatmapRequestKind::Beatmapset(capture.name("set_id").unwrap().as_str().parse()?) capture.name("set_id").unwrap().as_str().parse()?,
} ),
}; };
let beatmaps = osu.beatmaps(req, |v| match mode { let beatmaps = osu
.beatmaps(req, |v| match mode {
Some(m) => v.mode(m, true), Some(m) => v.mode(m, true),
None => v, None => v,
})?; })
match capture.name("beatmap_id") { .await?;
if beatmaps.is_empty() {
return Ok(None);
}
let r: Result<_> = Ok(match capture.name("beatmap_id") {
Some(_) => { Some(_) => {
for beatmap in beatmaps.into_iter() { let beatmap = beatmaps.into_iter().next().unwrap();
// 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
})
.collect::<stream::FuturesUnordered<_>>()
.filter_map(|v| {
future::ready(match v {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
None
} }
} })
Ok(to_prints) })
} }
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 {
SHORT_LINK_REGEX
.captures_iter(content)
.map(|capture| async move {
if let Some(guild_id) = msg.guild_id { if let Some(guild_id) = msg.guild_id {
if announcer::announcer_of(ctx, crate::discord::announcer::ANNOUNCER_KEY, guild_id)? if announcer::announcer_of(ctx, crate::discord::announcer::ANNOUNCER_KEY, guild_id)
.await?
!= Some(msg.channel_id) != Some(msg.channel_id)
{ {
// Disable if we are not in the server's announcer channel // Disable if we are not in the server's announcer channel
return Ok(vec![]); return Err(Error::msg("not in server announcer channel"));
} }
} }
let osu = ctx.data.get_cloned::<BeatmapMetaCache>(); let data = ctx.data.read().await;
let cache = ctx.data.get_cloned::<BeatmapCache>(); let osu = data.get::<BeatmapMetaCache>().unwrap();
Ok(SHORT_LINK_REGEX let cache = data.get::<BeatmapCache>().unwrap();
.captures_iter(content)
.map(|capture| -> Result<_, Error> {
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>(