aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/factoids/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/factoids/mod.rs')
-rw-r--r--src/plugins/factoids/mod.rs342
1 files changed, 342 insertions, 0 deletions
diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs
new file mode 100644
index 0000000..2f3690f
--- /dev/null
+++ b/src/plugins/factoids/mod.rs
@@ -0,0 +1,342 @@
+extern crate rlua;
+
+use std::fmt;
+use std::str::FromStr;
+use std::sync::Mutex;
+use self::rlua::prelude::*;
+use irc::client::prelude::*;
+
+use time;
+use chrono::NaiveDateTime;
+
+use plugin::*;
+pub mod database;
+use self::database::Database;
+
+mod utils;
+use self::utils::*;
+
+use failure::ResultExt;
+use error::ErrorKind as FrippyErrorKind;
+use error::FrippyError;
+use self::error::*;
+
+static LUA_SANDBOX: &'static str = include_str!("sandbox.lua");
+
+#[derive(PluginName)]
+pub struct Factoids<T: Database> {
+ factoids: Mutex<T>,
+}
+
+macro_rules! try_lock {
+ ( $m:expr ) => {
+ match $m.lock() {
+ Ok(guard) => guard,
+ Err(poisoned) => poisoned.into_inner(),
+ }
+ }
+}
+
+impl<T: Database> Factoids<T> {
+ pub fn new(db: T) -> Factoids<T> {
+ Factoids {
+ factoids: Mutex::new(db),
+ }
+ }
+
+ fn create_factoid(
+ &self,
+ name: &str,
+ content: &str,
+ author: &str,
+ ) -> Result<&str, FactoidsError> {
+ let count = try_lock!(self.factoids).count_factoids(name)?;
+ let tm = time::now().to_timespec();
+
+ let factoid = database::NewFactoid {
+ name: name,
+ idx: count,
+ content: content,
+ author: author,
+ created: NaiveDateTime::from_timestamp(tm.sec, 0u32),
+ };
+
+ Ok(try_lock!(self.factoids)
+ .insert_factoid(&factoid)
+ .map(|()| "Successfully added!")?)
+ }
+
+ fn add(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> {
+ if command.tokens.len() < 2 {
+ Err(ErrorKind::InvalidCommand)?;
+ }
+
+ let name = command.tokens.remove(0);
+ let content = command.tokens.join(" ");
+
+ Ok(self.create_factoid(&name, &content, &command.source)?)
+ }
+
+ fn add_from_url(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> {
+ if command.tokens.len() < 2 {
+ Err(ErrorKind::InvalidCommand)?;
+ }
+
+ let name = command.tokens.remove(0);
+ let url = &command.tokens[0];
+ let content = ::utils::download(url, Some(1024)).context(ErrorKind::Download)?;
+
+ Ok(self.create_factoid(&name, &content, &command.source)?)
+ }
+
+ fn remove(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> {
+ if command.tokens.len() < 1 {
+ Err(ErrorKind::InvalidCommand)?;
+ }
+
+ let name = command.tokens.remove(0);
+ let count = try_lock!(self.factoids).count_factoids(&name)?;
+
+ match try_lock!(self.factoids).delete_factoid(&name, count - 1) {
+ Ok(()) => Ok("Successfully removed"),
+ Err(e) => Err(e)?,
+ }
+ }
+
+ fn get(&self, command: &PluginCommand) -> Result<String, FactoidsError> {
+ let (name, idx) = match command.tokens.len() {
+ 0 => Err(ErrorKind::InvalidCommand)?,
+ 1 => {
+ let name = &command.tokens[0];
+ let count = try_lock!(self.factoids).count_factoids(name)?;
+
+ if count < 1 {
+ Err(ErrorKind::NotFound)?;
+ }
+
+ (name, count - 1)
+ }
+ _ => {
+ let name = &command.tokens[0];
+ let idx = match i32::from_str(&command.tokens[1]) {
+ Ok(i) => i,
+ Err(_) => Err(ErrorKind::InvalidCommand)?,
+ };
+
+ (name, idx)
+ }
+ };
+
+ let factoid = try_lock!(self.factoids)
+ .get_factoid(name, idx)
+ .context(ErrorKind::NotFound)?;
+
+ let message = factoid.content.replace("\n", "|").replace("\r", "");
+
+ Ok(format!("{}: {}", factoid.name, message))
+ }
+
+ fn info(&self, command: &PluginCommand) -> Result<String, FactoidsError> {
+ match command.tokens.len() {
+ 0 => Err(ErrorKind::InvalidCommand)?,
+ 1 => {
+ let name = &command.tokens[0];
+ let count = try_lock!(self.factoids).count_factoids(name)?;
+
+ Ok(match count {
+ 0 => Err(ErrorKind::NotFound)?,
+ 1 => format!("There is 1 version of {}", name),
+ _ => format!("There are {} versions of {}", count, name),
+ })
+ }
+ _ => {
+ let name = &command.tokens[0];
+ let idx = i32::from_str(&command.tokens[1]).context(ErrorKind::InvalidIndex)?;
+ let factoid = try_lock!(self.factoids).get_factoid(name, idx)?;
+
+ Ok(format!(
+ "{}: Added by {} at {} UTC",
+ name, factoid.author, factoid.created
+ ))
+ }
+ }
+ }
+
+ fn exec(&self, mut command: PluginCommand) -> Result<String, FactoidsError> {
+ if command.tokens.len() < 1 {
+ Err(ErrorKind::InvalidIndex)?
+ } else {
+ let name = command.tokens.remove(0);
+ let count = try_lock!(self.factoids).count_factoids(&name)?;
+ let factoid = try_lock!(self.factoids).get_factoid(&name, count - 1)?;
+
+ let content = factoid.content;
+ let value = if content.starts_with('>') {
+ let content = String::from(&content[1..]);
+
+ if content.starts_with('>') {
+ content
+ } else {
+ match self.run_lua(&name, &content, &command) {
+ Ok(v) => v,
+ Err(e) => format!("\"{}\"", e),
+ }
+ }
+ } else {
+ content
+ };
+
+ Ok(value.replace("\n", "|").replace("\r", ""))
+ }
+ }
+
+ fn run_lua(
+ &self,
+ name: &str,
+ code: &str,
+ command: &PluginCommand,
+ ) -> Result<String, rlua::Error> {
+ let args = command
+ .tokens
+ .iter()
+ .filter(|x| !x.is_empty())
+ .map(ToOwned::to_owned)
+ .collect::<Vec<String>>();
+
+ let lua = unsafe { Lua::new_with_debug() };
+ let globals = lua.globals();
+
+ globals.set("factoid", code)?;
+ globals.set("download", lua.create_function(download)?)?;
+ globals.set("sleep", lua.create_function(sleep)?)?;
+ globals.set("args", args)?;
+ globals.set("input", command.tokens.join(" "))?;
+ globals.set("user", command.source.clone())?;
+ globals.set("channel", command.target.clone())?;
+ globals.set("output", lua.create_table()?)?;
+
+ lua.exec::<()>(LUA_SANDBOX, Some(name))?;
+ let output: Vec<String> = globals.get::<_, Vec<String>>("output")?;
+
+ Ok(output.join("|"))
+ }
+}
+
+impl<T: Database> Plugin for Factoids<T> {
+ fn execute(&self, _: &IrcClient, message: &Message) -> ExecutionStatus {
+ match message.command {
+ Command::PRIVMSG(_, ref content) => if content.starts_with('!') {
+ ExecutionStatus::RequiresThread
+ } else {
+ ExecutionStatus::Done
+ },
+ _ => ExecutionStatus::Done,
+ }
+ }
+
+ fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError> {
+ if let Command::PRIVMSG(_, mut content) = message.command.clone() {
+ content.remove(0);
+
+ let t: Vec<String> = content.split(' ').map(ToOwned::to_owned).collect();
+
+ let c = PluginCommand {
+ source: message.source_nickname().unwrap().to_owned(),
+ target: message.response_target().unwrap().to_owned(),
+ tokens: t,
+ };
+
+ Ok(match self.exec(c) {
+ Ok(f) => client
+ .send_privmsg(&message.response_target().unwrap(), &f)
+ .context(FrippyErrorKind::Connection)?,
+ Err(_) => (),
+ })
+ } else {
+ Ok(())
+ }
+ }
+
+ fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), FrippyError> {
+ if command.tokens.is_empty() {
+ return Ok(client
+ .send_notice(&command.target, "Invalid command")
+ .context(FrippyErrorKind::Connection)?);
+ }
+
+ 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| s.to_owned()),
+ "fromurl" => self.add_from_url(&mut command).map(|s| s.to_owned()),
+ "remove" => self.remove(&mut command).map(|s| s.to_owned()),
+ "get" => self.get(&command),
+ "info" => self.info(&command),
+ "exec" => self.exec(command),
+ _ => Err(ErrorKind::InvalidCommand.into()),
+ };
+
+ Ok(match result {
+ Ok(v) => client
+ .send_privmsg(&target, &v)
+ .context(FrippyErrorKind::Connection)?,
+ Err(e) => {
+ let message = e.to_string();
+ client
+ .send_notice(&source, &message)
+ .context(FrippyErrorKind::Connection)?;
+ Err(e).context(FrippyErrorKind::Factoids)?
+ }
+ })
+ }
+
+ fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result<String, String> {
+ Err(String::from(
+ "Evaluation of commands is not implemented for Factoids at this time",
+ ))
+ }
+}
+
+impl<T: Database> fmt::Debug for Factoids<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Factoids {{ ... }}")
+ }
+}
+
+pub mod error {
+ #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)]
+ #[error = "FactoidsError"]
+ pub enum ErrorKind {
+ /// Invalid command error
+ #[fail(display = "Invalid Command")]
+ InvalidCommand,
+
+ /// Invalid index error
+ #[fail(display = "Invalid index")]
+ InvalidIndex,
+
+ /// Download error
+ #[fail(display = "Download failed")]
+ Download,
+
+ /// Duplicate error
+ #[fail(display = "Entry already exists")]
+ Duplicate,
+
+ /// Not found error
+ #[fail(display = "Factoid 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,
+ }
+}