diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..3f4e93f5ea49f5178ff0f424682cfb436f04500e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,19 @@ +image: "rust:latest" + +before_script: + - rustup component add rustfmt + - rustup component add clippy + - cargo install cargo-deb + +test: + script: + - cargo fmt -- --check + - cargo clippy --all-targets --all-features -- -D warnings + - cargo test + +build: + script: + - cargo deb + artifacts: + paths: + - target/debian/*.deb diff --git a/Cargo.lock b/Cargo.lock index c6fcb43f71f797f5345fda78890a168849c3436e..ab6188dd69e175688d8df09bab5aba45d1d0fa63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,7 +103,7 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "cat-disruptor-6500" -version = "0.1.1" +version = "0.1.2" dependencies = [ "bigdecimal", "diesel", diff --git a/Cargo.toml b/Cargo.toml index 316909dc15d4db2e979d0d599ec2032f1cb828cc..c738368f406adb776162ba684964814c61dcc44e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cat-disruptor-6500" -version = "0.1.1" +version = "0.1.2" authors = ["Stephen <stephen@stephendownward.ca>"] edition = "2018" diff --git a/rustfmt.toml b/rustfmt.toml index 230e0ae89494351df23c454dbe3793de06fbc531..3be9fc2661eac1f064b07b34b820549b2247da0e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,3 @@ tab_spaces = 4 -hard_tabs = true \ No newline at end of file +hard_tabs = true +edition = "2018" diff --git a/src/handlers/joke.rs b/src/handlers/joke.rs new file mode 100644 index 0000000000000000000000000000000000000000..7fc73ac6633b5e9d2c881213abcf91b7c52bd2c8 --- /dev/null +++ b/src/handlers/joke.rs @@ -0,0 +1,29 @@ +use crate::handlers::LineHandler; +use crate::joker::tell_joke; +use serenity::async_trait; +use serenity::model::channel::Message; +use serenity::prelude::*; + +pub struct JokeHandler; + +#[async_trait] +impl LineHandler for JokeHandler { + async fn line(&self, ctx: &Context, msg: &Message, line: &str) { + if line == "!JOKE" { + match tell_joke().await { + Some(s) => msg.channel_id.say(&ctx, s).await.unwrap(), + None => msg + .channel_id + .say(&ctx, "There was an error while fetching a joke.") + .await + .unwrap(), + }; + } + } +} + +impl Default for JokeHandler { + fn default() -> Self { + Self + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..730a63e8b2d6a1d2130b04a4c0fc8bdb0aa05827 --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,52 @@ +mod joke; +mod react; +mod xbasic; + +use crate::handlers::joke::*; +use crate::handlers::react::*; +use crate::handlers::xbasic::*; + +use serenity::async_trait; +use serenity::model::channel::Message; +use serenity::model::prelude::Ready; +use serenity::prelude::*; + +#[async_trait] +pub(crate) trait LineHandler: Send + Sync { + async fn message(&self, ctx: &Context, msg: &Message) { + for line in msg.content.split('\n') { + self.line(ctx, msg, line).await + } + } + + async fn line(&self, _ctx: &Context, _msg: &Message, _line: &str) {} +} + +pub(crate) struct Dispatcher { + handlers: Vec<Box<dyn LineHandler>>, +} + +#[async_trait] +impl EventHandler for Dispatcher { + async fn message(&self, ctx: Context, msg: Message) { + for h in &self.handlers { + h.message(&ctx, &msg).await; + } + } + + async fn ready(&self, _: Context, ready: Ready) { + println!("{} is connected", ready.user.name); + } +} + +impl Default for Dispatcher { + fn default() -> Self { + Self { + handlers: vec![ + Box::new(XbasicHandler::default()), + Box::new(JokeHandler::default()), + Box::new(ReactHandler::default()), + ], + } + } +} diff --git a/src/handlers/react.rs b/src/handlers/react.rs new file mode 100644 index 0000000000000000000000000000000000000000..b40159761f0ac24423ddcd7b7fe27f99a48abd14 --- /dev/null +++ b/src/handlers/react.rs @@ -0,0 +1,48 @@ +use crate::handlers::LineHandler; +use phf::phf_map; +use serenity::async_trait; +use serenity::model::channel::{Message, ReactionType}; +use serenity::prelude::*; +use std::str::FromStr; + +static EMOJI_MAP: phf::Map<&'static str, &'static str> = phf_map! { + "cat" => "ðŸˆ", + "chicken" => "ðŸ”", + "spaghetti" => "ðŸ", + "dog" => "ðŸ•", + "bot" => "🤖", + "mango" => "ðŸ¥", + "banana" => "ðŸŒ", + "bee" => "ðŸ" +}; + +pub struct ReactHandler; + +#[async_trait] +impl LineHandler for ReactHandler { + async fn message(&self, ctx: &Context, msg: &Message) { + for (key, value) in EMOJI_MAP.entries() { + let msg_lower = format!(" {} ", msg.content.to_lowercase()); + if msg_lower.contains(&format!(" {} ", key)) + || msg_lower.contains(&format!(" {}s ", key)) + { + let reaction_type = match ReactionType::from_str(value) { + Ok(x) => x, + Err(x) => { + println!("Could not react: {}", x); + return; + } + }; + if let Err(e) = msg.react(&ctx, reaction_type).await { + println!("Error reacting: {}", e); + } + } + } + } +} + +impl Default for ReactHandler { + fn default() -> Self { + Self + } +} diff --git a/src/handler.rs b/src/handlers/xbasic.rs similarity index 83% rename from src/handler.rs rename to src/handlers/xbasic.rs index fb79bc8c04c0570c5a6b1f8f7940b98c3a477a00..90474747d159de2fa3a1e0c8f3400c570369e58e 100644 --- a/src/handler.rs +++ b/src/handlers/xbasic.rs @@ -1,16 +1,16 @@ use crate::framebuffer::FrameBuffer; -use crate::joker::tell_joke; +use crate::handlers::LineHandler; use crate::program::Program; -use diesel::PgConnection; -use phf::phf_map; +use diesel::{Connection, PgConnection}; +use dotenv::dotenv; use serenity::async_trait; use serenity::http::AttachmentType; use serenity::model::channel::{Message, ReactionType}; use serenity::model::id::UserId; -use serenity::model::prelude::Ready; use serenity::prelude::*; use std::borrow::{Borrow, Cow}; use std::collections::HashMap; +use std::env; use std::str::FromStr; use std::sync::{Arc, Mutex}; use tokio::task; @@ -18,24 +18,6 @@ use xbasic::basic_io::BasicIO; use xbasic::expr::ExprValue; use xbasic::xbasic::XBasicBuilder; -// TODO - this file should be broken into 4 modules -// 1 that takes input messages and redirects them appropriately -// 1 that handles reacts -// 1 that handles the interpreter -// 1 that handles jokes -// Right now it's a mess - -static EMOJI_MAP: phf::Map<&'static str, &'static str> = phf_map! { -"cat" => "ðŸˆ", -"chicken" => "ðŸ”", -"spaghetti" => "ðŸ", -"dog" => "ðŸ•", -"bot" => "🤖", -"mango" => "ðŸ¥", -"banana" => "ðŸŒ", -"bee" => "ðŸ" -}; - struct DiscordIO { s: String, frame: Option<FrameBuffer>, @@ -66,19 +48,12 @@ macro_rules! get_user_programs { }; } -pub(crate) struct Handler { +pub struct XbasicHandler { programs: Arc<Mutex<HashMap<UserId, Program>>>, conn: Arc<Mutex<PgConnection>>, } -impl Handler { - pub fn new(conn: PgConnection) -> Self { - Self { - programs: Arc::new(Mutex::new(HashMap::new())), - conn: Arc::new(Mutex::new(conn)), - } - } - +impl XbasicHandler { async fn interpret_line(&self, msg: &Message, ctx: &Context, line: &str) { // TODO we lock the mutex to check, but unlock before locking again later // allows another thread to screw it up @@ -183,19 +158,8 @@ impl Handler { } } - match line { - "!START" => self.interpreter_start(msg), - "!JOKE" => { - match tell_joke().await { - Some(s) => msg.channel_id.say(&ctx, s).await.unwrap(), - None => msg - .channel_id - .say(&ctx, "There was an error while fetching a joke.") - .await - .unwrap(), - }; - } // FIXME - _ => {} + if line == "!START" { + self.interpreter_start(msg); } } @@ -282,7 +246,7 @@ impl Handler { async fn load_published_program(&self, msg: &Message, ctx: &Context, id: i32) -> Option<()> { let name = get_user_programs!(&self, &msg.author.id) - .load_published_program(&self.conn.lock().ok()?.borrow(), id)?; + .load_published_program(self.conn.lock().ok()?.borrow(), id)?; msg.channel_id .say(&ctx, format!("Loaded {} (\"{}\") into memory.", id, name)) @@ -441,32 +405,24 @@ impl Handler { } #[async_trait] -impl EventHandler for Handler { - async fn message(&self, ctx: Context, msg: Message) { - for (key, value) in EMOJI_MAP.entries() { - let msg_lower = format!(" {} ", msg.content.to_lowercase()); - if msg_lower.contains(&format!(" {} ", key)) - || msg_lower.contains(&format!(" {}s ", key)) - { - let reaction_type = match ReactionType::from_str(value) { - Ok(x) => x, - Err(x) => { - println!("Could not react: {}", x); - return; - } - }; - if let Err(e) = msg.react(&ctx, reaction_type).await { - println!("Error reacting: {}", e); - } - } - } +impl LineHandler for XbasicHandler { + async fn line(&self, ctx: &Context, msg: &Message, line: &str) { + self.interpret_line(msg, ctx, line).await; + } +} - for line in msg.content.split('\n') { - self.interpret_line(&msg, &ctx, line).await; +impl Default for XbasicHandler { + fn default() -> Self { + Self { + programs: Arc::new(Mutex::new(HashMap::new())), + conn: Arc::new(Mutex::new(establish_connection())), } } +} - async fn ready(&self, _: Context, ready: Ready) { - println!("{} is connected", ready.user.name); - } +fn establish_connection() -> PgConnection { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + PgConnection::establish(&database_url).expect("Error connecting to database") } diff --git a/src/main.rs b/src/main.rs index f748286586088c1c882f8a597e9ec86904597cd1..11cc71d446e2da1954d8ce6c7cb281a65be1640c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,34 +3,23 @@ extern crate diesel; mod config; mod framebuffer; -mod handler; +mod handlers; mod joker; mod models; mod program; mod schema; -use crate::handler::Handler; -use diesel::{Connection, PgConnection}; -use dotenv::dotenv; -use serenity::Client; -use std::env; - -fn establish_connection() -> PgConnection { - dotenv().ok(); +use crate::handlers::Dispatcher; - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - PgConnection::establish(&database_url).expect("Error connecting to database") -} +use serenity::Client; #[tokio::main] async fn main() { let config = config::get_conf(); let token = config.token; - let conn = establish_connection(); - let mut client = Client::builder(&token) - .event_handler(Handler::new(conn)) + .event_handler(Dispatcher::default()) .await .expect("Error creating client"); if let Err(e) = client.start().await {