tellirc 0.1.6

IRC bot - Saves messages and tell the person you are after
Documentation
extern crate rusqlite;
extern crate serde_json;
extern crate chrono;

use rusqlite::types::{ToSql};
use rusqlite::{Connection, NO_PARAMS};
use chrono::DateTime;
use chrono::offset::Local;
use irc::client::prelude::*;

fn get_usage_string() -> Vec<&'static str> {
    return vec![
        "!tell <target> <message> # Tells <message> to <target> when <target> joins the channel",
        "!tell@ga <target> # Get the alias group for <target>",
        "!tell@aa <target_group> <new_alias> # Add alias to <target_group>",
        "!tell@da <target_group> <alias> # Delete alias from <target_group>"
    ]

}

#[derive(Debug, Clone)]
pub struct AliasRow {
    id: u32,
    aliases: String,
}

impl PartialEq for AliasRow {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

type Aliases = Vec<String>;

#[derive(Debug, Clone)]
pub struct MessageCacheRow {
    id: u32,
    pub to_name: String,
    pub message: String,
    pub in_chan: Option<String>,
    pub from_name: String,
    pub time_stamp: DateTime<Local>,
}

pub trait DaoTrait {
    fn get_message_for(&self, who: &str, in_chan: &str) -> Result<Option<Vec<MessageCacheRow>>, &'static str>;
    fn add_message_for(&self, who: &str, what: &str, chan: &Option<&str>, from: &str) -> Result<(), &'static str>;
    fn del_message(&self, msg: &MessageCacheRow) -> Result<(), &'static str>;
    fn get_alias_for(&self, who: &str) -> Result<(Option<AliasRow>, Aliases), &'static str>;
    fn add_alias_for(&self, who: &str, to_add: &str) -> Result<Aliases, &'static str>;
    fn del_alias_for(&self, who: &str, to_del: &str) -> Result<Aliases, &'static str>;
}

pub struct Dao {
    connection: Connection,
}

impl Dao {
    pub fn new(conn: Connection) -> Dao {
        Dao { connection: conn }
    }

    fn _get_message_for_single(&self, who: &str, in_chan: &str) -> Result<Option<Vec<MessageCacheRow>>, &'static str> {
        let mut stmt = self.connection
                            .prepare("SELECT id, to_name, message, in_chan, from_name, iso_date
                                        FROM MessageCache WHERE to_name IS ?1 AND in_chan is ?2").unwrap();

        let iter = stmt.query_map(&[&who as &ToSql, &in_chan as &ToSql], |row| Ok(MessageCacheRow {
            id: row.get(0).unwrap(),
            to_name: row.get(1).unwrap(),
            message: row.get(2).unwrap(),
            in_chan: row.get(3).unwrap(),
            from_name: row.get(4).unwrap(),
            time_stamp: row.get(5).unwrap(),
        }
        )).unwrap();

        let res: Vec<MessageCacheRow> = iter.map(|a| a.unwrap()).collect();
        
        return Ok(Some(res))
    }
}

impl DaoTrait for Dao {
    fn del_message(&self, msg: &MessageCacheRow) -> Result<(), &'static str> {
        let res = self.connection
            .execute("DELETE FROM MessageCache WHERE id = ?1", &[&msg.id]);
        match res {
            Ok(_) => Ok(()),
            Err(e) => { println!("{}", e); Err("SQL error") },
        }

    }

    fn add_message_for(&self, who: &str, what: &str, chan: &Option<&str>, from: &str) -> Result<(), &'static str> {
        let now = Local::now();
        let result = self.connection
            .execute("INSERT INTO MessageCache (to_name, message, in_chan, from_name, iso_date)
                            VALUES (?1, ?2, ?3, ?4, ?5)",
            &[&who as &ToSql, &what as &ToSql, chan, &from as &ToSql, &now]);
        match result {
            Ok(_) => Ok(()),
            Err(e) => { println!("{}", e); Err("SQL error") },
        }
    }

    fn get_message_for(&self, who: &str, in_chan: &str) -> Result<Option<Vec<MessageCacheRow>>, &'static str> {
        let aliases = self.get_alias_for(who).unwrap();
        let mut results: Vec<MessageCacheRow> = vec!();
        for alias in aliases.1.iter() {
            let msgs = self._get_message_for_single(&alias, &in_chan);
            if let Ok(opt) = msgs {
                if let Some(mut data) = opt {
                    results.append(&mut data);
                }
            } else if let Err(e) = msgs {
                return Err(e)
            }
        }
        if results.len() == 0 {
            Ok(None)
        } else {
            Ok(Some(results))
        }
    }

    fn get_alias_for(&self, who: &str) -> Result<(Option<AliasRow>, Aliases), &'static str> {
        let mut stmt = self.connection
            .prepare("SELECT id, aliases FROM Aliases").unwrap();
        let iter = stmt.query_map(NO_PARAMS, |row| Ok(AliasRow {
            id: row.get(0).unwrap(),
            aliases: row.get(1).unwrap(),
        })).unwrap();
        let mapped: Vec<(AliasRow, Aliases)> = iter
                                .map(|item| {
                                    let temp = item.unwrap();
                                    let als = serde_json::from_str::<Aliases>(&temp.aliases).unwrap();
                                     (temp, als) 
                                    })
                                .filter(|v| { v.1.contains(&who.to_string()) } )
                                .collect();
        if mapped.len() == 0 {
            Ok((None, vec!(String::from(who))))
        } else {
            Ok((Some(mapped.get(0).unwrap().0.clone()), mapped.get(0).unwrap().1.to_vec()))
        }
    }

    fn add_alias_for(&self, who: &str, to_add: &str) -> Result<Aliases, &'static str> {
        if who.eq_ignore_ascii_case(to_add) {
            return Err("The two aliases are the same")
        }
        let res = self.get_alias_for(who);
        let res_to_add = self.get_alias_for(to_add);
        if let Ok((Some(ref row0), _)) = res_to_add { 
            if let Ok((Some(ref row1), _)) = res {
                if row0 == row1 {
                    return Err("Alias already exists")
                }
            } else {
                return Err("Attempting to assign alias to another group of aliases!")
            }
        }
        if let Ok((Some(row), mut als)) = res {
            // Found existing 
            let mut stmt = self.connection
                            .prepare("UPDATE Aliases SET aliases = ?1 WHERE id = ?2").unwrap();
            als.push(String::from(to_add));
            stmt.execute(&[serde_json::to_string(&als).unwrap(), row.id.to_string()]).unwrap();
            return Ok(als)
        } else if let Ok((None, mut als)) = res {
            let mut add_stmt = self.connection
                            .prepare("INSERT INTO Aliases (aliases) VALUES (?1)").unwrap();
            als.push(String::from(to_add));
            add_stmt.execute(&[serde_json::to_string(&als).unwrap()]).unwrap();
            return Ok(als)
        } else {
            Err("SQL error")
        }
    }

    fn del_alias_for(&self, who: &str, to_del: &str) -> Result<Aliases, &'static str> {
        let res = self.get_alias_for(who);
        let res_to_del = self.get_alias_for(to_del);
        if let Ok((Some(ref row0), ref al_del)) = res_to_del { 
            if let Ok((Some(ref row1), _)) = res {
                if row0 == row1 {
                    let new_vec: Aliases = al_del.iter().filter(|e| { **e != String::from(to_del)}).cloned().collect();
                    let mut stmt = self.connection
                                    .prepare("UPDATE Aliases SET aliases = ?1 WHERE id = ?2").unwrap();
                    stmt.execute(&[serde_json::to_string(&new_vec).unwrap(), row0.id.to_string()]).unwrap();
                    return Ok(new_vec)
                } else {
                    return Err("The two are not aliases!");
                }
            } else {
                return Err("Attempting to assign alias to another group of aliases!")
            }
        }
        return Err("The two are not aliases!");
    }
}

pub fn get_actual_message(what: &str) -> &str {
    let mut counter = 0;
    let index = what.find(move|c: char| {
        if c.is_whitespace() {
            counter = counter + 1;
        }
        counter >= 2 
    });
    match slice_index(&what, &index) {
        Some(s) => s,
        None => &what[0..0],
    }
}

fn slice_index<'a>(what: &'a str, index: &Option<usize>) -> Option<&'a str> {
    match index {
        Some(i) => Some(&what[*i+1..]),
        None => None,
    }
}

pub fn new_config_from_file(file_name: &str) -> Result<irc::client::prelude::Config, irc::error::IrcError> {
    irc::client::prelude::Config::load(&file_name)
}


pub fn make_handler_fn<U>(dao: Dao) -> impl (FnMut(&IrcClient, Message) -> Result<(), U>) {
    return move|client: &IrcClient, message: Message| {
        if let Command::PRIVMSG(ref __, ref msg) = message.command {
            let target = message.response_target().unwrap().clone();
            let sender = message.source_nickname().unwrap().clone();
            if !target.starts_with("#") {
                client.send_privmsg(target, "Please don't PM me sir, this is WIP").unwrap();
                return Ok(())
            }
            if msg.starts_with("!tell") {
                let trimmed = msg.trim();
                let tks: Vec<&str> = trimmed.split(" ").collect();
                let tks_len = tks.len();
                if tks_len > 2 && tks[0].eq_ignore_ascii_case("!tell@aa") {
                    let add_res = dao.add_alias_for(tks[1], tks[2]);
                    if let Ok(als) = add_res {
                        client.send_privmsg(target, format!("{:?} are now aliases", als)).unwrap();
                        return Ok(())
                    } else if let Err(e) = add_res {
                        client.send_privmsg(target, format!("{:?}", e)).unwrap();
                        return Ok(())
                    }
                } else if tks_len > 1 && tks[0].eq_ignore_ascii_case("!tell@ga") {
                    let get_res = dao.get_alias_for(tks[1]);
                    if let Ok((_, als)) = get_res {
                        client.send_privmsg(target, format!("{:?} are aliases", als)).unwrap();
                        return Ok(())
                    } else if let Err(err) = get_res {
                        client.send_privmsg(target, err).unwrap();
                    }
                } else if tks_len > 2 && tks[0].eq_ignore_ascii_case("!tell@da") {
                    let del_res = dao.del_alias_for(tks[1], tks[2]);
                    if let Ok(ref als) = del_res {
                        client.send_privmsg(target, format!("{:?} are now aliases", als)).unwrap();
                        return Ok(())
                    } else if let Err(err) = del_res {
                        client.send_privmsg(target, err).unwrap();
                    }
                } else if tks_len > 0 && tks[0].eq_ignore_ascii_case("!tell@quit") {
                    client.send_privmsg(target, "No! >:(").unwrap();
                    return Ok(())
                } else if tks_len > 1 && tks[0].eq_ignore_ascii_case("!tell@join") {
                    let chan = tks[1];
                    match client.send_join(chan) {
                        Ok(_) => return Ok(()),
                        Err(e) => client.send_privmsg(target, format!("Cannot join {}, Error: {:#?}", chan, e)).unwrap(),
                    };
                } else if tks_len > 2 && tks[0].eq_ignore_ascii_case("!tell") {
                    let who = tks[1];
                    // Check if user is in chan
                    let als = dao.get_alias_for(who).unwrap().1;
                    if client.list_users(target).unwrap().iter().any(|user| { als.contains(&String::from(user.get_nickname())) } ) {
                        client.send_privmsg(target, format!("{} is here! Why don't you tell that to them yourself??", who)).unwrap();
                        return Ok(())
                    }
                    let actual_msg = get_actual_message(&msg);
                    let dao_res = dao.add_message_for(who, actual_msg, &Some(target), &sender);
                    match dao_res {
                        Ok(_) => client.send_privmsg(target, format!("{}: Got it, sending \"{}\" to {} in {} upon joining", sender, actual_msg, who, target)).unwrap(),
                        Err(err) => client.send_privmsg(target, err).unwrap(),
                    }
                } else {
                    get_usage_string().iter().for_each(|item: &&str| {
                        client.send_privmsg(sender, item).unwrap();
                    });
                }
            }
        } else if let Command::JOIN(ref chanlist, ref __, ref ___) = message.command {
            let target = message.response_target().unwrap().clone();
            let dao_res = dao.get_message_for(&target, chanlist);
            match dao_res {
                Ok(Some(msgs)) => msgs.iter().for_each(|msg| {
                    client.send_privmsg(chanlist, format!("{}: {} tells you \"{}\" at {}", target, msg.from_name, msg.message, msg.time_stamp)).unwrap();
                    dao.del_message(msg).unwrap();
                }),
                _ => ()
            }
            return Ok(())
        }
        Ok(())
    };
}