aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/sed.rs
diff options
context:
space:
mode:
authorJokler <jokler.contact@gmail.com>2019-06-22 15:51:21 +0200
committerJokler <jokler.contact@gmail.com>2019-06-22 15:51:21 +0200
commit3592c7b6fb2522ff57c7f312b8927eb680d6dc5c (patch)
treed484a367c205afe43ba7327a888b06844fd24c0c /src/plugins/sed.rs
parent237f6ebe59c90d4ceddd9af6a8a19e562d304aaa (diff)
parenta92e622a0d42911e8e46239c3bde17169ed60c92 (diff)
downloadfrippy-3592c7b6fb2522ff57c7f312b8927eb680d6dc5c.tar.gz
frippy-3592c7b6fb2522ff57c7f312b8927eb680d6dc5c.zip
Merge branch 'dev'HEADv0.5.0master
Diffstat (limited to 'src/plugins/sed.rs')
-rw-r--r--src/plugins/sed.rs193
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,
+ }
+}