diff --git a/Cargo.lock b/Cargo.lock index b9f37cc..37efb68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,8 @@ dependencies = [ "poise", "pretty_env_logger", "rand", + "serde", + "serde_json", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 59a8595..8ee6ec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,6 @@ log = "0.4.21" poise = "0.6.1" pretty_env_logger = "0.5.0" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +serde = { version = "1.0.198", features = ["serde_derive"] } +serde_json = "1.0.116" diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..1662033 --- /dev/null +++ b/settings.json @@ -0,0 +1 @@ +{"channels":[1232064459737010247]} \ No newline at end of file diff --git a/src/command/util.rs b/src/command/util.rs index b46e17e..552e194 100644 --- a/src/command/util.rs +++ b/src/command/util.rs @@ -27,3 +27,33 @@ pub async fn info(ctx: Context<'_>) -> Result<(), Error> { info!("Executed command `info` successfully"); Ok(()) } + +/// Add information to the shared settings +#[poise::command(slash_command)] +pub async fn add_channel( + ctx: Context<'_>, + #[description = "Selected channel"] channel: Option, +) -> Result<(), Error> { + if let Some(channel_ok) = channel { + let config = &mut ctx.data().config_manager.lock().await; + let channel_id = { + u64::from(channel_ok.id()) + }; + config.channels.push(channel_id); + config.store().unwrap(); + ctx.say(format!("Successfully added <#{}> to the channel registry.", channel_id)).await?; + info!("Executed command `add_channel` successfully"); + } else { + ctx.say("Channel with supplied ID was not found.").await?; + error!("Failed to execute command `add_channel`."); + } + Ok(()) +} + +#[poise::command(slash_command)] +pub async fn list_channels(ctx: Context<'_>) -> Result<(), Error> { + let config = &mut ctx.data().config_manager.lock().await; + ctx.say(format!("Current channel IDs in registry: {:#?}", config.channels)).await?; + info!("Executed command `list_channels` successfully"); + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index be345d3..79e01df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ + +use std::sync::{Arc}; +use tokio::sync::Mutex; + use dotenv::dotenv; use poise::serenity_prelude as serenity; @@ -6,13 +10,24 @@ extern crate pretty_env_logger; #[macro_use] extern crate log; -mod command; -use crate::command::{fun::*, util::*}; +use serde::*; -struct Data {} // User data, which is stored and accessible in all command invocations +mod command; +use crate::{command::{fun::*, util::*}, settings::SettingsManager}; + +mod settings; + +struct Data { + config_manager: Arc>> +} // User data, which is stored and accessible in all command invocations type Error = Box; type Context<'a> = poise::Context<'a, Data, Error>; +#[derive(Debug, Serialize, Deserialize)] +struct Settings { + channels: Vec, +} + #[tokio::main] async fn main() { // Get secure env vars from .env file @@ -25,16 +40,21 @@ async fn main() { // Initialize logging pretty_env_logger::init(); + // Configure persistent options + let config = Settings { channels: vec![] }; + let config_manager = Arc::new(Mutex::new(SettingsManager::manage("settings.json", config))); + config_manager.lock().await.update(); + // Set up framework let framework = poise::Framework::builder() .options(poise::FrameworkOptions { - commands: vec![age(), info(), bleat()], + commands: vec![age(), info(), bleat(), add_channel(), list_channels()], ..Default::default() }) .setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; - Ok(Data {}) + Ok(Data {config_manager}) }) }) .build(); diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..4f0e647 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,66 @@ +#![allow(dead_code)] + +use std::io::{Read, Write}; +use std::ops::{Deref, DerefMut}; + +use serde::de::Deserialize; +use serde::ser::Serialize; + +/// A utility structure to manage a settings structure. +pub struct SettingsManager Deserialize<'a>> { + internal: T, + path: String, +} + +impl Deserialize<'a>> SettingsManager { + /// Instantiate new self if the path contains a valid serialization of + /// the settings structure. + pub fn load(path: &str) -> Option { + let mut file = std::fs::File::open(path).ok()?; + let mut data = String::new(); + file.read_to_string(&mut data).ok()?; + let settings = serde_json::from_str(&data).ok()?; + Some(Self { + internal: settings, + path: String::from(path), + }) + } + /// Update the data stored in the settings. + pub fn update(&mut self) -> Option<()> { + let mut file = std::fs::File::open(self.path.clone()).ok()?; + let mut data = String::new(); + file.read_to_string(&mut data).ok()?; + self.internal = serde_json::from_str(&data).ok()?; + Some(()) + } + /// Serialize settings structure to the stored path. Returns None if + /// unsuccessful. + pub fn store(&self) -> Option<()> { + let data = serde_json::to_string(&self.internal).ok()?; + let mut file = std::fs::File::create(&self.path).ok()?; + let _ = file.write(data.as_bytes()); + Some(()) + } + /// Create a new manager, passing in the path, and a structure to manage. + /// We cannot initialize a settings manager without fully initialized settings. + pub fn manage(path: &str, intake: T) -> Self { + Self { + internal: intake, + path: path.to_string(), + } + } +} + +impl Deserialize<'a>> Deref for SettingsManager { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.internal + } +} + +impl Deserialize<'a>> DerefMut for SettingsManager { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.internal + } +} \ No newline at end of file