From abdcc07fe282bef15a822ff093801f16137498d3 Mon Sep 17 00:00:00 2001 From: Lucy Bladen Date: Mon, 18 Jul 2022 00:25:59 +0100 Subject: [PATCH] ManifoldConfig rework --- Cargo.toml | 1 + src/config.rs | 126 ++++++++++++------------------------------- src/core_commands.rs | 15 +++--- src/error.rs | 10 ++++ src/events.rs | 8 +-- src/lib.rs | 46 ++++++---------- 6 files changed, 70 insertions(+), 136 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7464021..814df58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ built = { version = "0.5.1", features = ["git2", "chrono"] } [dependencies] built = { version = "0.5.1", features = ["git2", "chrono"] } clap = "3.2.10" +config = { version = "0.13.1", features = [ "yaml" ] } diesel = { version = "1.4.8", features = ["sqlite", "r2d2", "chrono"] } diesel_migrations = "1.4.0" env_logger = "0.9.0" diff --git a/src/config.rs b/src/config.rs index 3bf2e6b..c564bd5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,125 +1,65 @@ -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; +use config::Config; +use serenity::model::prelude::{ChannelId, MessageId, RoleId}; +use crate::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; + pub environment: String, + pub path: PathBuf, + pub config: Config, } impl ManifoldConfig { - pub fn new() -> Self { - ManifoldConfig { - config_path: Default::default(), - config_environment: "Production".to_string(), - config: HashMap::::new(), - } - } -} + pub fn load_config(config_file: &String, bot_environment: &String) -> ManifoldResult { -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 settings = Config::builder() + .set_override("BotEnvironment", bot_environment.clone())? + .add_source(config::File::with_name(config_file)) + .add_source(config::Environment::with_prefix("MANIFOLD")) + .build()?; - let config_entry_count = self.config.len(); - - debug!("Loaded config: {:?}", &self.config); - debug!("Loaded {} entries", config_entry_count); - - Ok(config_entry_count) + Ok(Self { + environment: bot_environment.clone(), + path: PathBuf::from(config_file), + config: settings + }) } - 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) + pub fn get_environment(&self) -> &String { + &self.environment } - 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() + pub fn get_path(&self) -> &PathBuf { + &self.path } - fn get_environment(&self) -> &String { - &self.config_environment + pub fn get_environment_mut(&mut self) -> &mut String { + &mut self.environment } - fn get_path(&self) -> &PathBuf { - &self.config_path + pub fn get_path_mut(&mut self) -> &mut PathBuf { + &mut self.path } - fn get_environment_mut(&mut self) -> &mut String { - &mut self.config_environment + pub fn get_value(&self, key: &String) -> ManifoldResult { + Ok(self.config.get_string(&format!("{}{}", &self.environment, key))?) } - 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 { + pub fn get_role(&self, role_name: &String) -> ManifoldResult { let key = format!("{}Role", role_name); - RoleId(self.get_value(&key).unwrap().parse::().unwrap()) + Ok(RoleId(self.get_value(&key)?.parse::()?)) } - fn get_channel(&self, channel_name: &String) -> ChannelId { + pub fn get_channel(&self, channel_name: &String) -> ManifoldResult { let key = format!("{}Channel", channel_name); - ChannelId(self.get_value(&key).unwrap().parse::().unwrap()) + Ok(ChannelId(self.get_value(&key)?.parse::()?)) } - fn get_message(&self, message_id: &String) -> MessageId { + pub fn get_message(&self, message_id: &String) -> ManifoldResult { let key = format!("{}Message", message_id); - MessageId(self.get_value(&key).unwrap().parse::().unwrap()) + Ok(MessageId(self.get_value(&key)?.parse::()?)) } } diff --git a/src/core_commands.rs b/src/core_commands.rs index 95b9ef7..f92f484 100644 --- a/src/core_commands.rs +++ b/src/core_commands.rs @@ -7,7 +7,8 @@ use serenity::{ }, model::prelude::* }; -use crate::config::{Config, ManifoldConfig}; + +use crate::ManifoldConfig; use crate::built_info; #[command] @@ -57,8 +58,8 @@ async fn get_config(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu }; let value = match config.get_value(&key) { - Some(v) => v.clone(), - None => "not found, sorry!".to_string() + Ok(v) => v.clone(), + Err(_) => "not found, sorry!".to_string() }; msg.reply_ping(&ctx, format!("Value for key {} was {}", &key, &value)).await?; @@ -84,13 +85,11 @@ async fn set_config(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu Err(e) => { msg.reply_ping(&ctx, format!("Error parsing your message: {:?}", e)).await?; Err("ArgParse")? } }; - match config.set_value(&key, &value) { - Some(_) => msg.reply_ping(&ctx, format!("Value for key {} set to {}", &key, &value)).await?, - None => msg.reply_ping(&ctx, format!("Error setting config value")).await?, + match config.config.set(&key, value.clone()) { + Ok(_) => msg.reply_ping(&ctx, format!("Value for key {} set to {}", &key, &value)).await?, + Err(_) => msg.reply_ping(&ctx, format!("Error setting config value")).await?, }; - config.save_config()?; - Ok(()) } diff --git a/src/error.rs b/src/error.rs index bcc8f23..bd83192 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,7 @@ use std::error::Error; use std::fmt; +use std::num::ParseIntError; +use config::ConfigError; use serenity::prelude::SerenityError; use reqwest::Error as ReqwestError; @@ -77,3 +79,11 @@ impl From for ManifoldError { ManifoldError::new(&err.to_string()) } } + +impl From for ManifoldError { + fn from(err: ConfigError) -> Self { ManifoldError::new(&err.to_string()) } +} + +impl From for ManifoldError { + fn from(err: ParseIntError) -> Self { ManifoldError::new(&err.to_string()) } +} diff --git a/src/events.rs b/src/events.rs index 7668836..3a5c5ed 100644 --- a/src/events.rs +++ b/src/events.rs @@ -6,7 +6,7 @@ use serenity::model::{ id::ChannelId, }; use serenity::prelude::{Context, EventHandler}; -use crate::config::{Config, ManifoldConfig}; +use crate::ManifoldConfig; use crate::responses::Responses; @@ -38,10 +38,10 @@ impl Handler { None => "Manifold bot connected to discord and ready to begin broadcast operations.".to_string(), }; - let bot_nickname = config.get_value(&"BotNickname".to_string()).map(|n| n.as_str()); - let channel: ChannelId = config.get_channel(&"Log".to_string()); + let bot_nickname = config.get_value(&"BotNickname".to_string()).unwrap_or("BrokenManifoldBot".to_string()); + let channel: ChannelId = config.get_channel(&"Log".to_string()).expect("Specified log channel invalid or unavailable"); for guild in data_about_bot.guilds { - match guild.id.edit_nickname(&ctx, bot_nickname).await { + match guild.id.edit_nickname(&ctx, Some(&*bot_nickname)).await { Ok(()) => (), Err(e) => { error!("Error setting bot nickname (lack permission?): {:?}", e); diff --git a/src/lib.rs b/src/lib.rs index d1f68ca..fe719bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,18 +3,18 @@ use std::collections::HashSet; use std::env; -use std::fs::File; use std::ops::Deref; use std::path::PathBuf; use std::sync::Arc; use clap::ArgMatches; use diesel::r2d2::{ConnectionManager}; use diesel::SqliteConnection; -use crate::config::{Config, ManifoldConfig}; use serenity::prelude::*; use serenity::framework::standard::{*, macros::*}; use serenity::http::Http; use serenity::model::prelude::{GuildId, Message, UserId}; +use crate::config::ManifoldConfig; + use crate::error::ManifoldResult; use crate::responses::Responses; @@ -63,27 +63,13 @@ struct Core; pub async fn prepare_client(arguments: ArgMatches, mut framework: StandardFramework, event_handler: T, intents: GatewayIntents) -> ManifoldResult { let bot_environment = arguments.value_of("environment").unwrap_or("Production").to_string(); - let config_file = format!("config/{}", arguments.value_of("config-file").unwrap_or("manifold.json")); - if arguments.occurrences_of("make-config") > 0 { - if File::open(&config_file).is_ok() { - warn!("Not making a new configuration file - configuration exists at {}. Please remove this if you want to generate a fresh config, or provide an alternative path.", &config_file); - Err("ConfigExists")? - } else { - let config = ManifoldConfig::new(); - match config.create_default_config(&config_file, &bot_environment) { - Ok(_) => warn!("Writing new configuration to {}", &config_file), - Err(e) => {error!("Could not write new config! {}", e.details); Err(e)?} - } - } - Err("MadeConfig")? - } + let config_file = format!("config/{}", arguments.value_of("config-file").unwrap_or("manifold.yaml")); info!("Reading configuration..."); debug!("Configuration file path: {}", &config_file); - let config = load_config(&config_file, &bot_environment); - debug!("Current configuration: {:?}", &config); + let config = ManifoldConfig::load_config(&config_file, &bot_environment).expect(&*format!("Could not read configuration file {}", &config_file)); - let prefix = config.get_value(&"BotPrefix".to_string()).unwrap(); + let prefix = config.get_value(&"BotPrefix".to_string()).expect("Could not read bot_prefix from config."); let manager = ConnectionManager::::new("manifold.db"); let pool = r2d2::Pool::builder() @@ -92,7 +78,7 @@ pub async fn prepare_client(arguments: ArgMatches, mu .expect("Database setup error!"); let mut responses = Responses::new(); - responses.reload(&PathBuf::from(config.get_value(&"ResponsesFilePath".to_string()).unwrap_or(&"txt/responses.txt".to_string()))).expect("Could not load responses file!"); + responses.reload(&PathBuf::from(config.get_value(&"ResponsesFilePath".to_string()).unwrap_or("txt/responses.txt".to_string()))).expect("Could not load responses file!"); let token = env::var("DISCORD_TOKEN").expect( @@ -142,14 +128,6 @@ pub async fn prepare_client(arguments: ArgMatches, mu Ok(client) } -fn load_config(config_file: &String, bot_environment: &String) -> ManifoldConfig { - let mut config = ManifoldConfig::new(); - *config.get_path_mut() = PathBuf::from(config_file); - *config.get_environment_mut() = bot_environment.to_owned(); - config.load_config().expect("Could not load config - halting"); - config -} - #[help] async fn manifold_help( ctx: &Context, @@ -173,8 +151,8 @@ async fn mod_or_higher_check(ctx: &Context, msg: &Message, _: &mut Args, _: &Com }; let role_guild_config = match config.get_value(&"RoleGuild".to_string()) { - Some(rgc) => rgc, - None => { + Ok(rgc) => rgc, + Err(_) => { error!("Guild not configured"); return Err(Reason::Unknown); } @@ -189,7 +167,13 @@ async fn mod_or_higher_check(ctx: &Context, msg: &Message, _: &mut Args, _: &Com }; let guild_id = GuildId(role_guild_number); - let administrator_role = config.get_role(&"Admin".to_string()); + let administrator_role = match config.get_role(&"Admin".to_string()) { + Ok(r) => r, + Err(e) => { + error!("Get admin role from config failed: {:?}", e); + return Err(Reason::Log(e.to_string())); + } + }; if !msg.author.has_role(&ctx, guild_id, administrator_role).await.unwrap_or(false) { return Err(Reason::User("User not administrative".to_string()));