From 95e7ae384238f2eda2fc4186ab533c75d3012565 Mon Sep 17 00:00:00 2001 From: Graham Littlewood Date: Mon, 13 Apr 2020 12:21:28 -0400 Subject: [PATCH] Add macro persistence (JSON) --- Cargo.toml | 2 +- src/commands/cmd_macro.rs | 90 ++++++++++++++++++++++++++++++++------- src/main.rs | 3 +- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80cec34..eb872ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,5 @@ edition = "2018" [dependencies] serenity = "0.8" clap = "2.33.0" -json = "0.12.4" +serde_json = "1.0" regex = "1.3.6" diff --git a/src/commands/cmd_macro.rs b/src/commands/cmd_macro.rs index 7f2bc41..96ac026 100644 --- a/src/commands/cmd_macro.rs +++ b/src/commands/cmd_macro.rs @@ -5,7 +5,9 @@ use serenity::{ utils::MessageBuilder, }; -extern crate json; +extern crate serde_json; +//use serde::{Deserialize, Serialize}; +use serde_json::Result; extern crate regex; use regex::{Regex, Match}; @@ -13,33 +15,44 @@ use regex::{Regex, Match}; use core::option::Option; use std::collections::HashMap; use std::string::String; +use std::fs::{File, OpenOptions}; +use std::io::BufReader; use crate::traits::Command; +const MACRO_PATH: &str = "macros.json"; + pub struct CmdMacro { format: Regex, - macros: HashMap + macros: HashMap> } impl Command for CmdMacro { fn execute(& mut self, ctx: &Context, msg: &Message) { + let id: u64; + match msg.guild_id { + Some(g) => id = *g.as_u64(), + None => id = *msg.channel_id.as_u64(), + } if let Some(parsed) = self.format.captures(&msg.content) { if let Some(op) = parsed.get(1) { let mut response = MessageBuilder::new(); if op.as_str().to_string().to_lowercase() == "add" { - if self.add(parsed.get(2), parsed.get(3)) { + if self.add(&id, parsed.get(2), parsed.get(3)) { response.push("Successfully added macro."); + self.save_json(); } else { response.push("Missing macro argument or macro already exists."); } } else if op.as_str().to_string().to_lowercase() == "remove" { - if self.remove(parsed.get(2)) { + if self.remove(&id, parsed.get(2)) { response.push("Successfully removed macro."); + self.save_json(); } else { response.push("Macro does not exist."); } } else { - response.push(self.get(&op.as_str().to_string())); + response.push(self.get(&id, &op.as_str().to_string())); } if let Err(why) = msg.channel_id.say(&ctx.http, &response.build()) { println!("Failed to send macro message {:?}", why); @@ -62,40 +75,87 @@ impl Command for CmdMacro { impl CmdMacro { pub fn new() -> CmdMacro{ + // load macro map from JSON + let mut file = File::open(MACRO_PATH); + match file { + Err(why) => { + file = File::create(MACRO_PATH); + println!("Creating file (error {:?})", why); + }, + _ => (), + } + let reader = BufReader::new(file.unwrap()); + let macros: HashMap> = serde_json::from_reader(reader).unwrap_or(HashMap::>::new()); return CmdMacro { format: Regex::new(r#"^!macro\s+([A-Za-z0-9]+|"[^\s]+"|'[^\s]+')(?:\s+([A-Za-z0-9]+|"[^\s]+"|'[^\s]+')\s+(.+))?"#) .unwrap(), - macros: HashMap::::new(), // TODO: load map from JSON + macros: macros, }; } - fn add<'t>(&mut self, key: Option>, value: Option>) -> bool { + fn add<'t>(&mut self, id: &u64, key: Option>, value: Option>) -> bool { + let map: &mut HashMap; + match self.macros.get_mut(id) { + Some(m) => map = m, + None => { + self.macros.insert(*id, HashMap::new()); + map = self.macros.get_mut(id).unwrap(); + }, + } let rk; let rv; if let Some(k) = key { - rk = k; - if self.macros.get(&key.to_owned()).is_some() { + rk = k.as_str().trim_matches('\"').trim_matches('\'').to_string(); + if map.get(&rk).is_some() { return false; } } else {return false;} if let Some(v) = value { - rv = v; + rv = v.as_str().to_string(); } else {return false;} - self.macros.insert(rk.as_str().to_string(), rv.as_str().to_string()); + map.insert(rk, rv); return true; } - fn remove<'t>(&mut self, key: Option>) -> bool { + fn remove<'t>(&mut self, id: &u64, key: Option>) -> bool { + let map: &mut HashMap; + match self.macros.get_mut(id) { + Some(m) => map = m, + None => { + self.macros.insert(*id, HashMap::new()); + map = self.macros.get_mut(id).unwrap(); + }, + } match key { - Some(k) => return self.macros.remove(&k.as_str().to_string()).is_some(), + Some(k) => return map.remove(&k.as_str().trim_matches('\"').trim_matches('\'').to_string()).is_some(), None => return false, } } - fn get(&self, key: &String) -> String { - if let Some(s) = self.macros.get(&key.to_owned()) { + fn get(&mut self, id: &u64, key: &String) -> String { + let map: &HashMap; + match self.macros.get_mut(id) { + Some(m) => map = m, + None => { + self.macros.insert(*id, HashMap::new()); + map = self.macros.get(id).unwrap(); + }, + } + if let Some(s) = map.get(&key.to_owned()) { return s.to_string(); } return "Invalid macro".to_string(); } + + fn save_json(&self) { + let mut file = OpenOptions::new() + .write(true) + .open(MACRO_PATH) + .unwrap(); + //let writer = BufWriter::new(file.unwrap()); + match serde_json::to_writer_pretty(file, &self.macros) { + Err(why) => println!("Macro saving failed ({:?})", why), + Ok(_) => (), + } + } } diff --git a/src/main.rs b/src/main.rs index 9c21032..7c2ce4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,6 @@ extern crate serenity; use serenity::{ model::{channel::Message, gateway::{Ready, Activity}}, prelude::*, - utils::MessageBuilder, }; mod traits; @@ -55,7 +54,7 @@ impl Handler { } fn main() { - println!("Leo42 v{} is starting", crate_version!()); + println!("Leo42 v{} is starting in {}", crate_version!(), env::current_dir().unwrap().to_str().unwrap()); let token = env::var("DISCORD_TOKEN") .expect("Expected a Discord API token in DISCORD_TOKEN environment variable"); let mut event_handler = Handler::new();