diff options
| author | Jokler <jokler.contact@gmail.com> | 2019-06-22 15:51:21 +0200 |
|---|---|---|
| committer | Jokler <jokler.contact@gmail.com> | 2019-06-22 15:51:21 +0200 |
| commit | 3592c7b6fb2522ff57c7f312b8927eb680d6dc5c (patch) | |
| tree | d484a367c205afe43ba7327a888b06844fd24c0c /src/plugins/sed.rs | |
| parent | 237f6ebe59c90d4ceddd9af6a8a19e562d304aaa (diff) | |
| parent | a92e622a0d42911e8e46239c3bde17169ed60c92 (diff) | |
| download | frippy-3592c7b6fb2522ff57c7f312b8927eb680d6dc5c.tar.gz frippy-3592c7b6fb2522ff57c7f312b8927eb680d6dc5c.zip | |
Diffstat (limited to 'src/plugins/sed.rs')
| -rw-r--r-- | src/plugins/sed.rs | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/src/plugins/sed.rs b/src/plugins/sed.rs new file mode 100644 index 0000000..2c8522f --- /dev/null +++ b/src/plugins/sed.rs @@ -0,0 +1,193 @@ +use std::collections::HashMap; +use std::marker::PhantomData; + +use antidote::RwLock; +use circular_queue::CircularQueue; +use regex::{Regex, RegexBuilder, Captures}; + +use irc::client::prelude::*; + +use plugin::*; +use FrippyClient; + +use self::error::*; +use error::ErrorKind as FrippyErrorKind; +use error::FrippyError; +use failure::Fail; +use failure::ResultExt; + +lazy_static! { + static ref RE: Regex = + Regex::new(r"^s/((?:\\/|[^/])+)/((?:\\/|[^/])*)/(?:(\w+))?\s*$").unwrap(); +} + +#[derive(PluginName, Debug)] +pub struct Sed<C> { + per_channel: usize, + channel_messages: RwLock<HashMap<String, CircularQueue<String>>>, + phantom: PhantomData<C>, +} + +impl<C: FrippyClient> Sed<C> { + pub fn new(per_channel: usize) -> Self { + Sed { + per_channel, + channel_messages: RwLock::new(HashMap::new()), + phantom: PhantomData, + } + } + + fn add_message(&self, channel: String, message: String) { + let mut channel_messages = self.channel_messages.write(); + let messages = channel_messages + .entry(channel) + .or_insert_with(|| CircularQueue::with_capacity(self.per_channel)); + messages.push(message); + } + + fn format_escaped(&self, input: &str) -> String { + let mut output = String::with_capacity(input.len()); + let mut escape = false; + + for c in input.chars() { + if escape && !r"/\".contains(c) { + output.push('\\'); + } else if !escape && c == '\\' { + escape = true; + continue; + } + escape = false; + + output.push(c); + } + + output + } + + fn run_regex(&self, channel: &str, captures: &Captures) -> Result<String, SedError> { + let mut global_match = false; + let mut case_insens = false; + let mut ign_whitespace = false; + let mut swap_greed = false; + let mut enable_unicode = true; + + debug!("{:?}", captures); + + let first = self.format_escaped(captures.get(1).unwrap().as_str()); + let second = self.format_escaped(captures.get(2).unwrap().as_str()); + + if let Some(flags) = captures.get(3) { + let flags = flags.as_str(); + + global_match = flags.contains('g'); + case_insens = flags.contains('i'); + ign_whitespace = flags.contains('x'); + swap_greed = flags.contains('U'); + enable_unicode = !flags.contains('u'); + } + + let user_re = RegexBuilder::new(&first) + .case_insensitive(case_insens) + .ignore_whitespace(ign_whitespace) + .unicode(enable_unicode) + .swap_greed(swap_greed) + .build() + .context(ErrorKind::InvalidRegex)?; + + let channel_messages = self.channel_messages.read(); + let messages = channel_messages.get(channel).ok_or(ErrorKind::NoMessages)?; + + for message in messages.iter() { + if user_re.is_match(message) { + let response = if global_match { + user_re.replace_all(message, &second[..]) + } else { + user_re.replace(message, &second[..]) + }; + + return Ok(response.to_string()); + } + } + + Err(ErrorKind::NoMatch)? + } +} + +impl<C: FrippyClient> Plugin for Sed<C> { + type Client = C; + fn execute(&self, client: &Self::Client, message: &Message) -> ExecutionStatus { + match message.command { + Command::PRIVMSG(_, ref content) => { + let channel = message.response_target().unwrap_or(""); + let user = message.source_nickname().unwrap_or(""); + if channel == user { + return ExecutionStatus::Done; + } + + if let Some(captures) = RE.captures(content) { + let result = match self.run_regex(channel, &captures) { + Ok(msg) => client.send_privmsg(channel, &msg), + Err(e) => match e.kind() { + ErrorKind::InvalidRegex => { + let err = e.cause().unwrap().to_string(); + client.send_notice(user, &err.replace('\n', "\r\n")) + } + _ => client.send_notice(user, &e.to_string()), + }, + }; + + match result { + Err(e) => { + ExecutionStatus::Err(e.context(FrippyErrorKind::Connection).into()) + } + Ok(_) => ExecutionStatus::Done, + } + } else { + self.add_message(channel.to_string(), content.to_string()); + + ExecutionStatus::Done + } + } + _ => ExecutionStatus::Done, + } + } + + fn execute_threaded(&self, _: &Self::Client, _: &Message) -> Result<(), FrippyError> { + panic!("Sed should not use threading") + } + + fn command(&self, client: &Self::Client, command: PluginCommand) -> Result<(), FrippyError> { + client + .send_notice( + &command.source, + "Currently this Plugin does not implement any commands.", + ) + .context(FrippyErrorKind::Connection)?; + + Ok(()) + } + + fn evaluate(&self, _: &Self::Client, _: PluginCommand) -> Result<String, String> { + Err(String::from( + "Evaluation of commands is not implemented for sed at this time", + )) + } +} + +pub mod error { + #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] + #[error = "SedError"] + pub enum ErrorKind { + /// Invalid regex error + #[fail(display = "Invalid regex")] + InvalidRegex, + + /// No messages found error + #[fail(display = "No messages were found for this channel")] + NoMessages, + + /// No match found error + #[fail(display = "No recent messages match this regex")] + NoMatch, + } +} |
