|
- extern crate serenity;
- use serenity::{
- model::channel::Message,
- prelude::*,
- utils::MessageBuilder,
- };
-
- extern crate serde_json;
-
- extern crate regex;
- use regex::{Regex, RegexBuilder, Match};
-
- use core::option::Option;
- use std::collections::HashMap;
- use std::string::String;
- use std::fs::{File, OpenOptions};
- use std::io::BufReader;
- use std::vec::Vec;
-
- use crate::traits::Command;
-
- const MACRO_PATH: &str = "macros.json";
-
- pub struct CmdMacro {
- format: Regex,
- help_format: Regex,
- macros: HashMap<u64,HashMap<String, String>>,
- }
-
- 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(&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(&id, parsed.get(2)) {
- response.push("Successfully removed macro.");
- self.save_json();
- } else {
- response.push("Macro does not exist.");
- }
- } else if op.as_str().to_string().to_lowercase() == "list" {
- response.push(self.list(&id));
- } else if op.as_str().to_string().to_lowercase() == "help" {
- self.help(ctx, msg);
- return;
- } else {
- let mut macro_msg = self.get(&id, &op.as_str().to_string());
- if msg.content.chars().count() > op.end() {
- // there's some parameters for the macro
- macro_msg = parse_macro(¯o_msg, &msg.content[op.end()+1..]);
- }
- response.push(¯o_msg);
- }
- if let Err(why) = msg.channel_id.say(&ctx.http, &response.build()) {
- println!("Failed to send macro message {:?}", why);
- }
- return;
- }
- }
- let response = MessageBuilder::new()
- .push("Unexpected failure.")
- .build();
- if let Err(why) = msg.channel_id.say(&ctx.http, &response) {
- println!("Failed to send macro failure message {:?}", why);
- }
- }
-
- fn valid(&self, _ctx: &Context, msg: &Message) -> bool {
- return self.format.is_match(&msg.content) && !msg.author.bot;
- }
-
- fn help(&self, ctx: &Context, msg: &Message) {
- let mut response = MessageBuilder::new();
- response.push("**Use text shortcuts to generate macro text.**\n!macro (short text)\n!macro list\n!macro add (shortcut) (macro)\n!macro remove (shortcut)".to_string());
- if let Err(why) = msg.channel_id.say(&ctx.http, &response.build()) {
- println!("Failed to send macro help message {:?}", why);
- }
- }
-
- fn valid_help(&self, _ctx: &Context, msg: &Message) -> bool {
- return self.help_format.is_match(&msg.content);
- }
- }
-
- impl CmdMacro {
- pub fn new() -> CmdMacro{
- // load macro map from JSON
- let file: File = OpenOptions::new()
- .read(true)
- .write(true)
- .create(true)
- .open(MACRO_PATH)
- .unwrap();
- let reader = BufReader::new(file);
- let macros: HashMap<u64, HashMap<String, String>> = serde_json::from_reader(reader).unwrap_or(HashMap::<u64, HashMap<String, String>>::new());
- return CmdMacro {
- format:
- RegexBuilder::new(r#"^!macro\s+([A-Za-z0-9]+|"[^\s]+"|'[^\s]+')(?:\s+([A-Za-z0-9]+|"[^\s]+"|'[^\s]+'))?(?:\s+(.+))?"#)
- .multi_line(true)
- .dot_matches_new_line(true)
- .case_insensitive(true)
- .build()
- .unwrap(),
- help_format:
- RegexBuilder::new(r#"^!help\s*macro$"#)
- .case_insensitive(true)
- .build()
- .unwrap(),
- macros: macros,
- };
- }
-
- fn add<'t>(&mut self, id: &u64, key: Option<Match<'t>>, value: Option<Match<'t>>) -> bool {
- let map: &mut HashMap<String, String>;
- 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.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.as_str().to_string();
- } else {return false;}
- map.insert(rk, rv);
- return true;
- }
-
- fn remove<'t>(&mut self, id: &u64, key: Option<Match<'t>>) -> bool {
- let map: &mut HashMap<String, String>;
- 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 map.remove(&k.as_str().trim_matches('\"').trim_matches('\'').to_string()).is_some(),
- None => return false,
- }
- }
-
- fn get(&mut self, id: &u64, key: &String) -> String {
- let map: &HashMap<String, String>;
- 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 list(&mut self, id: &u64) -> String {
- let map: &HashMap<String, String>;
- match self.macros.get_mut(id) {
- Some(m) => map = m,
- None => {
- self.macros.insert(*id, HashMap::new());
- map = self.macros.get(id).unwrap();
- },
- }
- let mut resp = "Available macros:\n`".to_owned();
- for k in map.keys() {
- resp += k;
- resp += ", ";
- }
- if resp == "Available macros:\n`" {
- resp += "(None)`";
- } else {
- resp = resp[0..resp.chars().count()-2].to_string()+"`";
- }
- return resp.to_string();
- }
-
- fn save_json(&self) {
- let file = OpenOptions::new()
- .write(true)
- .append(false)
- .truncate(true)
- .open(MACRO_PATH)
- .unwrap();
- match file.sync_all() {
- Err(why) => println!("File sync failed ({:?})", why),
- Ok(_) => (),
- }
- //let writer = BufWriter::new(file.unwrap());
- match serde_json::to_writer_pretty(file, &self.macros) {
- Err(why) => println!("Macro saving failed ({:?})", why),
- Ok(_) => (),
- }
- }
- }
-
- fn parse_macro<'a, 'b>(macro_pre: &'a str, macro_params: &'b str) -> String {
- if macro_params.chars().count() == 0 {
- return macro_pre.to_string();
- }
- let re = RegexBuilder::new(r#"^\s*([A-Za-z0-9]+|".*"|'.*')"#)
- .multi_line(true)
- .dot_matches_new_line(true)
- .case_insensitive(true)
- .build()
- .unwrap();
- let mut parsed_params = Vec::<String>::new();
- let mut params_str = macro_params.to_string();
- // move over macro_params like a tape until out of matches
- while let Some(re_capture) = re.captures(¶ms_str) {
- let re_match = re_capture.get(1).unwrap();
- parsed_params.push(re_match.as_str().trim_matches('\"').trim_matches('\'').to_string());
- params_str = params_str[re_match.end()..].to_string();
- }
- let mut macro_post = macro_pre.to_string();
- // replace {#} format in macro with given param
- for i in 0..parsed_params.len() {
- macro_post = macro_post.replace(&("{".to_owned()+&i.to_string()+"}"), parsed_params.get(i).unwrap());
- }
- return macro_post;
- }
|