aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/factoids
diff options
context:
space:
mode:
authorJokler <jokler.contact@gmail.com>2018-10-22 03:14:51 +0200
committerJokler <jokler.contact@gmail.com>2018-10-22 03:14:51 +0200
commitf9e3022756ea454a31f797bfc9cfdc1d81ee86cf (patch)
treeb161e4283e85732b081f0c57b0500d67b07200e4 /src/plugins/factoids
parent489b23850bd37f39c8ebabd4d59d5753923c3c07 (diff)
downloadfrippy-f9e3022756ea454a31f797bfc9cfdc1d81ee86cf.tar.gz
frippy-f9e3022756ea454a31f797bfc9cfdc1d81ee86cf.zip
Factoids: Rename to Factoid
Diffstat (limited to 'src/plugins/factoids')
-rw-r--r--src/plugins/factoids/database.rs160
-rw-r--r--src/plugins/factoids/mod.rs373
-rw-r--r--src/plugins/factoids/sandbox.lua126
-rw-r--r--src/plugins/factoids/utils.rs81
4 files changed, 0 insertions, 740 deletions
diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs
deleted file mode 100644
index ec8ed3e..0000000
--- a/src/plugins/factoids/database.rs
+++ /dev/null
@@ -1,160 +0,0 @@
-use std::collections::HashMap;
-#[cfg(feature = "mysql")]
-use std::sync::Arc;
-
-#[cfg(feature = "mysql")]
-use diesel::mysql::MysqlConnection;
-#[cfg(feature = "mysql")]
-use diesel::prelude::*;
-#[cfg(feature = "mysql")]
-use failure::ResultExt;
-#[cfg(feature = "mysql")]
-use r2d2::Pool;
-#[cfg(feature = "mysql")]
-use r2d2_diesel::ConnectionManager;
-
-use chrono::NaiveDateTime;
-
-use super::error::*;
-
-#[cfg_attr(feature = "mysql", derive(Queryable))]
-#[derive(Clone, Debug)]
-pub struct Factoid {
- pub name: String,
- pub idx: i32,
- pub content: String,
- pub author: String,
- pub created: NaiveDateTime,
-}
-
-#[cfg_attr(feature = "mysql", derive(Insertable))]
-#[cfg_attr(feature = "mysql", table_name = "factoids")]
-pub struct NewFactoid<'a> {
- pub name: &'a str,
- pub idx: i32,
- pub content: &'a str,
- pub author: &'a str,
- pub created: NaiveDateTime,
-}
-
-pub trait Database: Send + Sync {
- fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError>;
- fn get_factoid(&self, name: &str, idx: i32) -> Result<Factoid, FactoidsError>;
- fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError>;
- fn count_factoids(&self, name: &str) -> Result<i32, FactoidsError>;
-}
-
-// HashMap
-impl<S: ::std::hash::BuildHasher + Send + Sync> Database for HashMap<(String, i32), Factoid, S> {
- fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> {
- let factoid = Factoid {
- name: factoid.name.to_owned(),
- idx: factoid.idx,
- content: factoid.content.to_owned(),
- author: factoid.author.to_owned(),
- created: factoid.created,
- };
-
- let name = factoid.name.clone();
- match self.insert((name, factoid.idx), factoid) {
- None => Ok(()),
- Some(_) => Err(ErrorKind::Duplicate)?,
- }
- }
-
- fn get_factoid(&self, name: &str, idx: i32) -> Result<Factoid, FactoidsError> {
- Ok(self.get(&(name.to_owned(), idx))
- .cloned()
- .ok_or(ErrorKind::NotFound)?)
- }
-
- fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> {
- match self.remove(&(name.to_owned(), idx)) {
- Some(_) => Ok(()),
- None => Err(ErrorKind::NotFound)?,
- }
- }
-
- fn count_factoids(&self, name: &str) -> Result<i32, FactoidsError> {
- Ok(self.iter().filter(|&(&(ref n, _), _)| n == name).count() as i32)
- }
-}
-
-// Diesel automatically defines the factoids module as public.
-// We create a schema module to keep it private.
-#[cfg(feature = "mysql")]
-mod schema {
- table! {
- factoids (name, idx) {
- name -> Varchar,
- idx -> Integer,
- content -> Text,
- author -> Varchar,
- created -> Timestamp,
- }
- }
-}
-
-#[cfg(feature = "mysql")]
-use self::schema::factoids;
-
-#[cfg(feature = "mysql")]
-impl Database for Arc<Pool<ConnectionManager<MysqlConnection>>> {
- fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> {
- use diesel;
-
- let conn = &*self.get().context(ErrorKind::NoConnection)?;
- diesel::insert_into(factoids::table)
- .values(factoid)
- .execute(conn)
- .context(ErrorKind::MysqlError)?;
-
- Ok(())
- }
-
- fn get_factoid(&self, name: &str, idx: i32) -> Result<Factoid, FactoidsError> {
- let conn = &*self.get().context(ErrorKind::NoConnection)?;
- Ok(factoids::table
- .find((name, idx))
- .first(conn)
- .context(ErrorKind::MysqlError)?)
- }
-
- fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> {
- use self::factoids::columns;
- use diesel;
-
- let conn = &*self.get().context(ErrorKind::NoConnection)?;
- match diesel::delete(
- factoids::table
- .filter(columns::name.eq(name))
- .filter(columns::idx.eq(idx)),
- ).execute(conn)
- {
- Ok(v) => {
- if v > 0 {
- Ok(())
- } else {
- Err(ErrorKind::NotFound)?
- }
- }
- Err(e) => Err(e).context(ErrorKind::MysqlError)?,
- }
- }
-
- fn count_factoids(&self, name: &str) -> Result<i32, FactoidsError> {
- use diesel;
-
- let conn = &*self.get().context(ErrorKind::NoConnection)?;
- let count: Result<i64, _> = factoids::table
- .filter(factoids::columns::name.eq(name))
- .count()
- .get_result(conn);
-
- match count {
- Ok(c) => Ok(c as i32),
- Err(diesel::NotFound) => Ok(0),
- Err(e) => Err(e).context(ErrorKind::MysqlError)?,
- }
- }
-}
diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs
deleted file mode 100644
index a3d521a..0000000
--- a/src/plugins/factoids/mod.rs
+++ /dev/null
@@ -1,373 +0,0 @@
-extern crate rlua;
-
-use self::rlua::prelude::*;
-use antidote::RwLock;
-use irc::client::prelude::*;
-use std::fmt;
-use std::marker::PhantomData;
-use std::str::FromStr;
-
-use chrono::NaiveDateTime;
-use time;
-
-use plugin::*;
-use FrippyClient;
-pub mod database;
-use self::database::Database;
-
-mod utils;
-use self::utils::*;
-use utils::Url;
-
-use self::error::*;
-use error::ErrorKind as FrippyErrorKind;
-use error::FrippyError;
-use failure::ResultExt;
-
-static LUA_SANDBOX: &'static str = include_str!("sandbox.lua");
-
-enum FactoidResponse {
- Public(String),
- Private(String),
-}
-
-#[derive(PluginName)]
-pub struct Factoids<T: Database, C: Client> {
- factoids: RwLock<T>,
- phantom: PhantomData<C>,
-}
-
-impl<T: Database, C: Client> Factoids<T, C> {
- pub fn new(db: T) -> Self {
- Factoids {
- factoids: RwLock::new(db),
- phantom: PhantomData,
- }
- }
-
- fn create_factoid(
- &self,
- name: &str,
- content: &str,
- author: &str,
- ) -> Result<&str, FactoidsError> {
- let count = self.factoids.read().count_factoids(name)?;
- let tm = time::now().to_timespec();
-
- let factoid = database::NewFactoid {
- name,
- idx: count,
- content,
- author,
- created: NaiveDateTime::from_timestamp(tm.sec, 0u32),
- };
-
- Ok(self.factoids
- .write()
- .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 = Url::from(url.as_ref())
- .max_kib(1024)
- .request()
- .context(ErrorKind::Download)?;
-
- Ok(self.create_factoid(&name, &content, &command.source)?)
- }
-
- fn remove(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> {
- if command.tokens.is_empty() {
- Err(ErrorKind::InvalidCommand)?;
- }
-
- let name = command.tokens.remove(0);
- let count = self.factoids.read().count_factoids(&name)?;
-
- match self.factoids.write().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 = self.factoids.read().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 = self.factoids
- .read()
- .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 = self.factoids.read().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 = self.factoids.read().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.is_empty() {
- Err(ErrorKind::InvalidIndex)?
- } else {
- let name = command.tokens.remove(0);
- let count = self.factoids.read().count_factoids(&name)?;
- let factoid = self.factoids.read().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) => match e {
- LuaError::CallbackError { cause, .. } => cause.to_string(),
- _ => e.to_string(),
- },
- }
- }
- } else {
- content
- };
-
- Ok(value.replace("\n", "|").replace("\r", ""))
- }
- }
-
- fn run_lua(&self, name: &str, code: &str, command: &PluginCommand) -> Result<String, LuaError> {
- 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("json_decode", lua.create_function(json_decode)?)?;
- 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("|"))
- }
-
- fn help(&self) -> &str {
- "usage: factoids <subcommand>\r\n\
- subcommands: add, fromurl, remove, get, info, exec, help"
- }
-}
-
-impl<T: Database, C: FrippyClient> Plugin for Factoids<T, C> {
- type Client = C;
- fn execute(&self, _: &Self::Client, 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: &Self::Client,
- 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,
- };
-
- if let Ok(f) = self.exec(c) {
- client
- .send_privmsg(&message.response_target().unwrap(), &f)
- .context(FrippyErrorKind::Connection)?;
- }
- }
-
- Ok(())
- }
-
- fn command(
- &self,
- client: &Self::Client,
- mut command: PluginCommand,
- ) -> Result<(), FrippyError> {
- use self::FactoidResponse::{Private, Public};
-
- if command.tokens.is_empty() {
- client
- .send_notice(&command.source, "Invalid command")
- .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())),
- "fromurl" => self.add_from_url(&mut command)
- .map(|s| Private(s.to_owned())),
- "remove" => self.remove(&mut command).map(|s| Private(s.to_owned())),
- "get" => self.get(&command).map(Public),
- "info" => self.info(&command).map(Public),
- "exec" => self.exec(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::Factoids)?
- }
- }
-
- Ok(())
- }
-
- fn evaluate(&self, _: &Self::Client, _: PluginCommand) -> Result<String, String> {
- Err(String::from(
- "Evaluation of commands is not implemented for Factoids at this time",
- ))
- }
-}
-
-impl<T: Database, C: FrippyClient> fmt::Debug for Factoids<T, C> {
- 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,
- }
-}
diff --git a/src/plugins/factoids/sandbox.lua b/src/plugins/factoids/sandbox.lua
deleted file mode 100644
index a927535..0000000
--- a/src/plugins/factoids/sandbox.lua
+++ /dev/null
@@ -1,126 +0,0 @@
-function send(text)
- local text = tostring(text)
- local len = #output
- if len < 1 then
- output = { text }
- else
- output[len] = output[len] .. text
- end
-end
-
-function sendln(text)
- send(text)
- table.insert(output, "")
-end
-
-function trim(s)
- local from = s:match"^%s*()"
- return from > #s and "" or s:match(".*%S", from)
-end
-
-trimmedInput = trim(input)
-
-if trimmedInput == "" then
- ioru = user
-else
- ioru = trimmedInput
-end
-
-local sandbox_env = {
- print = send,
- println = sendln,
- trim = trim,
- eval = nil,
- sleep = nil,
- json = {decode = json_decode},
- args = args,
- input = input,
- user = user,
- ioru = ioru,
- channel = channel,
- request = download,
- string = string,
- math = math,
- table = table,
- pairs = pairs,
- ipairs = ipairs,
- next = next,
- select = select,
- unpack = unpack,
- tostring = tostring,
- tonumber = tonumber,
- type = type,
- assert = assert,
- error = error,
- pcall = pcall,
- xpcall = xpcall,
- _VERSION = _VERSION
-}
-
-sandbox_env.os = {
- clock = os.clock,
- time = os.time,
- difftime = os.difftime
-}
-
-sandbox_env.string.rep = nil
-sandbox_env.string.dump = nil
-sandbox_env.math.randomseed = nil
-
--- Temporary evaluation function
-function eval(code)
- local c, e = load(code, nil, nil, sandbox_env)
- if c then
- return c()
- else
- error(e)
- end
-end
-
--- Only sleeps for 1 second at a time
--- This ensures that the timeout check can still run
-function safesleep(dur)
- while dur > 1000 do
- dur = dur - 1000
- sleep(1000)
- end
- sleep(dur)
-end
-
-sandbox_env.eval = eval
-sandbox_env.sleep = safesleep
-
--- Check if the factoid timed out
-function checktime()
- if os.time() - time >= timeout then
- error("Timed out after " .. timeout .. " seconds", 0)
- else
- -- Limit the cpu usage of factoids
- sleep(1)
- end
-end
-
--- Check if the factoid uses too much memory
-function checkmem()
- if collectgarbage("count") > maxmem then
- error("Factoid used over " .. maxmem .. " kbyte of ram")
- end
-end
-
-local f, e = load(factoid, nil, nil, sandbox_env)
-
--- Add timeout hook
-time = os.time()
--- The timeout is defined in seconds
-timeout = 30
-debug.sethook(checktime, "l")
--- Add memory check hook
--- The max memory is defined in kilobytes
-maxmem = 1000
-debug.sethook(checkmem, "l")
-
-if f then
- f()
-else
- error(e)
-end
diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs
deleted file mode 100644
index a35dd27..0000000
--- a/src/plugins/factoids/utils.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use std::thread;
-use std::time::Duration;
-
-use serde_json::{self, Value as SerdeValue};
-
-use super::rlua::Error as LuaError;
-use super::rlua::Error::RuntimeError;
-use super::rlua::{Lua, Value as LuaValue};
-
-use utils::error::ErrorKind::Connection;
-use utils::Url;
-
-use failure::Fail;
-
-pub fn sleep(_: &Lua, dur: u64) -> Result<(), LuaError> {
- thread::sleep(Duration::from_millis(dur));
- Ok(())
-}
-
-pub fn download(_: &Lua, url: String) -> Result<String, LuaError> {
- let url = Url::from(url).max_kib(1024);
- match url.request() {
- Ok(v) => Ok(v),
- Err(e) => {
- let error = match e.kind() {
- Connection => e.cause().unwrap().to_string(),
- _ => e.to_string(),
- };
-
- Err(RuntimeError(format!(
- "Failed to download {} - {}",
- url.as_str(),
- error
- )))
- }
- }
-}
-
-fn convert_value(lua: &Lua, sval: SerdeValue, max_recurs: usize) -> Result<LuaValue, LuaError> {
- if max_recurs == 0 {
- return Err(RuntimeError(String::from(
- "Reached max recursion level - json is nested too deep",
- )));
- }
-
- let lval = match sval {
- SerdeValue::Null => LuaValue::Nil,
- SerdeValue::Bool(b) => LuaValue::Boolean(b),
- SerdeValue::String(s) => LuaValue::String(lua.create_string(&s)?),
- SerdeValue::Number(n) => {
- let f = n.as_f64().ok_or_else(|| RuntimeError(String::from(
- "Failed to convert number into double",
- )))?;
- LuaValue::Number(f)
- }
- SerdeValue::Array(arr) => {
- let table = lua.create_table()?;
- for (i, val) in arr.into_iter().enumerate() {
- table.set(i + 1, convert_value(lua, val, max_recurs - 1)?)?;
- }
-
- LuaValue::Table(table)
- }
- SerdeValue::Object(obj) => {
- let table = lua.create_table()?;
- for (key, val) in obj {
- table.set(key, convert_value(lua, val, max_recurs - 1)?)?;
- }
-
- LuaValue::Table(table)
- }
- };
-
- Ok(lval)
-}
-
-pub fn json_decode(lua: &Lua, json: String) -> Result<LuaValue, LuaError> {
- let ser_val: SerdeValue = serde_json::from_str(&json).map_err(|e| RuntimeError(e.to_string()))?;
-
- convert_value(lua, ser_val, 25)
-}