osu! announcer

This commit is contained in:
Natsu Kagami 2020-01-17 03:50:28 -05:00
parent 2820b025b8
commit 33fd152331
6 changed files with 113 additions and 4 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
target target
.env .env
*.ron *.toml
cargo-remote cargo-remote

View file

@ -20,6 +20,8 @@ pub use community::COMMUNITY_GROUP;
pub use fun::FUN_GROUP; pub use fun::FUN_GROUP;
pub use osu::OSU_GROUP; pub use osu::OSU_GROUP;
pub use announcer::Announcer;
// A help command // A help command
#[help] #[help]
pub fn help( pub fn help(

View file

@ -0,0 +1,96 @@
use super::{embeds::score_embed, BeatmapWithMode};
use crate::{
commands::announcer::Announcer,
db::{OsuSavedUsers, OsuUser},
http::{Osu, HTTP},
};
use reqwest::blocking::Client as HTTPClient;
use serenity::{
framework::standard::{CommandError as Error, CommandResult},
http::Http,
model::{
id::{ChannelId, UserId},
misc::Mentionable,
},
prelude::ShareMap,
};
use youmubot_osu::{
models::{Mode, Score},
request::{BeatmapRequestKind, UserID},
Client as OsuClient,
};
/// Announce osu! top scores.
pub struct OsuAnnouncer;
impl Announcer for OsuAnnouncer {
fn announcer_key() -> &'static str {
"osu"
}
fn send_messages(
c: &Http,
d: &mut ShareMap,
channels: impl Fn(UserId) -> Vec<ChannelId>,
) -> CommandResult {
let http = d.get::<HTTP>().expect("HTTP");
let osu = d.get::<Osu>().expect("osu!client");
// For each user...
let mut data = d
.get::<OsuSavedUsers>()
.expect("DB initialized")
.read(|f| f.clone())?;
for (user_id, osu_user) in data.iter_mut() {
let mut user = None;
for mode in &[Mode::Std, Mode::Taiko, Mode::Mania, Mode::Catch] {
let scores = OsuAnnouncer::scan_user(http, osu, osu_user, *mode)?;
if scores.is_empty() {
continue;
}
let user = user.get_or_insert_with(|| {
osu.user(http, UserID::ID(osu_user.id), |f| f)
.unwrap()
.unwrap()
});
for (rank, score) in scores {
let beatmap = BeatmapWithMode(
osu.beatmaps(http, BeatmapRequestKind::Beatmap(score.beatmap_id), |f| f)?
.into_iter()
.next()
.unwrap(),
*mode,
);
for channel in channels(*user_id) {
channel.send_message(c, |c| {
c.content(format!("New top record from {}!", user_id.mention()))
.embed(|e| score_embed(&score, &beatmap, &user, Some(rank), e))
})?;
}
}
}
osu_user.last_update = chrono::Utc::now();
}
// Update users
let f = d.get_mut::<OsuSavedUsers>().expect("DB initialized");
f.write(|f| *f = data)?;
f.save()?;
Ok(())
}
}
impl OsuAnnouncer {
fn scan_user(
http: &HTTPClient,
osu: &OsuClient,
u: &OsuUser,
mode: Mode,
) -> Result<Vec<(u8, Score)>, Error> {
let scores = osu.user_best(http, UserID::ID(u.id), |f| f.mode(mode).limit(25))?;
let scores = scores
.into_iter()
.filter(|s: &Score| s.date >= u.last_update)
.enumerate()
.map(|(i, v)| (i as u8, v))
.collect();
Ok(scores)
}
}

View file

@ -21,6 +21,7 @@ mod cache;
pub(crate) mod embeds; pub(crate) mod embeds;
mod hook; mod hook;
pub use announcer::OsuAnnouncer;
use embeds::{beatmap_embed, score_embed, user_embed}; use embeds::{beatmap_embed, score_embed, user_embed};
pub use hook::hook; pub use hook::hook;

View file

@ -33,6 +33,9 @@ where
} }
} }
/// A map from announcer keys to guild IDs and to channels.
pub type AnnouncerChannels = DB<HashMap<String, GuildMap<ChannelId>>>;
/// A list of SoftBans for all servers. /// A list of SoftBans for all servers.
pub type SoftBans = DB<GuildMap<ServerSoftBans>>; pub type SoftBans = DB<GuildMap<ServerSoftBans>>;
@ -49,9 +52,10 @@ pub fn setup_db(client: &mut Client) -> Result<(), Error> {
PathBuf::from("data") PathBuf::from("data")
}); });
let mut data = client.data.write(); let mut data = client.data.write();
SoftBans::insert_into(&mut *data, &path.join("soft_bans.ron"))?; SoftBans::insert_into(&mut *data, &path.join("soft_bans.toml"))?;
OsuSavedUsers::insert_into(&mut *data, &path.join("osu_saved_users.ron"))?; OsuSavedUsers::insert_into(&mut *data, &path.join("osu_saved_users.toml"))?;
OsuLastBeatmap::insert_into(&mut *data, &path.join("last_beatmaps.ron"))?; OsuLastBeatmap::insert_into(&mut *data, &path.join("last_beatmaps.toml"))?;
AnnouncerChannels::insert_into(&mut *data, &path.join("announcers.toml"))?;
Ok(()) Ok(())
} }

View file

@ -12,6 +12,9 @@ mod commands;
mod db; mod db;
mod http; mod http;
use commands::osu::OsuAnnouncer;
use commands::Announcer;
const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [commands::osu::hook]; const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [commands::osu::hook];
struct Handler; struct Handler;
@ -54,6 +57,9 @@ fn main() {
// Create handler threads // Create handler threads
std::thread::spawn(commands::admin::watch_soft_bans(&mut client)); std::thread::spawn(commands::admin::watch_soft_bans(&mut client));
// Announcers
OsuAnnouncer::scan(&client, std::time::Duration::from_secs(60));
println!("Starting..."); println!("Starting...");
if let Err(v) = client.start() { if let Err(v) = client.start() {
panic!(v) panic!(v)