commit ad1e0602e3c36ffddb74e2d36d52bef40cae667f Author: Lucy Bladen Date: Fri Nov 12 12:02:22 2021 +0000 Initial commit diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..5cd26de --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +extern crate built; +fn main() { + built::write_built_file().expect("Failed to collate build info"); +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..3bf2e6b --- /dev/null +++ b/src/config.rs @@ -0,0 +1,125 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufReader, BufWriter}; +use std::path::PathBuf; +use serenity::model::prelude::*; +use crate::error::ManifoldResult; + +#[derive(Debug)] +pub struct ManifoldConfig { + config_path: PathBuf, + config_environment: String, + config: HashMap +} + +pub trait Config { + fn load_config(&mut self) -> ManifoldResult; + fn save_config(&self) -> ManifoldResult; + fn create_default_config(&self, config_file: &String, config_environment: &String) -> ManifoldResult; + fn get_environment(&self) -> &String; + fn get_path(&self) -> &PathBuf; + fn get_environment_mut(&mut self) -> &mut String; + fn get_path_mut(&mut self) -> &mut PathBuf; + fn get_value(&self, key: &String) -> Option<&String>; + fn set_value(&mut self, key: &String, value: &String) -> Option; + fn unset_value(&mut self, key: &String) -> Option; + fn get_role(&self, role_name: &String) -> RoleId; + fn get_channel(&self, channel_name: &String) -> ChannelId; + fn get_message(&self, message_id: &String) -> MessageId; +} + +impl ManifoldConfig { + pub fn new() -> Self { + ManifoldConfig { + config_path: Default::default(), + config_environment: "Production".to_string(), + config: HashMap::::new(), + } + } +} + +impl Config for ManifoldConfig { + fn load_config(&mut self) -> ManifoldResult { + let file = File::open(&self.config_path).expect("Could not open config file. Please check and retry."); + let reader = BufReader::new(file); + self.config = serde_json::from_reader(reader) + .map_err(|e| {error!("Error reading config as JSON: {:?}", e.to_string()); e} )?; + + let config_entry_count = self.config.len(); + + debug!("Loaded config: {:?}", &self.config); + debug!("Loaded {} entries", config_entry_count); + + Ok(config_entry_count) + } + + fn save_config(&self) -> ManifoldResult { + let file = File::create(&self.config_path).expect("Could not open config file for saving."); + let writer = BufWriter::new(file); + serde_json::to_writer(writer, &self.config)?; + + let config_entry_count = self.config.len(); + + Ok(config_entry_count) + } + + fn create_default_config(&self, config_file: &String, config_environment: &String) -> ManifoldResult { + let mut config = ManifoldConfig::new(); + config.config_environment = config_environment.to_owned(); + config.config_path = PathBuf::from(config_file); + config.config.insert(format!("{}LogChannel", config_environment), "".to_string()); + config.config.insert(format!("{}BotOwner", config_environment), "".to_string()); + config.config.insert(format!("{}AdminRole", config_environment), "".to_string()); + config.config.insert(format!("{}DJRole", config_environment), "".to_string()); + config.config.insert(format!("{}SpamChannel", config_environment), "true".to_string()); + config.config.insert(format!("{}ResponsesFilePath", config_environment), "config/responses.txt".to_string()); + + config.save_config() + } + + fn get_environment(&self) -> &String { + &self.config_environment + } + + fn get_path(&self) -> &PathBuf { + &self.config_path + } + + fn get_environment_mut(&mut self) -> &mut String { + &mut self.config_environment + } + + fn get_path_mut(&mut self) -> &mut PathBuf { + &mut self.config_path + } + + fn get_value(&self, key: &String) -> Option<&String> { + self.config.get(&format!("{}{}", &self.config_environment, key)) + } + + fn set_value(&mut self, key: &String, value: &String) -> Option { + self.config.insert(format!("{}{}", &self.config_environment, key), value.clone()) + } + + fn unset_value(&mut self, key: &String) -> Option { + self.config.remove(&*format!("{}{}", &self.config_environment, key)) + } + + fn get_role(&self, role_name: &String) -> RoleId { + let key = format!("{}Role", role_name); + + RoleId(self.get_value(&key).unwrap().parse::().unwrap()) + } + + fn get_channel(&self, channel_name: &String) -> ChannelId { + let key = format!("{}Channel", channel_name); + + ChannelId(self.get_value(&key).unwrap().parse::().unwrap()) + } + + fn get_message(&self, message_id: &String) -> MessageId { + let key = format!("{}Message", message_id); + + MessageId(self.get_value(&key).unwrap().parse::().unwrap()) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..46c79f9 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,72 @@ +use std::error::Error; +use std::fmt; +use serenity::prelude::SerenityError; + +pub type ManifoldResult = Result; + +#[derive(Debug)] +pub struct ManifoldError { + pub details: String +} + +impl ManifoldError { + pub fn new(msg: &str) -> Self { + Self { + details: msg.to_string() + } + } +} + +impl fmt::Display for ManifoldError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.details) + } +} + +impl Error for ManifoldError { + fn description(&self) -> &str { + &self.details + } +} + +impl From<()> for ManifoldError { + fn from(_err: () ) -> Self { + ManifoldError::new("Unspecified error") + } +} + +impl From for ManifoldError { + fn from(err: r2d2::Error) -> Self { + ManifoldError::new(&err.to_string()) + } +} + +impl From for ManifoldError { + fn from(err: std::io::Error) -> Self { + ManifoldError::new(&err.to_string()) + } +} + +impl From for ManifoldError { + fn from(err: serde_json::error::Error) -> Self { + ManifoldError::new(&err.to_string()) + } +} + +impl From for ManifoldError { + fn from(err: regex::Error) -> Self { + ManifoldError::new(&err.to_string()) + } +} + +impl From<&str> for ManifoldError { + fn from(err: &str) -> Self { + ManifoldError::new(&err.to_string()) + } +} + +impl From for ManifoldError { + fn from(err: SerenityError) -> Self { + ManifoldError::new(&err.to_string()) + } +} diff --git a/src/events.rs b/src/events.rs new file mode 100644 index 0000000..769e38c --- /dev/null +++ b/src/events.rs @@ -0,0 +1,48 @@ +use std::sync::atomic::AtomicBool; + +use serenity::async_trait; +use serenity::model::{ + gateway::Ready, + id::ChannelId, +}; +use serenity::prelude::{Context, EventHandler}; +use crate::config::{Config, ManifoldConfig}; + +use crate::responses::Responses; + +pub struct Handler { + timer_running: AtomicBool, +} + +impl Handler { + pub fn new() -> Self { + Handler { + timer_running: AtomicBool::from(false) + } + } +} + +#[async_trait] +impl EventHandler for Handler { + async fn ready(&self, ctx: Context, _data_about_bot: Ready) { + let data = ctx.data.read().await; + let config = match data.get::() { + Some(c) => c.lock().await, + None => return + }; + + let responses = match data.get::() { + Some(r) => r.lock().await, + None => return + }; + + let greeting = match responses.get_response(&"bot startup".to_string()) { + Some(g) => g.replace("{NAME}", &*ctx.cache.current_user().await.name).replace("{COFFEE_TYPE}", "espresso").to_string(), + None => "Manifold bot connected to discord and ready to begin broadcast operations.".to_string(), + }; + + let channel: ChannelId = config.get_channel(&"Log".to_string()); + + channel.say(&ctx, greeting).await.expect("Couldn't message log channel!"); + } +} diff --git a/src/responses.rs b/src/responses.rs new file mode 100644 index 0000000..7c29567 --- /dev/null +++ b/src/responses.rs @@ -0,0 +1,57 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use std::fs::File; + +use regex::Regex; +use rand::seq::SliceRandom; + +use crate::error::ManifoldResult; +use std::io::{BufReader, BufRead}; + +pub struct Responses { + file_content: HashMap> +} + +impl Responses { + pub fn new() -> Self { + Responses { + file_content: HashMap::>::new() + } + } + + pub fn reload(&mut self, file_path: &PathBuf) -> ManifoldResult<()> { + self.file_content = HashMap::>::new(); + + let file = File::open(file_path)?; + + let reader = BufReader::new(file); + let section_pattern = Regex::new(r"^\[([\w\s]+)]$")?; + let mut current_section = "".to_string(); + let mut last_section_content = Vec::::new(); + + for line in reader.lines() { + if let Ok(l) = line { + debug!("{:?}", &l); + if let Some(c) = section_pattern.captures(&*l) { + self.file_content.insert(current_section, last_section_content); + current_section = c.get(1).unwrap().as_str().to_string(); + last_section_content = Vec::::new(); + } else { + last_section_content.push(l) + } + } + } + + debug!("{:?}", self.file_content); + + Ok(()) + } + + pub fn get_response(&self, section: &String) -> Option<&String> { + if let Some(s) = self.file_content.get(section) { + s.choose(&mut rand::thread_rng()) + } else { + None + } + } +}