From 625ca41cf54bac0268f7bde9d7ad9017c03d5919 Mon Sep 17 00:00:00 2001 From: Jokler Date: Fri, 21 Sep 2018 00:21:43 +0200 Subject: Quote: Add initial quote plugin --- src/plugins/quote/mod.rs | 262 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 src/plugins/quote/mod.rs (limited to 'src/plugins/quote/mod.rs') diff --git a/src/plugins/quote/mod.rs b/src/plugins/quote/mod.rs new file mode 100644 index 0000000..43333e7 --- /dev/null +++ b/src/plugins/quote/mod.rs @@ -0,0 +1,262 @@ +use std::fmt; +use std::marker::PhantomData; +use std::str::FromStr; + +use antidote::RwLock; +use irc::client::prelude::*; +use rand::{thread_rng, Rng}; +use chrono::NaiveDateTime; +use time; + +use plugin::*; +use FrippyClient; +pub mod database; +use self::database::Database; + +use self::error::*; +use error::ErrorKind as FrippyErrorKind; +use error::FrippyError; +use failure::ResultExt; + +enum QuoteResponse { + Public(String), + Private(String), +} + +#[derive(PluginName)] +pub struct Quote { + quotes: RwLock, + phantom: PhantomData, +} + +impl Quote { + pub fn new(db: T) -> Self { + Quote { + quotes: RwLock::new(db), + phantom: PhantomData, + } + } + + fn create_quote( + &self, + quotee: &str, + channel: &str, + content: &str, + author: &str, + ) -> Result<&str, QuoteError> { + let count = self.quotes.read().count_quotes(quotee, channel)?; + let tm = time::now().to_timespec(); + + let quote = database::NewQuote { + quotee, + channel, + idx: count + 1, + content, + author, + created: NaiveDateTime::from_timestamp(tm.sec, 0u32), + }; + + Ok(self.quotes + .write() + .insert_quote("e) + .map(|()| "Successfully added!")?) + } + + fn add(&self, command: &mut PluginCommand) -> Result<&str, QuoteError> { + if command.tokens.len() < 2 { + Err(ErrorKind::InvalidCommand)?; + } + + if command.target == command.source { + Err(ErrorKind::PrivateMessageNotAllowed)?; + } + + let quotee = command.tokens.remove(0); + let channel = &command.target; + let content = command.tokens.join(" "); + + Ok(self.create_quote("ee, channel, &content, &command.source)?) + } + + fn get(&self, command: &PluginCommand) -> Result { + if command.tokens.is_empty() { + Err(ErrorKind::InvalidCommand)?; + } + + let quotee = &command.tokens[0]; + let channel = &command.target; + let count = self.quotes.read().count_quotes(quotee, channel)?; + + if count < 1 { + Err(ErrorKind::NotFound)?; + } + + let idx = match command.tokens.len() { + 1 => thread_rng().gen_range(1, count + 1), + _ => { + let idx = match i32::from_str(&command.tokens[1]) { + Ok(i) => i, + Err(_) => Err(ErrorKind::InvalidIndex)?, + }; + + if idx < 0 { + count + idx + 1 + } else { + idx + } + } + }; + + let quote = self.quotes + .read() + .get_quote(quotee, channel, idx) + .context(ErrorKind::NotFound)?; + + + Ok(format!("\"{}\" - {}[{}/{}]", quote.content, quote.quotee, idx, count)) + } + + fn info(&self, command: &PluginCommand) -> Result { + match command.tokens.len() { + 0 => Err(ErrorKind::InvalidCommand)?, + 1 => { + let quotee = &command.tokens[0]; + let channel = &command.target; + let count = self.quotes.read().count_quotes(quotee, channel)?; + + Ok(match count { + 0 => Err(ErrorKind::NotFound)?, + 1 => format!("{} has 1 quote", quotee), + _ => format!("{} has {} quotes", quotee, count), + }) + } + _ => { + let quotee = &command.tokens[0]; + let channel = &command.target; + let idx = i32::from_str(&command.tokens[1]).context(ErrorKind::InvalidIndex)?; + let quote = self.quotes.read().get_quote(quotee, channel, idx)?; + + Ok(format!( + "{}'s quote was added by {} at {} UTC", + quotee, quote.author, quote.created + )) + } + } + } + + fn help(&self) -> &str { + "usage: quotes \r\n\ + subcommands: add, get, info, help" + } +} + +impl Plugin for Quote { + type Client = C; + fn execute(&self, _: &Self::Client, _: &Message) -> ExecutionStatus { + ExecutionStatus::Done + } + + fn execute_threaded(&self, _: &Self::Client, _: &Message) -> Result<(), FrippyError> { + panic!("Quotes should not use threading") + } + + fn command( + &self, + client: &Self::Client, + mut command: PluginCommand, + ) -> Result<(), FrippyError> { + use self::QuoteResponse::{Private, Public}; + + if command.tokens.is_empty() { + client + .send_notice(&command.source, &ErrorKind::InvalidCommand.to_string()) + .context(FrippyErrorKind::Connection)?; + + return Ok(()); + } + + let target = command.target.clone(); + let source = command.source.clone(); + + let sub_command = command.tokens.remove(0); + let result = match sub_command.as_ref() { + "add" => self.add(&mut command).map(|s| Private(s.to_owned())), + "get" => self.get(&command).map(Public), + "info" => self.info(&command).map(Public), + "help" => Ok(Private(self.help().to_owned())), + _ => Err(ErrorKind::InvalidCommand.into()), + }; + + match result { + Ok(v) => match v { + Public(m) => client + .send_privmsg(&target, &m) + .context(FrippyErrorKind::Connection)?, + Private(m) => client + .send_notice(&source, &m) + .context(FrippyErrorKind::Connection)?, + }, + Err(e) => { + let message = e.to_string(); + client + .send_notice(&source, &message) + .context(FrippyErrorKind::Connection)?; + Err(e).context(FrippyErrorKind::Quote)? + } + } + + Ok(()) + } + + fn evaluate(&self, _: &Self::Client, _: PluginCommand) -> Result { + Err(String::from( + "Evaluation of commands is not implemented for Quote at this time", + )) + } +} + +impl fmt::Debug for Quote { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Quote {{ ... }}") + } +} + +pub mod error { + #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] + #[error = "QuoteError"] + pub enum ErrorKind { + /// Invalid command error + #[fail(display = "Incorrect command. Send \"quote help\" for help")] + InvalidCommand, + + /// Invalid index error + #[fail(display = "Invalid index")] + InvalidIndex, + + /// Private message error + #[fail(display = "You can only add quotes in channel messages")] + PrivateMessageNotAllowed, + + /// Download error + #[fail(display = "Download failed")] + Download, + + /// Duplicate error + #[fail(display = "Entry already exists")] + Duplicate, + + /// Not found error + #[fail(display = "Quote was not found")] + NotFound, + + /// MySQL error + #[cfg(feature = "mysql")] + #[fail(display = "Failed to execute MySQL Query")] + MysqlError, + + /// No connection error + #[cfg(feature = "mysql")] + #[fail(display = "No connection to the database")] + NoConnection, + } +} -- cgit v1.2.3-70-g09d2 From 9401a62004f3ac7313f6bf12d2649d5af61835c5 Mon Sep 17 00:00:00 2001 From: Jokler Date: Fri, 21 Sep 2018 00:59:44 +0200 Subject: Quote: Ignore trailing space when no index was specified --- src/plugins/quote/database.rs | 12 +++++++++--- src/plugins/quote/mod.rs | 20 +++++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) (limited to 'src/plugins/quote/mod.rs') diff --git a/src/plugins/quote/database.rs b/src/plugins/quote/database.rs index 6ad08a0..49d6058 100644 --- a/src/plugins/quote/database.rs +++ b/src/plugins/quote/database.rs @@ -46,7 +46,9 @@ pub trait Database: Send + Sync { } // HashMap -impl Database for HashMap<(String, String, i32), Quote, S> { +impl Database + for HashMap<(String, String, i32), Quote, S> +{ fn insert_quote(&mut self, quote: &NewQuote) -> Result<(), QuoteError> { let quote = Quote { quotee: quote.quotee.to_owned(), @@ -66,13 +68,17 @@ impl Database for HashMap<(String, St } fn get_quote(&self, quotee: &str, channel: &str, idx: i32) -> Result { - Ok(self.get(&(quotee.to_owned(), channel.to_owned(), idx)) + Ok(self + .get(&(quotee.to_owned(), channel.to_owned(), idx)) .cloned() .ok_or(ErrorKind::NotFound)?) } fn count_quotes(&self, quotee: &str, channel: &str) -> Result { - Ok(self.iter().filter(|&(&(ref n, ref c, _), _)| n == quotee && c == channel).count() as i32) + Ok(self + .iter() + .filter(|&(&(ref n, ref c, _), _)| n == quotee && c == channel) + .count() as i32) } } diff --git a/src/plugins/quote/mod.rs b/src/plugins/quote/mod.rs index 43333e7..9f8a29e 100644 --- a/src/plugins/quote/mod.rs +++ b/src/plugins/quote/mod.rs @@ -3,9 +3,9 @@ use std::marker::PhantomData; use std::str::FromStr; use antidote::RwLock; +use chrono::NaiveDateTime; use irc::client::prelude::*; use rand::{thread_rng, Rng}; -use chrono::NaiveDateTime; use time; use plugin::*; @@ -56,7 +56,8 @@ impl Quote { created: NaiveDateTime::from_timestamp(tm.sec, 0u32), }; - Ok(self.quotes + Ok(self + .quotes .write() .insert_quote("e) .map(|()| "Successfully added!")?) @@ -92,9 +93,11 @@ impl Quote { } let idx = match command.tokens.len() { - 1 => thread_rng().gen_range(1, count + 1), + 1 | _ if command.tokens[1].is_empty() => thread_rng().gen_range(1, count + 1), _ => { - let idx = match i32::from_str(&command.tokens[1]) { + let idx_string = &command.tokens[1]; + + let idx = match i32::from_str(idx_string) { Ok(i) => i, Err(_) => Err(ErrorKind::InvalidIndex)?, }; @@ -107,13 +110,16 @@ impl Quote { } }; - let quote = self.quotes + let quote = self + .quotes .read() .get_quote(quotee, channel, idx) .context(ErrorKind::NotFound)?; - - Ok(format!("\"{}\" - {}[{}/{}]", quote.content, quote.quotee, idx, count)) + Ok(format!( + "\"{}\" - {}[{}/{}]", + quote.content, quote.quotee, idx, count + )) } fn info(&self, command: &PluginCommand) -> Result { -- cgit v1.2.3-70-g09d2 From f5c4786c14a3ef3868d8f11df897f36053bf29e3 Mon Sep 17 00:00:00 2001 From: Jokler Date: Sat, 22 Sep 2018 20:19:20 +0200 Subject: Quote: Fix get command when no index was specified --- src/plugins/quote/mod.rs | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) (limited to 'src/plugins/quote/mod.rs') diff --git a/src/plugins/quote/mod.rs b/src/plugins/quote/mod.rs index 9f8a29e..8a122d6 100644 --- a/src/plugins/quote/mod.rs +++ b/src/plugins/quote/mod.rs @@ -92,21 +92,20 @@ impl Quote { Err(ErrorKind::NotFound)?; } - let idx = match command.tokens.len() { - 1 | _ if command.tokens[1].is_empty() => thread_rng().gen_range(1, count + 1), - _ => { - let idx_string = &command.tokens[1]; - - let idx = match i32::from_str(idx_string) { - Ok(i) => i, - Err(_) => Err(ErrorKind::InvalidIndex)?, - }; - - if idx < 0 { - count + idx + 1 - } else { - idx - } + let len = command.tokens.len(); + let idx = if len < 2 || command.tokens[1].is_empty() { + thread_rng().gen_range(1, count + 1) + } else { + let idx_string = &command.tokens[1]; + let idx = match i32::from_str(idx_string) { + Ok(i) => i, + Err(_) => Err(ErrorKind::InvalidIndex)?, + }; + + if idx < 0 { + count + idx + 1 + } else { + idx } }; -- cgit v1.2.3-70-g09d2 From caf5fae723f9af78421cf154afd75ec7e344a0dd Mon Sep 17 00:00:00 2001 From: Jokler Date: Tue, 6 Nov 2018 05:40:39 +0100 Subject: Quote: Info: Add negative indexing and fix error --- src/plugins/quote/mod.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src/plugins/quote/mod.rs') diff --git a/src/plugins/quote/mod.rs b/src/plugins/quote/mod.rs index 8a122d6..edeed40 100644 --- a/src/plugins/quote/mod.rs +++ b/src/plugins/quote/mod.rs @@ -139,7 +139,18 @@ impl Quote { let quotee = &command.tokens[0]; let channel = &command.target; let idx = i32::from_str(&command.tokens[1]).context(ErrorKind::InvalidIndex)?; - let quote = self.quotes.read().get_quote(quotee, channel, idx)?; + + let idx = if idx < 0 { + self.quotes.read().count_quotes(quotee, channel)? + idx + 1 + } else { + idx + }; + + let quote = self + .quotes + .read() + .get_quote(quotee, channel, idx) + .context(ErrorKind::NotFound)?; Ok(format!( "{}'s quote was added by {} at {} UTC", -- cgit v1.2.3-70-g09d2