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, } }