From 8c34c7a3ba7b9c56c9ce541535f12198dcffac8a Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sun, 2 Feb 2020 15:47:21 -0500 Subject: [PATCH 1/8] Make OsuClient clone-able --- youmubot-osu/src/lib.rs | 36 +++++++------- youmubot/src/commands/osu/announcer.rs | 33 +++++++------ youmubot/src/commands/osu/hook.rs | 11 ++--- youmubot/src/commands/osu/mod.rs | 67 +++++++++++--------------- youmubot/src/main.rs | 4 +- 5 files changed, 70 insertions(+), 81 deletions(-) diff --git a/youmubot-osu/src/lib.rs b/youmubot-osu/src/lib.rs index 38c76b0..fa62543 100644 --- a/youmubot-osu/src/lib.rs +++ b/youmubot-osu/src/lib.rs @@ -10,11 +10,14 @@ use request::builders::*; use request::*; use reqwest::blocking::{Client as HTTPClient, RequestBuilder, Response}; use serenity::framework::standard::CommandError as Error; -use std::convert::TryInto; +use std::{convert::TryInto, sync::Arc}; /// Client is the client that will perform calls to the osu! api server. +/// It's cheap to clone, so do it. +#[derive(Clone)] pub struct Client { - key: String, + key: Arc, + client: HTTPClient, } fn vec_try_into>(v: Vec) -> Result, T::Error> { @@ -29,52 +32,50 @@ fn vec_try_into>(v: Vec) -> Result, T:: impl Client { /// Create a new client from the given API key. - pub fn new(key: impl AsRef) -> Client { + pub fn new(http_client: HTTPClient, key: String) -> Client { Client { - key: key.as_ref().to_string(), + key: Arc::new(key), + client: http_client, } } - fn build_request(&self, c: &HTTPClient, r: RequestBuilder) -> Result { - let v = r.query(&[("k", &self.key)]).build()?; + fn build_request(&self, r: RequestBuilder) -> Result { + let v = r.query(&[("k", &*self.key)]).build()?; dbg!(v.url()); - Ok(c.execute(v)?) + Ok(self.client.execute(v)?) } pub fn beatmaps( &self, - client: &HTTPClient, kind: BeatmapRequestKind, f: impl FnOnce(&mut BeatmapRequestBuilder) -> &mut BeatmapRequestBuilder, ) -> Result, Error> { let mut r = BeatmapRequestBuilder::new(kind); f(&mut r); - let res: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; Ok(vec_try_into(res)?) } pub fn user( &self, - client: &HTTPClient, user: UserID, f: impl FnOnce(&mut UserRequestBuilder) -> &mut UserRequestBuilder, ) -> Result, Error> { let mut r = UserRequestBuilder::new(user); f(&mut r); - let res: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; let res = vec_try_into(res)?; Ok(res.into_iter().next()) } pub fn scores( &self, - client: &HTTPClient, beatmap_id: u64, f: impl FnOnce(&mut ScoreRequestBuilder) -> &mut ScoreRequestBuilder, ) -> Result, Error> { let mut r = ScoreRequestBuilder::new(beatmap_id); f(&mut r); - let res: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; let mut res: Vec = vec_try_into(res)?; // with a scores request you need to fill the beatmap ids yourself @@ -86,32 +87,29 @@ impl Client { pub fn user_best( &self, - client: &HTTPClient, user: UserID, f: impl FnOnce(&mut UserScoreRequestBuilder) -> &mut UserScoreRequestBuilder, ) -> Result, Error> { - self.user_scores(UserScoreType::Best, client, user, f) + self.user_scores(UserScoreType::Best, user, f) } pub fn user_recent( &self, - client: &HTTPClient, user: UserID, f: impl FnOnce(&mut UserScoreRequestBuilder) -> &mut UserScoreRequestBuilder, ) -> Result, Error> { - self.user_scores(UserScoreType::Recent, client, user, f) + self.user_scores(UserScoreType::Recent, user, f) } fn user_scores( &self, u: UserScoreType, - client: &HTTPClient, user: UserID, f: impl FnOnce(&mut UserScoreRequestBuilder) -> &mut UserScoreRequestBuilder, ) -> Result, Error> { let mut r = UserScoreRequestBuilder::new(u, user); f(&mut r); - let res: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; let res = vec_try_into(res)?; Ok(res) } diff --git a/youmubot/src/commands/osu/announcer.rs b/youmubot/src/commands/osu/announcer.rs index 60a8166..fa08b3f 100644 --- a/youmubot/src/commands/osu/announcer.rs +++ b/youmubot/src/commands/osu/announcer.rs @@ -33,8 +33,7 @@ impl Announcer for OsuAnnouncer { d: &mut ShareMap, channels: impl Fn(UserId) -> Vec + Sync, ) -> CommandResult { - let http = d.get::().expect("HTTP"); - let osu = d.get::().expect("osu!client"); + let osu = d.get::().expect("osu!client").clone(); // For each user... let mut data = d .get::() @@ -43,20 +42,27 @@ impl Announcer for OsuAnnouncer { 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)?; + let scores = OsuAnnouncer::scan_user(&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() - }); + let user = { + let user = &mut user; + if let None = user { + match osu.user(UserID::ID(osu_user.id), |f| f.mode(*mode)) { + Ok(u) => { + *user = u; + } + Err(_) => continue, + } + }; + user.as_ref().unwrap() + }; scores .into_par_iter() .filter_map(|(rank, score)| { let beatmap = osu - .beatmaps(http, BeatmapRequestKind::Beatmap(score.beatmap_id), |f| f) + .beatmaps(BeatmapRequestKind::Beatmap(score.beatmap_id), |f| f) .map(|v| BeatmapWithMode(v.into_iter().next().unwrap(), *mode)); let channels = channels(*user_id); match beatmap { @@ -89,13 +95,8 @@ impl Announcer for OsuAnnouncer { } impl OsuAnnouncer { - fn scan_user( - http: &HTTPClient, - osu: &OsuClient, - u: &OsuUser, - mode: Mode, - ) -> Result, Error> { - let scores = osu.user_best(http, UserID::ID(u.id), |f| f.mode(mode).limit(25))?; + fn scan_user(osu: &OsuClient, u: &OsuUser, mode: Mode) -> Result, Error> { + let scores = osu.user_best(UserID::ID(u.id), |f| f.mode(mode).limit(25))?; let scores = scores .into_iter() .filter(|s: &Score| s.date >= u.last_update) diff --git a/youmubot/src/commands/osu/hook.rs b/youmubot/src/commands/osu/hook.rs index b109262..3c1adbc 100644 --- a/youmubot/src/commands/osu/hook.rs +++ b/youmubot/src/commands/osu/hook.rs @@ -71,9 +71,7 @@ struct ToPrint<'a> { } fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result>, Error> { - let data = ctx.data.write(); - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = ctx.data.read().get::().unwrap().clone(); let mut to_prints: Vec> = Vec::new(); for capture in OLD_LINK_REGEX.captures_iter(content) { let req_type = capture.name("link_type").unwrap().as_str(); @@ -95,7 +93,7 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result return None, }) }); - let beatmaps = osu.beatmaps(reqwest, req, |v| match mode { + let beatmaps = osu.beatmaps(req, |v| match mode { Some(m) => v.mode(m, true), None => v, })?; @@ -124,8 +122,7 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result(ctx: &mut Context, content: &'a str) -> Result>, Error> { let data = ctx.data.write(); - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = data.get::().unwrap().clone(); let mut to_prints: Vec> = Vec::new(); for capture in NEW_LINK_REGEX.captures_iter(content) { let mode = capture.name("mode").and_then(|v| { @@ -145,7 +142,7 @@ fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result v.mode(m, true), None => v, })?; diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot/src/commands/osu/mod.rs index 24a11af..2e8fac2 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot/src/commands/osu/mod.rs @@ -91,15 +91,14 @@ impl AsRef for BeatmapWithMode { #[usage = "[username or user_id]"] #[num_args(1)] pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { - let mut data = ctx.data.write(); - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = ctx.data.read().get::().unwrap().clone(); let user = args.single::()?; - let user: Option = osu.user(reqwest, UserID::Auto(user), |f| f)?; + let user: Option = osu.user(UserID::Auto(user), |f| f)?; match user { Some(u) => { - let mut db: DBWriteGuard<_> = data + let mut db = ctx.data.write(); + let mut db: DBWriteGuard<_> = db .get_mut::() .ok_or(Error::from("DB uninitialized"))? .into(); @@ -201,28 +200,27 @@ impl FromStr for Nth { #[example = "#1 / taiko / natsukagami"] #[max_args(3)] pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { - let mut data = ctx.data.write(); - let nth = args.single::().unwrap_or(Nth(1)).0.min(50).max(1); let mode = args.single::().unwrap_or(ModeArg(Mode::Std)).0; - let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; + let user = UsernameArg::to_user_id_query( + args.single::().ok(), + &mut *ctx.data.write(), + msg, + )?; - let reqwest = data.get::().unwrap(); - let osu: &OsuClient = data.get::().unwrap(); + let osu: OsuClient = ctx.data.read().get::().unwrap().clone(); let user = osu - .user(reqwest, user, |f| f.mode(mode))? + .user(user, |f| f.mode(mode))? .ok_or(Error::from("User not found"))?; let recent_play = osu - .user_recent(reqwest, UserID::ID(user.id), |f| f.mode(mode).limit(nth))? + .user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(nth))? .into_iter() .last() .ok_or(Error::from("No such play"))?; let beatmap = osu - .beatmaps( - reqwest, - BeatmapRequestKind::Beatmap(recent_play.beatmap_id), - |f| f.mode(mode, true), - )? + .beatmaps(BeatmapRequestKind::Beatmap(recent_play.beatmap_id), |f| { + f.mode(mode, true) + })? .into_iter() .next() .map(|v| BeatmapWithMode(v, mode)) @@ -237,7 +235,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult })?; // Save the beatmap... - cache::save_beatmap(&mut *data, msg.channel_id, &beatmap)?; + cache::save_beatmap(&mut *ctx.data.write(), msg.channel_id, &beatmap)?; Ok(()) } @@ -287,15 +285,12 @@ pub fn check(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = data.get::().unwrap().clone(); let user = osu - .user(reqwest, user, |f| f)? + .user(user, |f| f)? .ok_or(Error::from("User not found"))?; - let scores = osu.scores(reqwest, b.beatmap_id, |f| { - f.user(UserID::ID(user.id)).mode(m) - })?; + let scores = osu.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))?; if scores.is_empty() { msg.reply(&ctx, "No scores found")?; @@ -327,12 +322,11 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { let mut data = ctx.data.write(); let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; - let reqwest = data.get::().unwrap(); - let osu: &OsuClient = data.get::().unwrap(); + let osu: OsuClient = data.get::().unwrap().clone(); let user = osu - .user(reqwest, user, |f| f.mode(mode))? + .user(user, |f| f.mode(mode))? .ok_or(Error::from("User not found"))?; - let top_play = osu.user_best(reqwest, UserID::ID(user.id), |f| f.mode(mode).limit(nth))?; + let top_play = osu.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(nth))?; let rank = top_play.len() as u8; @@ -341,11 +335,9 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { .last() .ok_or(Error::from("No such play"))?; let beatmap = osu - .beatmaps( - reqwest, - BeatmapRequestKind::Beatmap(top_play.beatmap_id), - |f| f.mode(mode, true), - )? + .beatmaps(BeatmapRequestKind::Beatmap(top_play.beatmap_id), |f| { + f.mode(mode, true) + })? .into_iter() .next() .map(|v| BeatmapWithMode(v, mode)) @@ -368,17 +360,16 @@ 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 { let mut data = ctx.data.write(); let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); - let user = osu.user(reqwest, user, |f| f.mode(mode))?; + let osu = data.get::().unwrap().clone(); + let user = osu.user(user, |f| f.mode(mode))?; match user { Some(u) => { let best = osu - .user_best(reqwest, UserID::ID(u.id), |f| f.limit(1).mode(mode))? + .user_best(UserID::ID(u.id), |f| f.limit(1).mode(mode))? .into_iter() .next() .map(|m| { - osu.beatmaps(reqwest, BeatmapRequestKind::Beatmap(m.beatmap_id), |f| { + osu.beatmaps(BeatmapRequestKind::Beatmap(m.beatmap_id), |f| { f.mode(mode, true) }) .map(|map| (m, BeatmapWithMode(map.into_iter().next().unwrap(), mode))) diff --git a/youmubot/src/main.rs b/youmubot/src/main.rs index ae23ca4..8b5b504 100644 --- a/youmubot/src/main.rs +++ b/youmubot/src/main.rs @@ -48,8 +48,10 @@ fn main() { // Setup shared instances of things { let mut data = client.data.write(); - data.insert::(reqwest::blocking::Client::new()); + let http_client = reqwest::blocking::Client::new(); + data.insert::(http_client.clone()); data.insert::(OsuClient::new( + http_client.clone(), var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."), )); } From 9287bdf5b77525c920de635df644175b4b0a515b Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sun, 2 Feb 2020 20:21:07 -0500 Subject: [PATCH 2/8] Get rid of mut refs --- youmubot/src/commands/admin/soft_ban.rs | 20 ++++----- youmubot/src/commands/announcer.rs | 15 +++---- youmubot/src/commands/osu/announcer.rs | 7 ++-- youmubot/src/commands/osu/cache.rs | 6 +-- youmubot/src/commands/osu/hook.rs | 5 +-- youmubot/src/commands/osu/mod.rs | 54 +++++++++++-------------- youmubot/src/db/mod.rs | 12 +++--- 7 files changed, 52 insertions(+), 67 deletions(-) diff --git a/youmubot/src/commands/admin/soft_ban.rs b/youmubot/src/commands/admin/soft_ban.rs index 29e7653..0b3aeff 100644 --- a/youmubot/src/commands/admin/soft_ban.rs +++ b/youmubot/src/commands/admin/soft_ban.rs @@ -33,9 +33,9 @@ pub fn soft_ban(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResu }; let guild = msg.guild_id.ok_or(Error::from("Command is guild only"))?; - let mut data = ctx.data.write(); - let mut data = data - .get_mut::() + let data = ctx.data.read(); + let data = data + .get::() .ok_or(Error::from("DB initialized")) .map(|v| DBWriteGuard::from(v))?; let mut data = data.borrow_mut()?; @@ -98,14 +98,14 @@ pub fn soft_ban_init(ctx: &mut Context, msg: &Message, mut args: Args) -> Comman ))); } // Check if we already set up - let mut data = ctx.data.write(); - let mut db: DBWriteGuard<_> = data - .get_mut::() + let data = ctx.data.read(); + let db: DBWriteGuard<_> = data + .get::() .ok_or(Error::from("DB uninitialized"))? .into(); let mut db = db.borrow_mut()?; let server = db - .get_mut(&guild.id) + .get(&guild.id) .map(|v| match v { ServerSoftBans::Unimplemented => false, _ => true, @@ -135,9 +135,9 @@ pub fn watch_soft_bans(client: &mut serenity::Client) -> impl FnOnce() -> () + ' // Scope so that locks are released { // Poll the data for any changes. - let mut data = data.write(); - let mut db: DBWriteGuard<_> = data - .get_mut::() + let data = data.read(); + let db: DBWriteGuard<_> = data + .get::() .expect("DB wrongly initialized") .into(); let mut db = db.borrow_mut().expect("cannot unpack DB"); diff --git a/youmubot/src/commands/announcer.rs b/youmubot/src/commands/announcer.rs index dd25ecd..7a767e5 100644 --- a/youmubot/src/commands/announcer.rs +++ b/youmubot/src/commands/announcer.rs @@ -14,15 +14,12 @@ pub trait Announcer { fn announcer_key() -> &'static str; fn send_messages( c: &Http, - d: &mut ShareMap, + d: &ShareMap, channels: impl Fn(UserId) -> Vec + Sync, ) -> CommandResult; - fn set_channel(d: &mut ShareMap, guild: GuildId, channel: ChannelId) -> CommandResult { - let mut data: DBWriteGuard<_> = d - .get_mut::() - .expect("DB initialized") - .into(); + fn set_channel(d: &ShareMap, guild: GuildId, channel: ChannelId) -> CommandResult { + let data: DBWriteGuard<_> = d.get::().expect("DB initialized").into(); let mut data = data.borrow_mut()?; data.entry(Self::announcer_key().to_owned()) .or_default() @@ -30,7 +27,7 @@ pub trait Announcer { Ok(()) } - fn get_guilds(d: &mut ShareMap) -> Result, Error> { + fn get_guilds(d: &ShareMap) -> Result, Error> { let data = d .get::() .expect("DB initialized") @@ -42,7 +39,7 @@ pub trait Announcer { Ok(data) } - fn announce(c: &Http, d: &mut ShareMap) -> CommandResult { + fn announce(c: &Http, d: &ShareMap) -> CommandResult { let guilds: Vec<_> = Self::get_guilds(d)?; let member_sets = { let mut v = Vec::with_capacity(guilds.len()); @@ -75,7 +72,7 @@ pub trait Announcer { let c = client.cache_and_http.clone(); let data = client.data.clone(); spawn(move || loop { - if let Err(e) = Self::announce(c.http(), &mut *data.write()) { + if let Err(e) = Self::announce(c.http(), &*data.read()) { dbg!(e); } std::thread::sleep(cooldown); diff --git a/youmubot/src/commands/osu/announcer.rs b/youmubot/src/commands/osu/announcer.rs index fa08b3f..d832191 100644 --- a/youmubot/src/commands/osu/announcer.rs +++ b/youmubot/src/commands/osu/announcer.rs @@ -2,10 +2,9 @@ use super::{embeds::score_embed, BeatmapWithMode}; use crate::{ commands::announcer::Announcer, db::{OsuSavedUsers, OsuUser}, - http::{Osu, HTTP}, + http::Osu, }; use rayon::prelude::*; -use reqwest::blocking::Client as HTTPClient; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, http::Http, @@ -30,7 +29,7 @@ impl Announcer for OsuAnnouncer { } fn send_messages( c: &Http, - d: &mut ShareMap, + d: &ShareMap, channels: impl Fn(UserId) -> Vec + Sync, ) -> CommandResult { let osu = d.get::().expect("osu!client").clone(); @@ -87,7 +86,7 @@ impl Announcer for OsuAnnouncer { osu_user.last_update = chrono::Utc::now(); } // Update users - let f = d.get_mut::().expect("DB initialized"); + let f = d.get::().expect("DB initialized"); f.write(|f| *f = data)?; f.save()?; Ok(()) diff --git a/youmubot/src/commands/osu/cache.rs b/youmubot/src/commands/osu/cache.rs index 0709d34..e52355b 100644 --- a/youmubot/src/commands/osu/cache.rs +++ b/youmubot/src/commands/osu/cache.rs @@ -8,12 +8,12 @@ use serenity::{ /// Save the beatmap into the server data storage. pub(crate) fn save_beatmap( - data: &mut ShareMap, + data: &ShareMap, channel_id: ChannelId, bm: &BeatmapWithMode, ) -> CommandResult { - let mut db: DBWriteGuard<_> = data - .get_mut::() + let db: DBWriteGuard<_> = data + .get::() .expect("DB is implemented") .into(); let mut db = db.borrow_mut()?; diff --git a/youmubot/src/commands/osu/hook.rs b/youmubot/src/commands/osu/hook.rs index 3c1adbc..c087580 100644 --- a/youmubot/src/commands/osu/hook.rs +++ b/youmubot/src/commands/osu/hook.rs @@ -47,7 +47,7 @@ pub fn hook(ctx: &mut Context, msg: &Message) -> () { } // Save the beatmap for query later. if let Some(t) = last_beatmap { - if let Err(v) = super::cache::save_beatmap(&mut *ctx.data.write(), msg.channel_id, &t) { + if let Err(v) = super::cache::save_beatmap(&*ctx.data.read(), msg.channel_id, &t) { dbg!(v); } } @@ -121,8 +121,7 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result(ctx: &mut Context, content: &'a str) -> Result>, Error> { - let data = ctx.data.write(); - let osu = data.get::().unwrap().clone(); + let osu = ctx.data.read().get::().unwrap().clone(); let mut to_prints: Vec> = Vec::new(); for capture in NEW_LINK_REGEX.captures_iter(content) { let mode = capture.name("mode").and_then(|v| { diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot/src/commands/osu/mod.rs index 2e8fac2..571c6d6 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot/src/commands/osu/mod.rs @@ -97,9 +97,9 @@ pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { let user: Option = osu.user(UserID::Auto(user), |f| f)?; match user { Some(u) => { - let mut db = ctx.data.write(); - let mut db: DBWriteGuard<_> = db - .get_mut::() + let db = ctx.data.read(); + let db: DBWriteGuard<_> = db + .get::() .ok_or(Error::from("DB uninitialized"))? .into(); let mut db = db.borrow_mut()?; @@ -147,18 +147,14 @@ enum UsernameArg { } impl UsernameArg { - fn to_user_id_query( - s: Option, - data: &mut ShareMap, - msg: &Message, - ) -> Result { + fn to_user_id_query(s: Option, data: &ShareMap, msg: &Message) -> Result { let id = match s { Some(UsernameArg::Raw(s)) => return Ok(UserID::Auto(s)), Some(UsernameArg::Tagged(r)) => r, None => msg.author.id, }; let db: DBWriteGuard<_> = data - .get_mut::() + .get::() .ok_or(Error::from("DB uninitialized"))? .into(); let db = db.borrow()?; @@ -202,11 +198,8 @@ impl FromStr for Nth { pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { let nth = args.single::().unwrap_or(Nth(1)).0.min(50).max(1); let mode = args.single::().unwrap_or(ModeArg(Mode::Std)).0; - let user = UsernameArg::to_user_id_query( - args.single::().ok(), - &mut *ctx.data.write(), - msg, - )?; + let user = + UsernameArg::to_user_id_query(args.single::().ok(), &*ctx.data.read(), msg)?; let osu: OsuClient = ctx.data.read().get::().unwrap().clone(); let user = osu @@ -235,7 +228,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult })?; // Save the beatmap... - cache::save_beatmap(&mut *ctx.data.write(), msg.channel_id, &beatmap)?; + cache::save_beatmap(&*ctx.data.read(), msg.channel_id, &beatmap)?; Ok(()) } @@ -244,9 +237,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult #[description = "Show information from the last queried beatmap."] #[num_args(0)] pub fn last(ctx: &mut Context, msg: &Message, _: Args) -> CommandResult { - let mut data = ctx.data.write(); - - let b = cache::get_beatmap(&mut *data, msg.channel_id)?; + let b = cache::get_beatmap(&*ctx.data.read(), msg.channel_id)?; match b { Some(BeatmapWithMode(b, m)) => { @@ -271,9 +262,7 @@ pub fn last(ctx: &mut Context, msg: &Message, _: Args) -> CommandResult { #[description = "Check your own or someone else's best record on the last beatmap."] #[max_args(1)] pub fn check(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { - let mut data = ctx.data.write(); - - let bm = cache::get_beatmap(&mut *data, msg.channel_id)?; + let bm = cache::get_beatmap(&*ctx.data.read(), msg.channel_id)?; match bm { None => { @@ -282,10 +271,13 @@ pub fn check(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult Some(bm) => { let b = &bm.0; let m = bm.1; - let user = - UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; + let user = UsernameArg::to_user_id_query( + args.single::().ok(), + &*ctx.data.read(), + msg, + )?; - let osu = data.get::().unwrap().clone(); + let osu = ctx.data.read().get::().unwrap().clone(); let user = osu .user(user, |f| f)? @@ -319,10 +311,10 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { .map(|ModeArg(t)| t) .unwrap_or(Mode::Std); - let mut data = ctx.data.write(); - let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; + let user = + UsernameArg::to_user_id_query(args.single::().ok(), &*ctx.data.read(), msg)?; - let osu: OsuClient = data.get::().unwrap().clone(); + let osu: OsuClient = ctx.data.read().get::().unwrap().clone(); let user = osu .user(user, |f| f.mode(mode))? .ok_or(Error::from("User not found"))?; @@ -352,15 +344,15 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { })?; // Save the beatmap... - cache::save_beatmap(&mut *data, msg.channel_id, &beatmap)?; + cache::save_beatmap(&*ctx.data.read(), msg.channel_id, &beatmap)?; Ok(()) } fn get_user(ctx: &mut Context, msg: &Message, mut args: Args, mode: Mode) -> CommandResult { - let mut data = ctx.data.write(); - let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; - let osu = data.get::().unwrap().clone(); + let user = + UsernameArg::to_user_id_query(args.single::().ok(), &*ctx.data.read(), msg)?; + let osu = ctx.data.read().get::().unwrap().clone(); let user = osu.user(user, |f| f.mode(mode))?; match user { Some(u) => { diff --git a/youmubot/src/db/mod.rs b/youmubot/src/db/mod.rs index e15517e..e8f4c98 100644 --- a/youmubot/src/db/mod.rs +++ b/youmubot/src/db/mod.rs @@ -63,15 +63,15 @@ pub fn setup_db(client: &mut Client) -> Result<(), Error> { Ok(()) } -pub struct DBWriteGuard<'a, T>(&'a mut FileDatabase) +pub struct DBWriteGuard<'a, T>(&'a FileDatabase) where T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned; -impl<'a, T> From<&'a mut FileDatabase> for DBWriteGuard<'a, T> +impl<'a, T> From<&'a FileDatabase> for DBWriteGuard<'a, T> where T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, { - fn from(v: &'a mut FileDatabase) -> Self { + fn from(v: &'a FileDatabase) -> Self { DBWriteGuard(v) } } @@ -83,9 +83,7 @@ where pub fn borrow(&self) -> Result, rustbreak::RustbreakError> { (*self).0.borrow_data() } - pub fn borrow_mut( - &mut self, - ) -> Result, rustbreak::RustbreakError> { + pub fn borrow_mut(&self) -> Result, rustbreak::RustbreakError> { (*self).0.borrow_data_mut() } } @@ -112,7 +110,7 @@ impl ServerSoftBans { // Create a new, implemented role. pub fn new_implemented(role: RoleId) -> ServerSoftBans { ServerSoftBans::Implemented(ImplementedSoftBans { - role: role, + role, periodical_bans: HashMap::new(), }) } From c4916a24f78183b9c1c6bea5b72d7cc8af70886f Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sun, 2 Feb 2020 21:51:54 -0500 Subject: [PATCH 3/8] Split db into youmubot-db --- Cargo.lock | 14 ++++- Cargo.toml | 1 + youmubot-db/Cargo.toml | 24 ++++++++ youmubot-db/src/lib.rs | 73 +++++++++++++++++++++++++ youmubot/Cargo.toml | 4 +- youmubot/src/commands/admin/soft_ban.rs | 29 ++++------ youmubot/src/commands/announcer.rs | 35 ++++++------ youmubot/src/commands/fun/images.rs | 15 ++--- youmubot/src/commands/osu/announcer.rs | 25 +++------ youmubot/src/commands/osu/cache.rs | 11 ++-- youmubot/src/commands/osu/hook.rs | 7 +-- youmubot/src/commands/osu/mod.rs | 27 ++++----- youmubot/src/{db/mod.rs => db.rs} | 68 ++--------------------- youmubot/src/http.rs | 14 ----- youmubot/src/main.rs | 10 ++-- youmubot/src/prelude.rs | 50 +++++++++++++++++ 16 files changed, 231 insertions(+), 176 deletions(-) create mode 100644 youmubot-db/Cargo.toml create mode 100644 youmubot-db/src/lib.rs rename youmubot/src/{db/mod.rs => db.rs} (52%) delete mode 100644 youmubot/src/http.rs create mode 100644 youmubot/src/prelude.rs diff --git a/Cargo.lock b/Cargo.lock index a0b61f6..5295786 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1684,13 +1684,25 @@ dependencies = [ "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rustbreak 2.0.0-rc3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "youmubot-db 0.1.0", "youmubot-osu 0.1.0", ] +[[package]] +name = "youmubot-db" +version = "0.1.0" +dependencies = [ + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustbreak 2.0.0-rc3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "youmubot-osu" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 10fc90c..1be4f54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "youmubot-db", "youmubot-osu", "youmubot", ] diff --git a/youmubot-db/Cargo.toml b/youmubot-db/Cargo.toml new file mode 100644 index 0000000..3008027 --- /dev/null +++ b/youmubot-db/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "youmubot-db" +version = "0.1.0" +authors = ["Natsu Kagami "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serenity = "0.8" +dotenv = "0.15" +serde = { version = "1.0", features = ["derive"] } +chrono = "0.4.9" +# rand = "0.7.2" +# static_assertions = "1.1.0" +# reqwest = "0.10.1" +# regex = "1" +# lazy_static = "1" +# youmubot-osu = { path = "../youmubot-osu" } +rayon = "1.1" + +[dependencies.rustbreak] +version = "2.0.0-rc3" +features = ["yaml_enc"] diff --git a/youmubot-db/src/lib.rs b/youmubot-db/src/lib.rs new file mode 100644 index 0000000..aa82582 --- /dev/null +++ b/youmubot-db/src/lib.rs @@ -0,0 +1,73 @@ +use rustbreak::{deser::Yaml as Ron, FileDatabase}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serenity::{framework::standard::CommandError as Error, model::id::GuildId, prelude::*}; +use std::collections::HashMap; +use std::path::Path; + +/// GuildMap defines the guild-map type. +/// It is basically a HashMap from a GuildId to a data structure. +pub type GuildMap = HashMap; +/// The generic DB type we will be using. +pub struct DB(std::marker::PhantomData); +impl serenity::prelude::TypeMapKey for DB { + type Value = FileDatabase; +} + +impl DB +where + for<'de> T: Deserialize<'de>, +{ + /// Insert into a ShareMap. + pub fn insert_into(data: &mut ShareMap, path: impl AsRef) -> Result<(), Error> { + let db = FileDatabase::::from_path(path, T::default())?; + db.load().or_else(|e| { + dbg!(e); + db.save() + })?; + data.insert::>(db); + Ok(()) + } + + /// Open a previously inserted DB. + pub fn open(data: &ShareMap) -> DBWriteGuard<'_, T> { + data.get::().expect("DB initialized").into() + } +} + +/// The write guard for our FileDatabase. +/// It wraps the FileDatabase in a write-on-drop lock. +pub struct DBWriteGuard<'a, T>(&'a FileDatabase) +where + T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned; + +impl<'a, T> From<&'a FileDatabase> for DBWriteGuard<'a, T> +where + T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, +{ + fn from(v: &'a FileDatabase) -> Self { + DBWriteGuard(v) + } +} + +impl<'a, T> DBWriteGuard<'a, T> +where + T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, +{ + /// Borrows the FileDatabase. + pub fn borrow(&self) -> Result, rustbreak::RustbreakError> { + (*self).0.borrow_data() + } + /// Borrows the FileDatabase for writing. + pub fn borrow_mut(&self) -> Result, rustbreak::RustbreakError> { + (*self).0.borrow_data_mut() + } +} + +impl<'a, T> Drop for DBWriteGuard<'a, T> +where + T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, +{ + fn drop(&mut self) { + self.0.save().expect("Save succeed") + } +} diff --git a/youmubot/Cargo.toml b/youmubot/Cargo.toml index 5df5fbb..3114212 100644 --- a/youmubot/Cargo.toml +++ b/youmubot/Cargo.toml @@ -18,7 +18,5 @@ regex = "1" lazy_static = "1" youmubot-osu = { path = "../youmubot-osu" } rayon = "1.1" +youmubot-db = { path = "../youmubot-db" } -[dependencies.rustbreak] -version = "2.0.0-rc3" -features = ["yaml_enc"] diff --git a/youmubot/src/commands/admin/soft_ban.rs b/youmubot/src/commands/admin/soft_ban.rs index 0b3aeff..f142773 100644 --- a/youmubot/src/commands/admin/soft_ban.rs +++ b/youmubot/src/commands/admin/soft_ban.rs @@ -1,6 +1,6 @@ use crate::{ commands::args, - db::{DBWriteGuard, ServerSoftBans, SoftBans}, + db::{ServerSoftBans, SoftBans}, }; use chrono::offset::Utc; use serenity::prelude::*; @@ -33,13 +33,10 @@ pub fn soft_ban(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResu }; let guild = msg.guild_id.ok_or(Error::from("Command is guild only"))?; - let data = ctx.data.read(); - let data = data - .get::() - .ok_or(Error::from("DB initialized")) - .map(|v| DBWriteGuard::from(v))?; - let mut data = data.borrow_mut()?; - let mut server_ban = data.get_mut(&guild).and_then(|v| match v { + let db = ctx.data.read(); + let db = SoftBans::open(&*db); + let mut db = db.borrow_mut()?; + let mut server_ban = db.get_mut(&guild).and_then(|v| match v { ServerSoftBans::Unimplemented => None, ServerSoftBans::Implemented(ref mut v) => Some(v), }); @@ -98,11 +95,8 @@ pub fn soft_ban_init(ctx: &mut Context, msg: &Message, mut args: Args) -> Comman ))); } // Check if we already set up - let data = ctx.data.read(); - let db: DBWriteGuard<_> = data - .get::() - .ok_or(Error::from("DB uninitialized"))? - .into(); + let db = ctx.data.read(); + let db = SoftBans::open(&*db); let mut db = db.borrow_mut()?; let server = db .get(&guild.id) @@ -135,12 +129,9 @@ pub fn watch_soft_bans(client: &mut serenity::Client) -> impl FnOnce() -> () + ' // Scope so that locks are released { // Poll the data for any changes. - let data = data.read(); - let db: DBWriteGuard<_> = data - .get::() - .expect("DB wrongly initialized") - .into(); - let mut db = db.borrow_mut().expect("cannot unpack DB"); + let db = data.read(); + let db = SoftBans::open(&*db); + let mut db = db.borrow_mut().expect("Borrowable"); let now = Utc::now(); for (server_id, soft_bans) in db.iter_mut() { let server_name: String = match server_id.to_partial_guild(cache_http) { diff --git a/youmubot/src/commands/announcer.rs b/youmubot/src/commands/announcer.rs index 7a767e5..d36d940 100644 --- a/youmubot/src/commands/announcer.rs +++ b/youmubot/src/commands/announcer.rs @@ -1,9 +1,9 @@ -use crate::db::{AnnouncerChannels, DBWriteGuard}; +use crate::db::AnnouncerChannels; +use crate::prelude::*; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, http::{CacheHttp, Http}, model::id::{ChannelId, GuildId, UserId}, - prelude::ShareMap, }; use std::{ collections::HashSet, @@ -14,33 +14,30 @@ pub trait Announcer { fn announcer_key() -> &'static str; fn send_messages( c: &Http, - d: &ShareMap, + d: AppData, channels: impl Fn(UserId) -> Vec + Sync, ) -> CommandResult; - fn set_channel(d: &ShareMap, guild: GuildId, channel: ChannelId) -> CommandResult { - let data: DBWriteGuard<_> = d.get::().expect("DB initialized").into(); - let mut data = data.borrow_mut()?; - data.entry(Self::announcer_key().to_owned()) + fn set_channel(d: AppData, guild: GuildId, channel: ChannelId) -> CommandResult { + AnnouncerChannels::open(&*d.read()) + .borrow_mut()? + .entry(Self::announcer_key().to_owned()) .or_default() .insert(guild, channel); Ok(()) } - fn get_guilds(d: &ShareMap) -> Result, Error> { - let data = d - .get::() - .expect("DB initialized") - .read(|v| { - v.get(Self::announcer_key()) - .map(|m| m.iter().map(|(a, b)| (*a, *b)).collect()) - .unwrap_or_else(|| vec![]) - })?; + fn get_guilds(d: AppData) -> Result, Error> { + let data = AnnouncerChannels::open(&*d.read()) + .borrow()? + .get(Self::announcer_key()) + .map(|m| m.iter().map(|(a, b)| (*a, *b)).collect()) + .unwrap_or_else(|| vec![]); Ok(data) } - fn announce(c: &Http, d: &ShareMap) -> CommandResult { - let guilds: Vec<_> = Self::get_guilds(d)?; + fn announce(c: impl AsRef, d: AppData) -> CommandResult { + let guilds: Vec<_> = Self::get_guilds(d.clone())?; let member_sets = { let mut v = Vec::with_capacity(guilds.len()); for (guild, channel) in guilds.into_iter() { @@ -72,7 +69,7 @@ pub trait Announcer { let c = client.cache_and_http.clone(); let data = client.data.clone(); spawn(move || loop { - if let Err(e) = Self::announce(c.http(), &*data.read()) { + if let Err(e) = Self::announce(c.http(), data.clone()) { dbg!(e); } std::thread::sleep(cooldown); diff --git a/youmubot/src/commands/fun/images.rs b/youmubot/src/commands/fun/images.rs index 32bb9f1..0898a29 100644 --- a/youmubot/src/commands/fun/images.rs +++ b/youmubot/src/commands/fun/images.rs @@ -1,8 +1,6 @@ -use crate::http::HTTP; -use reqwest::blocking::Client as HTTPClient; +use crate::prelude::*; use serde::Deserialize; use serenity::framework::standard::CommandError as Error; -use serenity::prelude::*; use serenity::{ framework::standard::{ macros::{check, command}, @@ -45,9 +43,8 @@ fn nsfw_check(ctx: &mut Context, msg: &Message, _: &mut Args, _: &CommandOptions fn message_command(ctx: &mut Context, msg: &Message, args: Args, rating: Rating) -> CommandResult { let tags = args.remains().unwrap_or("touhou"); - let http = ctx.data.read(); - let http = http.get::().unwrap(); - let image = get_image(http, rating, tags)?; + let http = ctx.data.get_cloned::(); + let image = get_image(&http, rating, tags)?; match image { None => msg.reply(&ctx, "🖼️ No image found...\n💡 Tip: In danbooru, character names follow Japanese standards (last name before first name), so **Hakurei Reimu** might give you an image while **Reimu Hakurei** won't."), Some(url) => msg.reply( @@ -59,7 +56,11 @@ fn message_command(ctx: &mut Context, msg: &Message, args: Args, rating: Rating) } // Gets an image URL. -fn get_image(client: &HTTPClient, rating: Rating, tags: &str) -> Result, Error> { +fn get_image( + client: &reqwest::blocking::Client, + rating: Rating, + tags: &str, +) -> Result, Error> { // Fix the tags: change whitespaces to + let tags = tags.split_whitespace().collect::>().join("_"); let req = client diff --git a/youmubot/src/commands/osu/announcer.rs b/youmubot/src/commands/osu/announcer.rs index d832191..a0c80d7 100644 --- a/youmubot/src/commands/osu/announcer.rs +++ b/youmubot/src/commands/osu/announcer.rs @@ -2,22 +2,18 @@ use super::{embeds::score_embed, BeatmapWithMode}; use crate::{ commands::announcer::Announcer, db::{OsuSavedUsers, OsuUser}, - http::Osu, + prelude::*, }; use rayon::prelude::*; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, http::Http, - model::{ - id::{ChannelId, UserId}, - misc::Mentionable, - }, - prelude::ShareMap, + model::id::{ChannelId, UserId}, }; use youmubot_osu::{ models::{Mode, Score}, request::{BeatmapRequestKind, UserID}, - Client as OsuClient, + Client as Osu, }; /// Announce osu! top scores. @@ -29,15 +25,12 @@ impl Announcer for OsuAnnouncer { } fn send_messages( c: &Http, - d: &ShareMap, + d: AppData, channels: impl Fn(UserId) -> Vec + Sync, ) -> CommandResult { - let osu = d.get::().expect("osu!client").clone(); + let osu = d.get_cloned::(); // For each user... - let mut data = d - .get::() - .expect("DB initialized") - .read(|f| f.clone())?; + let mut data = OsuSavedUsers::open(&*d.read()).borrow()?.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] { @@ -86,15 +79,13 @@ impl Announcer for OsuAnnouncer { osu_user.last_update = chrono::Utc::now(); } // Update users - let f = d.get::().expect("DB initialized"); - f.write(|f| *f = data)?; - f.save()?; + *OsuSavedUsers::open(&*d.read()).borrow_mut()? = data; Ok(()) } } impl OsuAnnouncer { - fn scan_user(osu: &OsuClient, u: &OsuUser, mode: Mode) -> Result, Error> { + fn scan_user(osu: &Osu, u: &OsuUser, mode: Mode) -> Result, Error> { let scores = osu.user_best(UserID::ID(u.id), |f| f.mode(mode).limit(25))?; let scores = scores .into_iter() diff --git a/youmubot/src/commands/osu/cache.rs b/youmubot/src/commands/osu/cache.rs index e52355b..8e3af26 100644 --- a/youmubot/src/commands/osu/cache.rs +++ b/youmubot/src/commands/osu/cache.rs @@ -1,5 +1,5 @@ use super::BeatmapWithMode; -use crate::db::{DBWriteGuard, OsuLastBeatmap}; +use crate::db::OsuLastBeatmap; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, model::id::ChannelId, @@ -12,10 +12,7 @@ pub(crate) fn save_beatmap( channel_id: ChannelId, bm: &BeatmapWithMode, ) -> CommandResult { - let db: DBWriteGuard<_> = data - .get::() - .expect("DB is implemented") - .into(); + let db = OsuLastBeatmap::open(data); let mut db = db.borrow_mut()?; db.insert(channel_id, (bm.0.clone(), bm.mode())); @@ -28,8 +25,8 @@ pub(crate) fn get_beatmap( data: &ShareMap, channel_id: ChannelId, ) -> Result, Error> { - let db = data.get::().expect("DB is implemented"); - let db = db.borrow_data()?; + let db = OsuLastBeatmap::open(data); + let db = db.borrow()?; Ok(db .get(&channel_id) diff --git a/youmubot/src/commands/osu/hook.rs b/youmubot/src/commands/osu/hook.rs index c087580..2b3bf03 100644 --- a/youmubot/src/commands/osu/hook.rs +++ b/youmubot/src/commands/osu/hook.rs @@ -1,11 +1,10 @@ -use crate::http; +use crate::prelude::*; use lazy_static::lazy_static; use regex::Regex; use serenity::{ builder::CreateMessage, framework::standard::{CommandError as Error, CommandResult}, model::channel::Message, - prelude::*, utils::MessageBuilder, }; use youmubot_osu::{ @@ -71,7 +70,7 @@ struct ToPrint<'a> { } fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result>, Error> { - let osu = ctx.data.read().get::().unwrap().clone(); + let osu = ctx.data.get_cloned::(); let mut to_prints: Vec> = Vec::new(); for capture in OLD_LINK_REGEX.captures_iter(content) { let req_type = capture.name("link_type").unwrap().as_str(); @@ -121,7 +120,7 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result(ctx: &mut Context, content: &'a str) -> Result>, Error> { - let osu = ctx.data.read().get::().unwrap().clone(); + let osu = ctx.data.get_cloned::(); let mut to_prints: Vec> = Vec::new(); for capture in NEW_LINK_REGEX.captures_iter(content) { let mode = capture.name("mode").and_then(|v| { diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot/src/commands/osu/mod.rs index 571c6d6..159acfe 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot/src/commands/osu/mod.rs @@ -1,19 +1,17 @@ -use crate::db::{DBWriteGuard, OsuSavedUsers, OsuUser}; -use crate::http; +use crate::db::{OsuSavedUsers, OsuUser}; +use crate::prelude::*; use serenity::{ framework::standard::{ macros::{command, group}, Args, CommandError as Error, CommandResult, }, model::{channel::Message, id::UserId}, - prelude::*, utils::MessageBuilder, }; use std::str::FromStr; use youmubot_osu::{ models::{Beatmap, Mode, User}, request::{BeatmapRequestKind, UserID}, - Client as OsuClient, }; mod announcer; @@ -91,17 +89,14 @@ impl AsRef for BeatmapWithMode { #[usage = "[username or user_id]"] #[num_args(1)] pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { - let osu = ctx.data.read().get::().unwrap().clone(); + let osu = ctx.data.get_cloned::(); let user = args.single::()?; let user: Option = osu.user(UserID::Auto(user), |f| f)?; match user { Some(u) => { let db = ctx.data.read(); - let db: DBWriteGuard<_> = db - .get::() - .ok_or(Error::from("DB uninitialized"))? - .into(); + let db = OsuSavedUsers::open(&db); let mut db = db.borrow_mut()?; db.insert( @@ -153,10 +148,8 @@ impl UsernameArg { Some(UsernameArg::Tagged(r)) => r, None => msg.author.id, }; - let db: DBWriteGuard<_> = data - .get::() - .ok_or(Error::from("DB uninitialized"))? - .into(); + + let db = OsuSavedUsers::open(data); let db = db.borrow()?; db.get(&id) .cloned() @@ -201,7 +194,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult let user = UsernameArg::to_user_id_query(args.single::().ok(), &*ctx.data.read(), msg)?; - let osu: OsuClient = ctx.data.read().get::().unwrap().clone(); + let osu = ctx.data.get_cloned::(); let user = osu .user(user, |f| f.mode(mode))? .ok_or(Error::from("User not found"))?; @@ -277,7 +270,7 @@ pub fn check(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult msg, )?; - let osu = ctx.data.read().get::().unwrap().clone(); + let osu = ctx.data.get_cloned::(); let user = osu .user(user, |f| f)? @@ -314,7 +307,7 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { let user = UsernameArg::to_user_id_query(args.single::().ok(), &*ctx.data.read(), msg)?; - let osu: OsuClient = ctx.data.read().get::().unwrap().clone(); + let osu = ctx.data.get_cloned::(); let user = osu .user(user, |f| f.mode(mode))? .ok_or(Error::from("User not found"))?; @@ -352,7 +345,7 @@ 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 { let user = UsernameArg::to_user_id_query(args.single::().ok(), &*ctx.data.read(), msg)?; - let osu = ctx.data.read().get::().unwrap().clone(); + let osu = ctx.data.get_cloned::(); let user = osu.user(user, |f| f.mode(mode))?; match user { Some(u) => { diff --git a/youmubot/src/db/mod.rs b/youmubot/src/db.rs similarity index 52% rename from youmubot/src/db/mod.rs rename to youmubot/src/db.rs index e8f4c98..c6b7a1d 100644 --- a/youmubot/src/db/mod.rs +++ b/youmubot/src/db.rs @@ -1,41 +1,17 @@ use chrono::{DateTime, Utc}; use dotenv::var; -use rustbreak::{deser::Yaml as Ron, FileDatabase}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use serde::{Deserialize, Serialize}; use serenity::{ client::Client, framework::standard::CommandError as Error, - model::id::{ChannelId, GuildId, RoleId, UserId}, - prelude::*, + model::id::{ChannelId, RoleId, UserId}, }; use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; +use youmubot_db::{GuildMap, DB}; use youmubot_osu::models::{Beatmap, Mode}; -/// GuildMap defines the guild-map type. -/// It is basically a HashMap from a GuildId to a data structure. -pub type GuildMap = HashMap; -/// The generic DB type we will be using. -pub struct DB(std::marker::PhantomData); -impl serenity::prelude::TypeMapKey for DB { - type Value = FileDatabase; -} - -impl DB -where - for<'de> T: Deserialize<'de>, -{ - fn insert_into(data: &mut ShareMap, path: impl AsRef) -> Result<(), Error> { - let db = FileDatabase::::from_path(path, T::default())?; - db.load().or_else(|e| { - dbg!(e); - db.save() - })?; - data.insert::>(db); - Ok(()) - } -} - /// A map from announcer keys to guild IDs and to channels. pub type AnnouncerChannels = DB>>; @@ -63,40 +39,6 @@ pub fn setup_db(client: &mut Client) -> Result<(), Error> { Ok(()) } -pub struct DBWriteGuard<'a, T>(&'a FileDatabase) -where - T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned; - -impl<'a, T> From<&'a FileDatabase> for DBWriteGuard<'a, T> -where - T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, -{ - fn from(v: &'a FileDatabase) -> Self { - DBWriteGuard(v) - } -} - -impl<'a, T> DBWriteGuard<'a, T> -where - T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, -{ - pub fn borrow(&self) -> Result, rustbreak::RustbreakError> { - (*self).0.borrow_data() - } - pub fn borrow_mut(&self) -> Result, rustbreak::RustbreakError> { - (*self).0.borrow_data_mut() - } -} - -impl<'a, T> Drop for DBWriteGuard<'a, T> -where - T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, -{ - fn drop(&mut self) { - self.0.save().expect("Save succeed") - } -} - /// For the admin commands: /// - Each server might have a `soft ban` role implemented. /// - We allow periodical `soft ban` applications. diff --git a/youmubot/src/http.rs b/youmubot/src/http.rs deleted file mode 100644 index 9f319c4..0000000 --- a/youmubot/src/http.rs +++ /dev/null @@ -1,14 +0,0 @@ -use serenity::prelude::TypeMapKey; -use youmubot_osu::Client as OsuClient; - -pub(crate) struct HTTP; - -impl TypeMapKey for HTTP { - type Value = reqwest::blocking::Client; -} - -pub(crate) struct Osu; - -impl TypeMapKey for Osu { - type Value = OsuClient; -} diff --git a/youmubot/src/main.rs b/youmubot/src/main.rs index 8b5b504..5227392 100644 --- a/youmubot/src/main.rs +++ b/youmubot/src/main.rs @@ -4,16 +4,16 @@ use reqwest; use serenity::{ framework::standard::{DispatchError, StandardFramework}, model::{channel::Message, gateway}, - prelude::*, }; -use youmubot_osu::Client as OsuClient; +use youmubot_osu::Client as OsuApiClient; mod commands; mod db; -mod http; +mod prelude; use commands::osu::OsuAnnouncer; use commands::Announcer; +use prelude::*; const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [commands::osu::hook]; @@ -49,8 +49,8 @@ fn main() { { let mut data = client.data.write(); let http_client = reqwest::blocking::Client::new(); - data.insert::(http_client.clone()); - data.insert::(OsuClient::new( + data.insert::(http_client.clone()); + data.insert::(OsuApiClient::new( http_client.clone(), var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."), )); diff --git a/youmubot/src/prelude.rs b/youmubot/src/prelude.rs new file mode 100644 index 0000000..dc98a94 --- /dev/null +++ b/youmubot/src/prelude.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; +use youmubot_osu::Client as OsuHttpClient; + +pub use serenity::prelude::*; + +/// The global app data. +pub type AppData = Arc>; + +/// The HTTP client. +pub(crate) struct HTTPClient; + +impl TypeMapKey for HTTPClient { + type Value = reqwest::blocking::Client; +} + +/// The osu! client. +pub(crate) struct OsuClient; + +impl TypeMapKey for OsuClient { + type Value = OsuHttpClient; +} + +/// The TypeMap trait that allows TypeMaps to quickly get a clonable item. +pub trait GetCloned { + /// Gets an item from the store, cloned. + fn get_cloned(&self) -> T::Value + where + T: TypeMapKey, + T::Value: Clone + Send + Sync; +} + +impl GetCloned for ShareMap { + fn get_cloned(&self) -> T::Value + where + T: TypeMapKey, + T::Value: Clone + Send + Sync, + { + self.get::().cloned().expect("Should be there") + } +} + +impl GetCloned for AppData { + fn get_cloned(&self) -> T::Value + where + T: TypeMapKey, + T::Value: Clone + Send + Sync, + { + self.read().get::().cloned().expect("Should be there") + } +} From d5f7a17a2c70d2f5565fed2a47b192c5503fc4d3 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 5 Feb 2020 15:31:58 -0500 Subject: [PATCH 4/8] Improve db ergonomics --- youmubot-db/src/lib.rs | 27 +++++++++---------------- youmubot/src/commands/admin/soft_ban.rs | 6 ++---- youmubot/src/commands/osu/mod.rs | 3 +-- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/youmubot-db/src/lib.rs b/youmubot-db/src/lib.rs index aa82582..ccf8576 100644 --- a/youmubot-db/src/lib.rs +++ b/youmubot-db/src/lib.rs @@ -3,6 +3,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serenity::{framework::standard::CommandError as Error, model::id::GuildId, prelude::*}; use std::collections::HashMap; use std::path::Path; +use std::sync::Arc; /// GuildMap defines the guild-map type. /// It is basically a HashMap from a GuildId to a data structure. @@ -10,7 +11,7 @@ pub type GuildMap = HashMap; /// The generic DB type we will be using. pub struct DB(std::marker::PhantomData); impl serenity::prelude::TypeMapKey for DB { - type Value = FileDatabase; + type Value = Arc>; } impl DB @@ -24,32 +25,33 @@ where dbg!(e); db.save() })?; - data.insert::>(db); + data.insert::>(Arc::new(db)); Ok(()) } /// Open a previously inserted DB. - pub fn open(data: &ShareMap) -> DBWriteGuard<'_, T> { - data.get::().expect("DB initialized").into() + pub fn open(data: &ShareMap) -> DBWriteGuard { + data.get::().expect("DB initialized").clone().into() } } /// The write guard for our FileDatabase. /// It wraps the FileDatabase in a write-on-drop lock. -pub struct DBWriteGuard<'a, T>(&'a FileDatabase) +#[derive(Debug)] +pub struct DBWriteGuard(Arc>) where T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned; -impl<'a, T> From<&'a FileDatabase> for DBWriteGuard<'a, T> +impl From>> for DBWriteGuard where T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, { - fn from(v: &'a FileDatabase) -> Self { + fn from(v: Arc>) -> Self { DBWriteGuard(v) } } -impl<'a, T> DBWriteGuard<'a, T> +impl DBWriteGuard where T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, { @@ -62,12 +64,3 @@ where (*self).0.borrow_data_mut() } } - -impl<'a, T> Drop for DBWriteGuard<'a, T> -where - T: Send + Sync + Clone + std::fmt::Debug + Serialize + DeserializeOwned, -{ - fn drop(&mut self) { - self.0.save().expect("Save succeed") - } -} diff --git a/youmubot/src/commands/admin/soft_ban.rs b/youmubot/src/commands/admin/soft_ban.rs index f142773..24f48ad 100644 --- a/youmubot/src/commands/admin/soft_ban.rs +++ b/youmubot/src/commands/admin/soft_ban.rs @@ -33,8 +33,7 @@ pub fn soft_ban(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResu }; let guild = msg.guild_id.ok_or(Error::from("Command is guild only"))?; - let db = ctx.data.read(); - let db = SoftBans::open(&*db); + let db = SoftBans::open(&*ctx.data.read()); let mut db = db.borrow_mut()?; let mut server_ban = db.get_mut(&guild).and_then(|v| match v { ServerSoftBans::Unimplemented => None, @@ -95,8 +94,7 @@ pub fn soft_ban_init(ctx: &mut Context, msg: &Message, mut args: Args) -> Comman ))); } // Check if we already set up - let db = ctx.data.read(); - let db = SoftBans::open(&*db); + let db = SoftBans::open(&*ctx.data.read()); let mut db = db.borrow_mut()?; let server = db .get(&guild.id) diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot/src/commands/osu/mod.rs index 159acfe..4f63d2d 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot/src/commands/osu/mod.rs @@ -95,8 +95,7 @@ pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { let user: Option = osu.user(UserID::Auto(user), |f| f)?; match user { Some(u) => { - let db = ctx.data.read(); - let db = OsuSavedUsers::open(&db); + let db = OsuSavedUsers::open(&*ctx.data.read()); let mut db = db.borrow_mut()?; db.insert( From 03be1a4acc20c31f9eae4d2d67749cd1ad75e8cf Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 5 Feb 2020 16:21:11 -0500 Subject: [PATCH 5/8] Move the prelude into a seperate package --- Cargo.lock | 10 ++++++ Cargo.toml | 1 + youmubot-prelude/Cargo.toml | 12 +++++++ .../src}/announcer.rs | 8 +++-- .../commands => youmubot-prelude/src}/args.rs | 0 .../prelude.rs => youmubot-prelude/src/lib.rs | 21 +++++++----- youmubot-prelude/src/setup.rs | 14 ++++++++ youmubot/Cargo.toml | 1 + youmubot/src/commands/admin/mod.rs | 2 +- youmubot/src/commands/admin/soft_ban.rs | 2 +- youmubot/src/commands/community/mod.rs | 2 +- youmubot/src/commands/community/votes.rs | 2 +- youmubot/src/commands/fun/images.rs | 2 +- youmubot/src/commands/fun/mod.rs | 2 +- youmubot/src/commands/mod.rs | 5 +-- youmubot/src/commands/osu/announcer.rs | 7 ++-- youmubot/src/commands/osu/hook.rs | 2 +- youmubot/src/commands/osu/mod.rs | 2 +- youmubot/src/db.rs | 5 +-- youmubot/src/main.rs | 32 ++++++++++++------- 20 files changed, 88 insertions(+), 44 deletions(-) create mode 100644 youmubot-prelude/Cargo.toml rename {youmubot/src/commands => youmubot-prelude/src}/announcer.rs (93%) rename {youmubot/src/commands => youmubot-prelude/src}/args.rs (100%) rename youmubot/src/prelude.rs => youmubot-prelude/src/lib.rs (80%) create mode 100644 youmubot-prelude/src/setup.rs diff --git a/Cargo.lock b/Cargo.lock index 5295786..8828ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1689,6 +1689,7 @@ dependencies = [ "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "youmubot-db 0.1.0", "youmubot-osu 0.1.0", + "youmubot-prelude 0.1.0", ] [[package]] @@ -1715,6 +1716,15 @@ dependencies = [ "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "youmubot-prelude" +version = "0.1.0" +dependencies = [ + "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "youmubot-db 0.1.0", +] + [metadata] "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" diff --git a/Cargo.toml b/Cargo.toml index 1be4f54..3bc9602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "youmubot-prelude", "youmubot-db", "youmubot-osu", "youmubot", diff --git a/youmubot-prelude/Cargo.toml b/youmubot-prelude/Cargo.toml new file mode 100644 index 0000000..5e8e18b --- /dev/null +++ b/youmubot-prelude/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "youmubot-prelude" +version = "0.1.0" +authors = ["Natsu Kagami "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serenity = "0.8" +youmubot-db = { path = "../youmubot-db" } +reqwest = "0.10" diff --git a/youmubot/src/commands/announcer.rs b/youmubot-prelude/src/announcer.rs similarity index 93% rename from youmubot/src/commands/announcer.rs rename to youmubot-prelude/src/announcer.rs index d36d940..47e7400 100644 --- a/youmubot/src/commands/announcer.rs +++ b/youmubot-prelude/src/announcer.rs @@ -1,14 +1,16 @@ -use crate::db::AnnouncerChannels; -use crate::prelude::*; +use crate::AppData; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, http::{CacheHttp, Http}, model::id::{ChannelId, GuildId, UserId}, }; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, thread::{spawn, JoinHandle}, }; +use youmubot_db::DB; + +pub(crate) type AnnouncerChannels = DB>>; pub trait Announcer { fn announcer_key() -> &'static str; diff --git a/youmubot/src/commands/args.rs b/youmubot-prelude/src/args.rs similarity index 100% rename from youmubot/src/commands/args.rs rename to youmubot-prelude/src/args.rs diff --git a/youmubot/src/prelude.rs b/youmubot-prelude/src/lib.rs similarity index 80% rename from youmubot/src/prelude.rs rename to youmubot-prelude/src/lib.rs index dc98a94..c391553 100644 --- a/youmubot/src/prelude.rs +++ b/youmubot-prelude/src/lib.rs @@ -1,24 +1,29 @@ -use std::sync::Arc; -use youmubot_osu::Client as OsuHttpClient; - pub use serenity::prelude::*; +use std::sync::Arc; + +pub mod announcer; +pub mod args; +pub mod setup; + +pub use announcer::Announcer; +pub use args::Duration; /// The global app data. pub type AppData = Arc>; /// The HTTP client. -pub(crate) struct HTTPClient; +pub struct HTTPClient; impl TypeMapKey for HTTPClient { type Value = reqwest::blocking::Client; } /// The osu! client. -pub(crate) struct OsuClient; +// pub(crate) struct OsuClient; -impl TypeMapKey for OsuClient { - type Value = OsuHttpClient; -} +// impl TypeMapKey for OsuClient { +// type Value = OsuHttpClient; +// } /// The TypeMap trait that allows TypeMaps to quickly get a clonable item. pub trait GetCloned { diff --git a/youmubot-prelude/src/setup.rs b/youmubot-prelude/src/setup.rs new file mode 100644 index 0000000..4aadd37 --- /dev/null +++ b/youmubot-prelude/src/setup.rs @@ -0,0 +1,14 @@ +use serenity::{framework::standard::StandardFramework, prelude::*}; +use std::path::Path; + +/// Set up the prelude libraries. +/// +/// Panics on failure: Youmubot should *NOT* attempt to continue when this function fails. +pub fn setup_prelude(db_path: &Path, data: &mut ShareMap, _: &mut StandardFramework) { + // Setup the announcer DB. + crate::announcer::AnnouncerChannels::insert_into(data, db_path.join("announcers.yaml")) + .expect("Announcers DB set up"); + + data.insert::(reqwest::blocking::Client::new()) + .expect("Should be able to insert"); +} diff --git a/youmubot/Cargo.toml b/youmubot/Cargo.toml index 3114212..40b31d1 100644 --- a/youmubot/Cargo.toml +++ b/youmubot/Cargo.toml @@ -19,4 +19,5 @@ lazy_static = "1" youmubot-osu = { path = "../youmubot-osu" } rayon = "1.1" youmubot-db = { path = "../youmubot-db" } +youmubot-prelude = { path = "../youmubot-prelude" } diff --git a/youmubot/src/commands/admin/mod.rs b/youmubot/src/commands/admin/mod.rs index c63a979..12849da 100644 --- a/youmubot/src/commands/admin/mod.rs +++ b/youmubot/src/commands/admin/mod.rs @@ -1,4 +1,3 @@ -use serenity::prelude::*; use serenity::{ framework::standard::{ macros::{command, group}, @@ -11,6 +10,7 @@ use serenity::{ }; use soft_ban::{SOFT_BAN_COMMAND, SOFT_BAN_INIT_COMMAND}; use std::{thread::sleep, time::Duration}; +use youmubot_prelude::*; mod soft_ban; pub use soft_ban::watch_soft_bans; diff --git a/youmubot/src/commands/admin/soft_ban.rs b/youmubot/src/commands/admin/soft_ban.rs index 24f48ad..371272f 100644 --- a/youmubot/src/commands/admin/soft_ban.rs +++ b/youmubot/src/commands/admin/soft_ban.rs @@ -3,7 +3,6 @@ use crate::{ db::{ServerSoftBans, SoftBans}, }; use chrono::offset::Utc; -use serenity::prelude::*; use serenity::{ framework::standard::{macros::command, Args, CommandError as Error, CommandResult}, model::{ @@ -12,6 +11,7 @@ use serenity::{ }, }; use std::cmp::max; +use youmubot_prelude::*; #[command] #[required_permissions(ADMINISTRATOR)] diff --git a/youmubot/src/commands/community/mod.rs b/youmubot/src/commands/community/mod.rs index 0886bcf..4d90da4 100644 --- a/youmubot/src/commands/community/mod.rs +++ b/youmubot/src/commands/community/mod.rs @@ -2,7 +2,6 @@ use rand::{ distributions::{Distribution, Uniform}, thread_rng, }; -use serenity::prelude::*; use serenity::{ framework::standard::{ macros::{command, group}, @@ -14,6 +13,7 @@ use serenity::{ }, utils::MessageBuilder, }; +use youmubot_prelude::*; mod votes; diff --git a/youmubot/src/commands/community/votes.rs b/youmubot/src/commands/community/votes.rs index 67de2e8..072c81c 100644 --- a/youmubot/src/commands/community/votes.rs +++ b/youmubot/src/commands/community/votes.rs @@ -1,6 +1,5 @@ use crate::commands::args::Duration as ParseDuration; use serenity::framework::standard::CommandError as Error; -use serenity::prelude::*; use serenity::{ framework::standard::{macros::command, Args, CommandResult}, model::{ @@ -12,6 +11,7 @@ use serenity::{ use std::collections::HashMap as Map; use std::thread; use std::time::Duration; +use youmubot_prelude::*; #[command] #[description = "🎌 Cast a poll upon everyone and ask them for opinions!"] diff --git a/youmubot/src/commands/fun/images.rs b/youmubot/src/commands/fun/images.rs index 0898a29..ae19783 100644 --- a/youmubot/src/commands/fun/images.rs +++ b/youmubot/src/commands/fun/images.rs @@ -1,4 +1,3 @@ -use crate::prelude::*; use serde::Deserialize; use serenity::framework::standard::CommandError as Error; use serenity::{ @@ -9,6 +8,7 @@ use serenity::{ model::channel::{Channel, Message}, }; use std::string::ToString; +use youmubot_prelude::*; #[command] #[checks(nsfw)] diff --git a/youmubot/src/commands/fun/mod.rs b/youmubot/src/commands/fun/mod.rs index a61fb02..57640fc 100644 --- a/youmubot/src/commands/fun/mod.rs +++ b/youmubot/src/commands/fun/mod.rs @@ -2,7 +2,6 @@ use rand::{ distributions::{Distribution, Uniform}, thread_rng, }; -use serenity::prelude::*; use serenity::{ framework::standard::{ macros::{command, group}, @@ -11,6 +10,7 @@ use serenity::{ model::{channel::Message, id::UserId}, utils::MessageBuilder, }; +use youmubot_prelude::*; mod images; mod names; diff --git a/youmubot/src/commands/mod.rs b/youmubot/src/commands/mod.rs index 0160e99..3506467 100644 --- a/youmubot/src/commands/mod.rs +++ b/youmubot/src/commands/mod.rs @@ -1,4 +1,3 @@ -use serenity::prelude::*; use serenity::{ framework::standard::{ help_commands, macros::help, Args, CommandGroup, CommandResult, HelpOptions, @@ -6,9 +5,7 @@ use serenity::{ model::{channel::Message, id::UserId}, }; use std::collections::HashSet; - -mod announcer; -mod args; +use youmubot_prelude::*; pub mod admin; pub mod community; diff --git a/youmubot/src/commands/osu/announcer.rs b/youmubot/src/commands/osu/announcer.rs index a0c80d7..781a105 100644 --- a/youmubot/src/commands/osu/announcer.rs +++ b/youmubot/src/commands/osu/announcer.rs @@ -1,9 +1,5 @@ use super::{embeds::score_embed, BeatmapWithMode}; -use crate::{ - commands::announcer::Announcer, - db::{OsuSavedUsers, OsuUser}, - prelude::*, -}; +use crate::db::{OsuSavedUsers, OsuUser}; use rayon::prelude::*; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, @@ -15,6 +11,7 @@ use youmubot_osu::{ request::{BeatmapRequestKind, UserID}, Client as Osu, }; +use youmubot_prelude::*; /// Announce osu! top scores. pub struct OsuAnnouncer; diff --git a/youmubot/src/commands/osu/hook.rs b/youmubot/src/commands/osu/hook.rs index 2b3bf03..0f1b8f0 100644 --- a/youmubot/src/commands/osu/hook.rs +++ b/youmubot/src/commands/osu/hook.rs @@ -1,4 +1,3 @@ -use crate::prelude::*; use lazy_static::lazy_static; use regex::Regex; use serenity::{ @@ -11,6 +10,7 @@ use youmubot_osu::{ models::{Beatmap, Mode}, request::BeatmapRequestKind, }; +use youmubot_prelude::*; use super::embeds::{beatmap_embed, beatmapset_embed}; diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot/src/commands/osu/mod.rs index 4f63d2d..d33892a 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot/src/commands/osu/mod.rs @@ -1,5 +1,4 @@ use crate::db::{OsuSavedUsers, OsuUser}; -use crate::prelude::*; use serenity::{ framework::standard::{ macros::{command, group}, @@ -13,6 +12,7 @@ use youmubot_osu::{ models::{Beatmap, Mode, User}, request::{BeatmapRequestKind, UserID}, }; +use youmubot_prelude::*; mod announcer; mod cache; diff --git a/youmubot/src/db.rs b/youmubot/src/db.rs index c6b7a1d..21ec47a 100644 --- a/youmubot/src/db.rs +++ b/youmubot/src/db.rs @@ -12,9 +12,6 @@ use std::path::PathBuf; use youmubot_db::{GuildMap, DB}; use youmubot_osu::models::{Beatmap, Mode}; -/// A map from announcer keys to guild IDs and to channels. -pub type AnnouncerChannels = DB>>; - /// A list of SoftBans for all servers. pub type SoftBans = DB>; @@ -34,7 +31,7 @@ pub fn setup_db(client: &mut Client) -> Result<(), Error> { SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?; OsuSavedUsers::insert_into(&mut *data, &path.join("osu_saved_users.yaml"))?; OsuLastBeatmap::insert_into(&mut *data, &path.join("last_beatmaps.yaml"))?; - AnnouncerChannels::insert_into(&mut *data, &path.join("announcers.yaml"))?; + // AnnouncerChannels::insert_into(&mut *data, &path.join("announcers.yaml"))?; Ok(()) } diff --git a/youmubot/src/main.rs b/youmubot/src/main.rs index 5227392..c8c3f9a 100644 --- a/youmubot/src/main.rs +++ b/youmubot/src/main.rs @@ -1,19 +1,16 @@ use dotenv; use dotenv::var; -use reqwest; use serenity::{ framework::standard::{DispatchError, StandardFramework}, model::{channel::Message, gateway}, }; use youmubot_osu::Client as OsuApiClient; +use youmubot_prelude::*; mod commands; mod db; -mod prelude; use commands::osu::OsuAnnouncer; -use commands::Announcer; -use prelude::*; const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [commands::osu::hook]; @@ -40,16 +37,30 @@ fn main() { // Collect the token let token = var("TOKEN").expect("Please set TOKEN as the Discord Bot's token to be used."); // Attempt to connect and set up a framework - setup_framework(Client::new(token, Handler).expect("Cannot connect...")) + Client::new(token, Handler).expect("Cannot connect") }; + // Set up base framework + let mut fw = setup_framework(&client); + + // Setup each package starting from the prelude. + { + let mut data = client.data.write(); + let db_path = var("DBPATH") + .map(|v| std::path::PathBuf::from(v)) + .unwrap_or_else(|e| { + println!("No DBPATH set up ({:?}), using `/data`", e); + std::path::PathBuf::from("data") + }); + youmubot_prelude::setup::setup_prelude(&db_path, &mut data, &mut fw); + } + // Setup initial data db::setup_db(&mut client).expect("Setup db should succeed"); // Setup shared instances of things { let mut data = client.data.write(); - let http_client = reqwest::blocking::Client::new(); - data.insert::(http_client.clone()); + let http_client = data.get_cloned::(); data.insert::(OsuApiClient::new( http_client.clone(), var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."), @@ -71,7 +82,7 @@ fn main() { } // Sets up a framework for a client -fn setup_framework(mut client: Client) -> Client { +fn setup_framework(client: &Client) -> StandardFramework { // Collect owners let owner = client .cache_and_http @@ -80,8 +91,7 @@ fn setup_framework(mut client: Client) -> Client { .expect("Should be able to get app info") .owner; - client.with_framework( - StandardFramework::new() + StandardFramework::new() .configure(|c| { c.with_whitespace(false) .prefix("y!") @@ -141,6 +151,4 @@ fn setup_framework(mut client: Client) -> Client { .group(&commands::FUN_GROUP) .group(&commands::COMMUNITY_GROUP) .group(&commands::OSU_GROUP) - ); - client } From aec9cd130d8962a85278b0e31b6c3aca9f57e249 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 5 Feb 2020 17:25:34 -0500 Subject: [PATCH 6/8] Split youmubot-osu --- Cargo.lock | 8 +-- youmubot-osu/Cargo.toml | 6 +++ .../src/discord}/announcer.rs | 14 ++--- .../osu => youmubot-osu/src/discord}/cache.rs | 2 +- youmubot-osu/src/discord/db.rs | 22 ++++++++ .../src/discord}/embeds.rs | 4 +- .../osu => youmubot-osu/src/discord}/hook.rs | 9 ++-- .../osu => youmubot-osu/src/discord}/mod.rs | 52 +++++++++++++++++-- youmubot-osu/src/lib.rs | 2 +- youmubot-prelude/src/lib.rs | 7 --- youmubot-prelude/src/setup.rs | 3 +- youmubot/Cargo.toml | 3 -- youmubot/src/commands/mod.rs | 4 -- youmubot/src/db.rs | 31 ++--------- youmubot/src/main.rs | 27 +++------- 15 files changed, 108 insertions(+), 86 deletions(-) rename {youmubot/src/commands/osu => youmubot-osu/src/discord}/announcer.rs (96%) rename {youmubot/src/commands/osu => youmubot-osu/src/discord}/cache.rs (96%) create mode 100644 youmubot-osu/src/discord/db.rs rename {youmubot/src/commands/osu => youmubot-osu/src/discord}/embeds.rs (99%) rename {youmubot/src/commands/osu => youmubot-osu/src/discord}/hook.rs (99%) rename {youmubot/src/commands/osu => youmubot-osu/src/discord}/mod.rs (90%) diff --git a/Cargo.lock b/Cargo.lock index 8828ed8..f3fabf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1679,10 +1679,7 @@ version = "0.1.0" dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1710,10 +1707,15 @@ version = "0.1.0" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "youmubot-db 0.1.0", + "youmubot-prelude 0.1.0", ] [[package]] diff --git a/youmubot-osu/Cargo.toml b/youmubot-osu/Cargo.toml index 5aa5545..d851508 100644 --- a/youmubot-osu/Cargo.toml +++ b/youmubot-osu/Cargo.toml @@ -12,6 +12,12 @@ chrono = "0.4.10" reqwest = "0.10.1" serde = { version = "1.0", features = ["derive"] } bitflags = "1" +rayon = "1.1" +lazy_static = "1" +regex = "1" + +youmubot-db = { path = "../youmubot-db" } +youmubot-prelude = { path = "../youmubot-prelude" } [dev-dependencies] serde_json = "1" diff --git a/youmubot/src/commands/osu/announcer.rs b/youmubot-osu/src/discord/announcer.rs similarity index 96% rename from youmubot/src/commands/osu/announcer.rs rename to youmubot-osu/src/discord/announcer.rs index 781a105..8ab7e8c 100644 --- a/youmubot/src/commands/osu/announcer.rs +++ b/youmubot-osu/src/discord/announcer.rs @@ -1,16 +1,16 @@ -use super::{embeds::score_embed, BeatmapWithMode}; -use crate::db::{OsuSavedUsers, OsuUser}; +use super::db::{OsuSavedUsers, OsuUser}; +use super::{embeds::score_embed, BeatmapWithMode, OsuClient}; +use crate::{ + models::{Mode, Score}, + request::{BeatmapRequestKind, UserID}, + Client as Osu, +}; use rayon::prelude::*; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, http::Http, model::id::{ChannelId, UserId}, }; -use youmubot_osu::{ - models::{Mode, Score}, - request::{BeatmapRequestKind, UserID}, - Client as Osu, -}; use youmubot_prelude::*; /// Announce osu! top scores. diff --git a/youmubot/src/commands/osu/cache.rs b/youmubot-osu/src/discord/cache.rs similarity index 96% rename from youmubot/src/commands/osu/cache.rs rename to youmubot-osu/src/discord/cache.rs index 8e3af26..9dc2d63 100644 --- a/youmubot/src/commands/osu/cache.rs +++ b/youmubot-osu/src/discord/cache.rs @@ -1,5 +1,5 @@ +use super::db::OsuLastBeatmap; use super::BeatmapWithMode; -use crate::db::OsuLastBeatmap; use serenity::{ framework::standard::{CommandError as Error, CommandResult}, model::id::ChannelId, diff --git a/youmubot-osu/src/discord/db.rs b/youmubot-osu/src/discord/db.rs new file mode 100644 index 0000000..a24d23b --- /dev/null +++ b/youmubot-osu/src/discord/db.rs @@ -0,0 +1,22 @@ +use chrono::{DateTime, Utc}; + +use serde::{Deserialize, Serialize}; +use serenity::{ + model::id::{ChannelId, UserId}, +}; +use std::collections::HashMap; +use youmubot_db::{DB}; +use crate::models::{Beatmap, Mode}; + +/// Save the user IDs. +pub type OsuSavedUsers = DB>; + +/// Save each channel's last requested beatmap. +pub type OsuLastBeatmap = DB>; + +/// An osu! saved user. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct OsuUser { + pub id: u64, + pub last_update: DateTime, +} diff --git a/youmubot/src/commands/osu/embeds.rs b/youmubot-osu/src/discord/embeds.rs similarity index 99% rename from youmubot/src/commands/osu/embeds.rs rename to youmubot-osu/src/discord/embeds.rs index 2afa35f..d2318a7 100644 --- a/youmubot/src/commands/osu/embeds.rs +++ b/youmubot-osu/src/discord/embeds.rs @@ -1,8 +1,8 @@ use super::BeatmapWithMode; -use crate::commands::args::Duration; +use crate::models::{Beatmap, Mode, Rank, Score, User}; use chrono::Utc; use serenity::{builder::CreateEmbed, utils::MessageBuilder}; -use youmubot_osu::models::{Beatmap, Mode, Rank, Score, User}; +use youmubot_prelude::*; fn format_mode(actual: Mode, original: Mode) -> String { if actual == original { diff --git a/youmubot/src/commands/osu/hook.rs b/youmubot-osu/src/discord/hook.rs similarity index 99% rename from youmubot/src/commands/osu/hook.rs rename to youmubot-osu/src/discord/hook.rs index 0f1b8f0..62c2bcf 100644 --- a/youmubot/src/commands/osu/hook.rs +++ b/youmubot-osu/src/discord/hook.rs @@ -1,3 +1,8 @@ +use super::OsuClient; +use crate::{ + models::{Beatmap, Mode}, + request::BeatmapRequestKind, +}; use lazy_static::lazy_static; use regex::Regex; use serenity::{ @@ -6,10 +11,6 @@ use serenity::{ model::channel::Message, utils::MessageBuilder, }; -use youmubot_osu::{ - models::{Beatmap, Mode}, - request::BeatmapRequestKind, -}; use youmubot_prelude::*; use super::embeds::{beatmap_embed, beatmapset_embed}; diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot-osu/src/discord/mod.rs similarity index 90% rename from youmubot/src/commands/osu/mod.rs rename to youmubot-osu/src/discord/mod.rs index d33892a..9d29fd8 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot-osu/src/discord/mod.rs @@ -1,4 +1,8 @@ -use crate::db::{OsuSavedUsers, OsuUser}; +use crate::{ + models::{Beatmap, Mode, User}, + request::{BeatmapRequestKind, UserID}, + Client as OsuHttpClient, +}; use serenity::{ framework::standard::{ macros::{command, group}, @@ -8,21 +12,59 @@ use serenity::{ utils::MessageBuilder, }; use std::str::FromStr; -use youmubot_osu::{ - models::{Beatmap, Mode, User}, - request::{BeatmapRequestKind, UserID}, -}; use youmubot_prelude::*; mod announcer; mod cache; +mod db; pub(crate) mod embeds; mod hook; pub use announcer::OsuAnnouncer; +use db::OsuUser; +use db::{OsuLastBeatmap, OsuSavedUsers}; use embeds::{beatmap_embed, score_embed, user_embed}; pub use hook::hook; +/// The osu! client. +pub(crate) struct OsuClient; + +impl TypeMapKey for OsuClient { + type Value = OsuHttpClient; +} + +/// Sets up the osu! command handling section. +/// +/// This automatically enables: +/// - Related databases +/// - An announcer system (that will eventually be revamped) +/// - The osu! API client. +/// +/// This does NOT automatically enable: +/// - Commands on the "osu" prefix +/// - Hooks. Hooks are completely opt-in. +/// +pub fn setup( + path: &std::path::Path, + client: &serenity::client::Client, + data: &mut ShareMap, +) -> CommandResult { + // Databases + OsuSavedUsers::insert_into(&mut *data, &path.join("osu_saved_users.yaml"))?; + OsuLastBeatmap::insert_into(&mut *data, &path.join("last_beatmaps.yaml"))?; + + // API client + let http_client = data.get_cloned::(); + data.insert::(OsuHttpClient::new( + http_client, + std::env::var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."), + )); + + // Announcer + OsuAnnouncer::scan(&client, std::time::Duration::from_secs(300)); + Ok(()) +} + #[group] #[prefix = "osu"] #[description = "osu! related commands."] diff --git a/youmubot-osu/src/lib.rs b/youmubot-osu/src/lib.rs index fa62543..035cfea 100644 --- a/youmubot-osu/src/lib.rs +++ b/youmubot-osu/src/lib.rs @@ -1,5 +1,5 @@ +pub mod discord; pub mod models; - pub mod request; #[cfg(test)] diff --git a/youmubot-prelude/src/lib.rs b/youmubot-prelude/src/lib.rs index c391553..1b73b8c 100644 --- a/youmubot-prelude/src/lib.rs +++ b/youmubot-prelude/src/lib.rs @@ -18,13 +18,6 @@ impl TypeMapKey for HTTPClient { type Value = reqwest::blocking::Client; } -/// The osu! client. -// pub(crate) struct OsuClient; - -// impl TypeMapKey for OsuClient { -// type Value = OsuHttpClient; -// } - /// The TypeMap trait that allows TypeMaps to quickly get a clonable item. pub trait GetCloned { /// Gets an item from the store, cloned. diff --git a/youmubot-prelude/src/setup.rs b/youmubot-prelude/src/setup.rs index 4aadd37..f503922 100644 --- a/youmubot-prelude/src/setup.rs +++ b/youmubot-prelude/src/setup.rs @@ -9,6 +9,5 @@ pub fn setup_prelude(db_path: &Path, data: &mut ShareMap, _: &mut StandardFramew crate::announcer::AnnouncerChannels::insert_into(data, db_path.join("announcers.yaml")) .expect("Announcers DB set up"); - data.insert::(reqwest::blocking::Client::new()) - .expect("Should be able to insert"); + data.insert::(reqwest::blocking::Client::new()); } diff --git a/youmubot/Cargo.toml b/youmubot/Cargo.toml index 40b31d1..f263819 100644 --- a/youmubot/Cargo.toml +++ b/youmubot/Cargo.toml @@ -14,10 +14,7 @@ chrono = "0.4.9" rand = "0.7.2" static_assertions = "1.1.0" reqwest = "0.10.1" -regex = "1" -lazy_static = "1" youmubot-osu = { path = "../youmubot-osu" } -rayon = "1.1" youmubot-db = { path = "../youmubot-db" } youmubot-prelude = { path = "../youmubot-prelude" } diff --git a/youmubot/src/commands/mod.rs b/youmubot/src/commands/mod.rs index 3506467..4562bc5 100644 --- a/youmubot/src/commands/mod.rs +++ b/youmubot/src/commands/mod.rs @@ -10,14 +10,10 @@ use youmubot_prelude::*; pub mod admin; pub mod community; pub mod fun; -pub mod osu; pub use admin::ADMIN_GROUP; pub use community::COMMUNITY_GROUP; pub use fun::FUN_GROUP; -pub use osu::OSU_GROUP; - -pub use announcer::Announcer; // A help command #[help] diff --git a/youmubot/src/db.rs b/youmubot/src/db.rs index 21ec47a..cb2250d 100644 --- a/youmubot/src/db.rs +++ b/youmubot/src/db.rs @@ -1,37 +1,21 @@ use chrono::{DateTime, Utc}; -use dotenv::var; use serde::{Deserialize, Serialize}; use serenity::{ - client::Client, framework::standard::CommandError as Error, - model::id::{ChannelId, RoleId, UserId}, + model::id::{RoleId, UserId}, }; use std::collections::HashMap; -use std::path::PathBuf; +use std::path::Path; use youmubot_db::{GuildMap, DB}; -use youmubot_osu::models::{Beatmap, Mode}; +use youmubot_prelude::*; /// A list of SoftBans for all servers. pub type SoftBans = DB>; -/// Save the user IDs. -pub type OsuSavedUsers = DB>; - -/// Save each channel's last requested beatmap. -pub type OsuLastBeatmap = DB>; - /// Sets up all databases in the client. -pub fn setup_db(client: &mut Client) -> Result<(), Error> { - let path: PathBuf = var("DBPATH").map(|v| PathBuf::from(v)).unwrap_or_else(|e| { - println!("No DBPATH set up ({:?}), using `/data`", e); - PathBuf::from("data") - }); - let mut data = client.data.write(); +pub fn setup_db(path: &Path, data: &mut ShareMap) -> Result<(), Error> { SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?; - OsuSavedUsers::insert_into(&mut *data, &path.join("osu_saved_users.yaml"))?; - OsuLastBeatmap::insert_into(&mut *data, &path.join("last_beatmaps.yaml"))?; - // AnnouncerChannels::insert_into(&mut *data, &path.join("announcers.yaml"))?; Ok(()) } @@ -62,10 +46,3 @@ pub struct ImplementedSoftBans { /// List of all to-unban people. pub periodical_bans: HashMap>, } - -/// An osu! saved user. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct OsuUser { - pub id: u64, - pub last_update: DateTime, -} diff --git a/youmubot/src/main.rs b/youmubot/src/main.rs index c8c3f9a..e156192 100644 --- a/youmubot/src/main.rs +++ b/youmubot/src/main.rs @@ -4,15 +4,13 @@ use serenity::{ framework::standard::{DispatchError, StandardFramework}, model::{channel::Message, gateway}, }; -use youmubot_osu::Client as OsuApiClient; +use youmubot_osu::discord::{setup as setup_osu, OSU_GROUP}; use youmubot_prelude::*; mod commands; mod db; -use commands::osu::OsuAnnouncer; - -const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [commands::osu::hook]; +const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [youmubot_osu::discord::hook]; struct Handler; @@ -53,26 +51,15 @@ fn main() { std::path::PathBuf::from("data") }); youmubot_prelude::setup::setup_prelude(&db_path, &mut data, &mut fw); - } - - // Setup initial data - db::setup_db(&mut client).expect("Setup db should succeed"); - // Setup shared instances of things - { - let mut data = client.data.write(); - let http_client = data.get_cloned::(); - data.insert::(OsuApiClient::new( - http_client.clone(), - var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."), - )); + // Setup initial data + db::setup_db(&db_path, &mut data).expect("Setup db should succeed"); + // osu! + setup_osu(&db_path, &client, &mut data).expect("osu! is initialized"); } // Create handler threads std::thread::spawn(commands::admin::watch_soft_bans(&mut client)); - // Announcers - OsuAnnouncer::scan(&client, std::time::Duration::from_secs(300)); - println!("Starting..."); if let Err(v) = client.start() { panic!(v) @@ -150,5 +137,5 @@ fn setup_framework(client: &Client) -> StandardFramework { .group(&commands::ADMIN_GROUP) .group(&commands::FUN_GROUP) .group(&commands::COMMUNITY_GROUP) - .group(&commands::OSU_GROUP) + .group(&OSU_GROUP) } From 84150cd82ede73a65f70dc62821e803ec8a7a30d Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 5 Feb 2020 17:51:14 -0500 Subject: [PATCH 7/8] Split youmubot-core --- Cargo.lock | 15 ++++++++++++--- Cargo.toml | 1 + youmubot-core/Cargo.toml | 17 +++++++++++++++++ .../src}/admin/mod.rs | 0 .../src}/admin/soft_ban.rs | 7 ++----- .../src}/community/mod.rs | 0 .../src}/community/votes.rs | 3 +-- {youmubot => youmubot-core}/src/db.rs | 10 ---------- .../src}/fun/images.rs | 2 +- .../commands => youmubot-core/src}/fun/mod.rs | 0 .../src}/fun/names.rs | 0 .../mod.rs => youmubot-core/src/lib.rs | 15 +++++++++++++++ youmubot/Cargo.toml | 6 +----- youmubot/src/main.rs | 18 ++++++------------ 14 files changed, 56 insertions(+), 38 deletions(-) create mode 100644 youmubot-core/Cargo.toml rename {youmubot/src/commands => youmubot-core/src}/admin/mod.rs (100%) rename {youmubot/src/commands => youmubot-core/src}/admin/soft_ban.rs (97%) rename {youmubot/src/commands => youmubot-core/src}/community/mod.rs (100%) rename {youmubot/src/commands => youmubot-core/src}/community/votes.rs (99%) rename {youmubot => youmubot-core}/src/db.rs (78%) rename {youmubot/src/commands => youmubot-core/src}/fun/images.rs (98%) rename {youmubot/src/commands => youmubot-core/src}/fun/mod.rs (100%) rename {youmubot/src/commands => youmubot-core/src}/fun/names.rs (100%) rename youmubot/src/commands/mod.rs => youmubot-core/src/lib.rs (63%) diff --git a/Cargo.lock b/Cargo.lock index f3fabf8..3210d13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1677,15 +1677,24 @@ dependencies = [ name = "youmubot" version = "0.1.0" dependencies = [ - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "youmubot-core 0.1.0", + "youmubot-db 0.1.0", + "youmubot-osu 0.1.0", + "youmubot-prelude 0.1.0", +] + +[[package]] +name = "youmubot-core" +version = "0.1.0" +dependencies = [ + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "youmubot-db 0.1.0", - "youmubot-osu 0.1.0", "youmubot-prelude 0.1.0", ] diff --git a/Cargo.toml b/Cargo.toml index 3bc9602..828c358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "youmubot-prelude", "youmubot-db", + "youmubot-core", "youmubot-osu", "youmubot", ] diff --git a/youmubot-core/Cargo.toml b/youmubot-core/Cargo.toml new file mode 100644 index 0000000..407431c --- /dev/null +++ b/youmubot-core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "youmubot-core" +version = "0.1.0" +authors = ["Natsu Kagami "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serenity = "0.8" +rand = "0.7" +serde = { version = "1", features = ["derive"] } +chrono = "0.4" +static_assertions = "1.1" + +youmubot-db = { path = "../youmubot-db" } +youmubot-prelude = { path = "../youmubot-prelude" } diff --git a/youmubot/src/commands/admin/mod.rs b/youmubot-core/src/admin/mod.rs similarity index 100% rename from youmubot/src/commands/admin/mod.rs rename to youmubot-core/src/admin/mod.rs diff --git a/youmubot/src/commands/admin/soft_ban.rs b/youmubot-core/src/admin/soft_ban.rs similarity index 97% rename from youmubot/src/commands/admin/soft_ban.rs rename to youmubot-core/src/admin/soft_ban.rs index 371272f..b8d6d25 100644 --- a/youmubot/src/commands/admin/soft_ban.rs +++ b/youmubot-core/src/admin/soft_ban.rs @@ -1,7 +1,4 @@ -use crate::{ - commands::args, - db::{ServerSoftBans, SoftBans}, -}; +use crate::db::{ServerSoftBans, SoftBans}; use chrono::offset::Utc; use serenity::{ framework::standard::{macros::command, Args, CommandError as Error, CommandResult}, @@ -114,7 +111,7 @@ pub fn soft_ban_init(ctx: &mut Context, msg: &Message, mut args: Args) -> Comman } // Watch the soft bans. -pub fn watch_soft_bans(client: &mut serenity::Client) -> impl FnOnce() -> () + 'static { +pub fn watch_soft_bans(client: &serenity::Client) -> impl FnOnce() -> () + 'static { let cache_http = { let cache_http = client.cache_and_http.clone(); let cache: serenity::cache::CacheRwLock = cache_http.cache.clone().into(); diff --git a/youmubot/src/commands/community/mod.rs b/youmubot-core/src/community/mod.rs similarity index 100% rename from youmubot/src/commands/community/mod.rs rename to youmubot-core/src/community/mod.rs diff --git a/youmubot/src/commands/community/votes.rs b/youmubot-core/src/community/votes.rs similarity index 99% rename from youmubot/src/commands/community/votes.rs rename to youmubot-core/src/community/votes.rs index 072c81c..b78f7fd 100644 --- a/youmubot/src/commands/community/votes.rs +++ b/youmubot-core/src/community/votes.rs @@ -1,4 +1,3 @@ -use crate::commands::args::Duration as ParseDuration; use serenity::framework::standard::CommandError as Error; use serenity::{ framework::standard::{macros::command, Args, CommandResult}, @@ -11,7 +10,7 @@ use serenity::{ use std::collections::HashMap as Map; use std::thread; use std::time::Duration; -use youmubot_prelude::*; +use youmubot_prelude::{Duration as ParseDuration, *}; #[command] #[description = "🎌 Cast a poll upon everyone and ask them for opinions!"] diff --git a/youmubot/src/db.rs b/youmubot-core/src/db.rs similarity index 78% rename from youmubot/src/db.rs rename to youmubot-core/src/db.rs index cb2250d..f7926a6 100644 --- a/youmubot/src/db.rs +++ b/youmubot-core/src/db.rs @@ -2,24 +2,14 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serenity::{ - framework::standard::CommandError as Error, model::id::{RoleId, UserId}, }; use std::collections::HashMap; -use std::path::Path; use youmubot_db::{GuildMap, DB}; -use youmubot_prelude::*; /// A list of SoftBans for all servers. pub type SoftBans = DB>; -/// Sets up all databases in the client. -pub fn setup_db(path: &Path, data: &mut ShareMap) -> Result<(), Error> { - SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?; - - Ok(()) -} - /// For the admin commands: /// - Each server might have a `soft ban` role implemented. /// - We allow periodical `soft ban` applications. diff --git a/youmubot/src/commands/fun/images.rs b/youmubot-core/src/fun/images.rs similarity index 98% rename from youmubot/src/commands/fun/images.rs rename to youmubot-core/src/fun/images.rs index ae19783..8c5c32e 100644 --- a/youmubot/src/commands/fun/images.rs +++ b/youmubot-core/src/fun/images.rs @@ -57,7 +57,7 @@ fn message_command(ctx: &mut Context, msg: &Message, args: Args, rating: Rating) // Gets an image URL. fn get_image( - client: &reqwest::blocking::Client, + client: &::Value, rating: Rating, tags: &str, ) -> Result, Error> { diff --git a/youmubot/src/commands/fun/mod.rs b/youmubot-core/src/fun/mod.rs similarity index 100% rename from youmubot/src/commands/fun/mod.rs rename to youmubot-core/src/fun/mod.rs diff --git a/youmubot/src/commands/fun/names.rs b/youmubot-core/src/fun/names.rs similarity index 100% rename from youmubot/src/commands/fun/names.rs rename to youmubot-core/src/fun/names.rs diff --git a/youmubot/src/commands/mod.rs b/youmubot-core/src/lib.rs similarity index 63% rename from youmubot/src/commands/mod.rs rename to youmubot-core/src/lib.rs index 4562bc5..b039c75 100644 --- a/youmubot/src/commands/mod.rs +++ b/youmubot-core/src/lib.rs @@ -9,12 +9,27 @@ use youmubot_prelude::*; pub mod admin; pub mod community; +mod db; pub mod fun; pub use admin::ADMIN_GROUP; pub use community::COMMUNITY_GROUP; pub use fun::FUN_GROUP; +/// Sets up all databases in the client. +pub fn setup( + path: &std::path::Path, + client: &serenity::client::Client, + data: &mut youmubot_prelude::ShareMap, +) -> serenity::framework::standard::CommandResult { + db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?; + + // Create handler threads + std::thread::spawn(admin::watch_soft_bans(client)); + + Ok(()) +} + // A help command #[help] pub fn help( diff --git a/youmubot/Cargo.toml b/youmubot/Cargo.toml index f263819..da96075 100644 --- a/youmubot/Cargo.toml +++ b/youmubot/Cargo.toml @@ -9,12 +9,8 @@ edition = "2018" [dependencies] serenity = "0.8" dotenv = "0.15" -serde = { version = "1.0", features = ["derive"] } -chrono = "0.4.9" -rand = "0.7.2" -static_assertions = "1.1.0" -reqwest = "0.10.1" youmubot-osu = { path = "../youmubot-osu" } youmubot-db = { path = "../youmubot-db" } youmubot-prelude = { path = "../youmubot-prelude" } +youmubot-core = { path = "../youmubot-core" } diff --git a/youmubot/src/main.rs b/youmubot/src/main.rs index e156192..dadc921 100644 --- a/youmubot/src/main.rs +++ b/youmubot/src/main.rs @@ -7,9 +7,6 @@ use serenity::{ use youmubot_osu::discord::{setup as setup_osu, OSU_GROUP}; use youmubot_prelude::*; -mod commands; -mod db; - const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [youmubot_osu::discord::hook]; struct Handler; @@ -51,15 +48,12 @@ fn main() { std::path::PathBuf::from("data") }); youmubot_prelude::setup::setup_prelude(&db_path, &mut data, &mut fw); - // Setup initial data - db::setup_db(&db_path, &mut data).expect("Setup db should succeed"); + // Setup core + youmubot_core::setup(&db_path, &client, &mut data).expect("Setup db should succeed"); // osu! setup_osu(&db_path, &client, &mut data).expect("osu! is initialized"); } - // Create handler threads - std::thread::spawn(commands::admin::watch_soft_bans(&mut client)); - println!("Starting..."); if let Err(v) = client.start() { panic!(v) @@ -85,7 +79,7 @@ fn setup_framework(client: &Client) -> StandardFramework { .delimiters(vec![" / ", "/ ", " /", "/"]) .owners([owner.id].iter().cloned().collect()) }) - .help(&commands::HELP) + .help(&youmubot_core::HELP) .before(|_, msg, command_name| { println!( "Got command '{}' by user '{}'", @@ -134,8 +128,8 @@ fn setup_framework(client: &Client) -> StandardFramework { c.delay(30).time_span(30).limit(1) }) // groups here - .group(&commands::ADMIN_GROUP) - .group(&commands::FUN_GROUP) - .group(&commands::COMMUNITY_GROUP) + .group(&youmubot_core::ADMIN_GROUP) + .group(&youmubot_core::FUN_GROUP) + .group(&youmubot_core::COMMUNITY_GROUP) .group(&OSU_GROUP) } From dfc8eede73afcc60a0487ba9fb0cae944fed64a6 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 5 Feb 2020 18:02:48 -0500 Subject: [PATCH 8/8] Add README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f05978d --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# youmubot + +A Discord bot made specifically for server "Dự tuyển Tổng Hợp". Written in Rust. + +All PRs welcome. + +## Project structure + +- `youmubot`: The main command. Collect configurations and dispatch commands. +- `youmubot-prelude`: Base structures and handy functions for command parsing / service handling. +- `youmubot-db`: Base database structures. +- `youmubot-core`: Core commands: admin, fun, community +- `youmubot-osu`: osu!-related commands. + +## License + +Basically MIT.