diff --git a/Cargo.lock b/Cargo.lock index 9d022d3..0d97dfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,7 +304,10 @@ version = "0.1.0" dependencies = [ "clap", "dotenv", + "env_logger", "futures", + "lazy_static", + "log", "rand", "regex", "serenity", @@ -334,6 +337,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -561,6 +577,15 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "hyper" version = "0.13.5" @@ -967,6 +992,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.3" @@ -1401,6 +1432,15 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -1799,6 +1839,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index c35e068..e433c2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,7 @@ futures = "0.3" tokio = { version = "0.2", features = ["full"] } dotenv = "0.15" rand = "0.7" -unicode-segmentation = "1.6.0" \ No newline at end of file +unicode-segmentation = "1.6" +log = "0.4" +env_logger = "0.7" +lazy_static = "1.4" \ No newline at end of file diff --git a/src/commands/clap.rs b/src/commands/clap.rs index 5bd44fe..09ead8c 100644 --- a/src/commands/clap.rs +++ b/src/commands/clap.rs @@ -1,3 +1,4 @@ +use log::error; use serenity::framework::standard::{macros::command, Args, CommandResult}; use serenity::model::channel::Message; use serenity::prelude::Context; @@ -6,7 +7,16 @@ use serenity::prelude::Context; async fn clap(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { let resp = match args .iter() - .map(|e: Result| e.unwrap()) + .map(|arg: Result| match arg { + Ok(arg) => arg, + Err(e) => { + error!( + "Failed to cast clap arg to String; returning empty string instead: {:?}", + e + ); + String::default() + } + }) .collect::>() .as_slice() { diff --git a/src/commands/cube.rs b/src/commands/cube.rs index f92b11f..256eebd 100644 --- a/src/commands/cube.rs +++ b/src/commands/cube.rs @@ -1,3 +1,4 @@ +/// This was directly ported from the Java version. I make no quality assurances. use serenity::framework::standard::{macros::command, Args, CommandResult}; use serenity::model::channel::Message; use serenity::prelude::Context; diff --git a/src/commands/heck.rs b/src/commands/heck.rs index e95fc3e..6b365ab 100644 --- a/src/commands/heck.rs +++ b/src/commands/heck.rs @@ -7,10 +7,15 @@ use serenity::prelude::Context; async fn heck(ctx: &mut Context, msg: &Message) -> CommandResult { let db_pool = ctx.data.clone(); let mut db_pool = db_pool.write().await; - let db_pool = db_pool.get_mut::().unwrap(); + let db_pool = db_pool + .get_mut::() + .expect("No db pool in context?!"); let value = db_pool.get_heck().await; msg.channel_id - .say(ctx, format!("This command has been hecked {} times", value)) + .say( + ctx, + format!("This command has been hecked {} times", value?), + ) .await?; Ok(()) diff --git a/src/main.rs b/src/main.rs index 002716f..994c93c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,12 @@ use crate::commands::GENERAL_GROUP; use crate::passive::Handler; -use serenity::client::Client; +use env_logger; +use log::error; +use serenity::client::{validate_token, Client}; use serenity::framework::standard::StandardFramework; use std::env; use util::db::DbConnPool; +use util::error::KuranteError; mod commands; mod passive; @@ -12,28 +15,48 @@ mod util; pub(crate) const COMMAND_PREFIX: &str = "~"; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() { + std::process::exit(match run().await { + Ok(_) => 0, + Err(_) => 1, + }); +} + +async fn run() -> Result<(), KuranteError> { + // Init dotenv vars before the env logger dotenv::dotenv().ok(); + env_logger::init(); let framework = StandardFramework::new() .configure(|c| c.prefix(COMMAND_PREFIX)) .group(&GENERAL_GROUP); - let mut client = - Client::new_with_extras(&env::var("DISCORD_TOKEN").expect("token"), |extras| { - extras - .event_handler(Handler::default()) - .framework(framework) - }) - .await - .expect("Error creating client"); + let token = match env::var("DISCORD_TOKEN") { + Ok(token) => match validate_token(&token) { + Ok(_) => token, + Err(_) => { + error!("Provided token was invalid."); + return Err(KuranteError::InvalidToken); + } + }, + Err(e) => { + error!("`DISCORD_TOKEN` environment variable was not set. Unable to start bot!"); + return Err(e.into()); + } + }; + + let mut client = Client::new_with_extras(token, |extras| { + extras + .event_handler(Handler::default()) + .framework(framework) + }) + .await?; { let mut data = client.data.write().await; - data.insert::(DbConnPool::new().await); + data.insert::(DbConnPool::new().await?); } - // start listening for events by starting a single shard if let Err(why) = client.start().await { println!("An error occurred while running the client: {:?}", why); } diff --git a/src/passive/ahhhh.rs b/src/passive/ahhhh.rs index 4cf3559..76ee7d2 100644 --- a/src/passive/ahhhh.rs +++ b/src/passive/ahhhh.rs @@ -1,30 +1,12 @@ +use crate::simple_responder; +use lazy_static::lazy_static; use regex::Regex; use serenity::async_trait; use serenity::model::channel::Message; use serenity::prelude::{Context, EventHandler}; -pub(crate) struct YellResponder { - regex: Regex, -} - -impl Default for YellResponder { - fn default() -> Self { - Self { - regex: Regex::new(r"A+H{5,}").unwrap(), - } - } -} - -#[async_trait] -impl EventHandler for YellResponder { - async fn message(&self, ctx: Context, message: Message) { - let content = &message.content_safe(ctx.clone()).await; - if self.regex.is_match(content) { - message - .channel_id - .say(ctx, "its ok ur gonna get a 6* someday") - .await - .unwrap(); - } - } -} +simple_responder!( + YellResponder, + r"A+H{5,}", + "its ok ur gonna get a 6* someday" +); diff --git a/src/passive/best_doctor.rs b/src/passive/best_doctor.rs index 402010d..97e4cc3 100644 --- a/src/passive/best_doctor.rs +++ b/src/passive/best_doctor.rs @@ -1,30 +1,12 @@ +use crate::simple_responder; +use lazy_static::lazy_static; use regex::Regex; use serenity::async_trait; use serenity::model::channel::Message; use serenity::prelude::{Context, EventHandler}; -pub(crate) struct BestDoctorResponder { - regex: Regex, -} - -impl Default for BestDoctorResponder { - fn default() -> Self { - Self { - regex: Regex::new(r"[iI].*(?:best|genius) doc").unwrap(), - } - } -} - -#[async_trait] -impl EventHandler for BestDoctorResponder { - async fn message(&self, ctx: Context, message: Message) { - let content = &message.content_safe(ctx.clone()).await; - if self.regex.is_match(content) { - message - .channel_id - .say(ctx, "smol brain doctor..") - .await - .unwrap(); - } - } -} +simple_responder!( + BestDoctorResponder, + r"[iI].*(?:best|genius) doc", + "smol brain doctor..." +); diff --git a/src/passive/fufufu.rs b/src/passive/fufufu.rs index d384a4b..789b682 100644 --- a/src/passive/fufufu.rs +++ b/src/passive/fufufu.rs @@ -1,29 +1,11 @@ +use crate::simple_responder; +use lazy_static::lazy_static; use regex::Regex; use serenity::async_trait; use serenity::model::channel::Message; use serenity::prelude::{Context, EventHandler}; -pub(crate) struct FufufuResponder { - regex: Regex, -} - -impl Default for FufufuResponder { - fn default() -> Self { - Self { - regex: Regex::new(r"(?:[fF][uU]){3,}").unwrap(), - } - } -} - -#[async_trait] -impl EventHandler for FufufuResponder { - async fn message(&self, ctx: Context, message: Message) { - let content = &message.content_safe(ctx.clone()).await; - if self.regex.is_match(content) { - message.channel_id.say(ctx, get_desu()).await.unwrap(); - } - } -} +simple_responder!(FufufuResponder, r"(?:[fF][uU]){3,}", (get_desu())); const DESU_STRINGS: &[&str] = &[ "ใงใ™ใ€‚", diff --git a/src/passive/mod.rs b/src/passive/mod.rs index be46739..c00f1c6 100644 --- a/src/passive/mod.rs +++ b/src/passive/mod.rs @@ -17,9 +17,9 @@ impl Default for Handler { fn default() -> Self { Self { responders: vec![ - Box::new(BestDoctorResponder::default()), - Box::new(FufufuResponder::default()), - Box::new(YellResponder::default()), + Box::new(BestDoctorResponder), + Box::new(FufufuResponder), + Box::new(YellResponder), ], } } @@ -33,3 +33,25 @@ impl EventHandler for Handler { } } } + +#[macro_export] +macro_rules! simple_responder { + // $phrase should be `expr`, see https://github.com/dtolnay/async-trait/issues/46 + // above issue is blocked on rustc bug, see https://github.com/rust-lang/rust/issues/43081 + ($name:tt, $regex:expr, $phrase:tt) => { + lazy_static! { + static ref REGEX: Regex = Regex::new($regex).unwrap(); + } + + pub(crate) struct $name; + + #[async_trait] + impl EventHandler for $name { + async fn message(&self, ctx: Context, message: Message) { + if REGEX.is_match(&message.content_safe(ctx.clone()).await) { + message.channel_id.say(ctx, $phrase).await.unwrap(); + } + } + } + }; +} diff --git a/src/util/db.rs b/src/util/db.rs index 831ec78..efa965c 100644 --- a/src/util/db.rs +++ b/src/util/db.rs @@ -1,7 +1,7 @@ use serenity::prelude::TypeMapKey; use sqlx::{ sqlite::{SqliteConnection, SqlitePool}, - Pool, + Error, Pool, }; use std::env; @@ -12,22 +12,21 @@ pub(crate) struct DbConnPool { } impl DbConnPool { - pub async fn new() -> Self { - Self { - pool: init_pool().await, - } + pub async fn new() -> Result { + Ok(Self { + pool: init_pool().await?, + }) } - pub async fn get_heck(&self) -> i32 { + + pub async fn get_heck(&self) -> Result { sqlx::query!("UPDATE Heck SET count = count + 1") .execute(&self.pool) - .await - .unwrap(); + .await?; - sqlx::query!("SELECT count FROM Heck") + Ok(sqlx::query!("SELECT count FROM Heck") .fetch_one(&self.pool) - .await - .unwrap() - .count + .await? + .count) } } @@ -35,30 +34,26 @@ impl TypeMapKey for DbConnPool { type Value = Self; } -async fn init_pool() -> DbPool { +async fn init_pool() -> Result { let pool = SqlitePool::builder() .build(&env::var("DATABASE_URL").unwrap()) - .await - .unwrap(); + .await?; sqlx::query!( "CREATE TABLE IF NOT EXISTS Heck (id INTEGER PRIMARY KEY NOT NULL, count INTEGER NOT NULL)" ) .execute(&pool) - .await - .unwrap(); + .await?; if sqlx::query!("SELECT count FROM Heck") .fetch_all(&pool) - .await - .unwrap() + .await? .is_empty() { sqlx::query!("INSERT INTO Heck VALUES (1, 0)") .execute(&pool) - .await - .unwrap(); + .await?; } - pool + Ok(pool) } diff --git a/src/util/error.rs b/src/util/error.rs new file mode 100644 index 0000000..7442a4e --- /dev/null +++ b/src/util/error.rs @@ -0,0 +1,22 @@ +pub enum KuranteError { + MissingEnvVar(std::env::VarError), + Bot(serenity::Error), + InvalidToken, + Database(sqlx::Error), +} + +/// Generates a from implementation from the specified type to the provided +/// bunbun error. +macro_rules! from_error { + ($from:ty, $to:ident) => { + impl From<$from> for KuranteError { + fn from(e: $from) -> Self { + Self::$to(e) + } + } + }; +} + +from_error!(std::env::VarError, MissingEnvVar); +from_error!(serenity::Error, Bot); +from_error!(sqlx::Error, Database); diff --git a/src/util/mod.rs b/src/util/mod.rs index dec1023..f06d0de 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1 +1,2 @@ pub mod db; +pub mod error;