about summary refs log tree commit diff
path: root/fun/tvldb/src
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2020-07-27T19·45+0100
committertazjin <mail@tazj.in>2020-07-27T23·54+0000
commit82ba28f1976305c1163adb5993745604ccb696cc (patch)
tree6d3887b3ffd1213461d8f612549bfaeda46a0657 /fun/tvldb/src
parentef54f5da9fa30b5c302f2a49595ee5d041f9706a (diff)
chore: Move //fun/tvldb -> //fun/paroxysm r/1496
Say ~my~ its name!

Change-Id: I7890318aef984af0f6bc011de32282f16e01cbb3
Reviewed-on: https://cl.tvl.fyi/c/depot/+/1483
Tested-by: BuildkiteCI
Reviewed-by: eta <eta@theta.eu.org>
Diffstat (limited to 'fun/tvldb/src')
-rw-r--r--fun/tvldb/src/cfg.rs11
-rw-r--r--fun/tvldb/src/keyword.rs210
-rw-r--r--fun/tvldb/src/main.rs371
-rw-r--r--fun/tvldb/src/models.rs36
-rw-r--r--fun/tvldb/src/schema.rs18
5 files changed, 0 insertions, 646 deletions
diff --git a/fun/tvldb/src/cfg.rs b/fun/tvldb/src/cfg.rs
deleted file mode 100644
index 038795a6f114..000000000000
--- a/fun/tvldb/src/cfg.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use std::collections::HashSet;
-
-#[derive(Deserialize)]
-pub struct Config {
-    pub database_url: String,
-    pub irc_config_path: String,
-    #[serde(default)]
-    pub admins: HashSet<String>,
-    #[serde(default)]
-    pub log_filter: Option<String>,
-}
diff --git a/fun/tvldb/src/keyword.rs b/fun/tvldb/src/keyword.rs
deleted file mode 100644
index 9c5ea47ea523..000000000000
--- a/fun/tvldb/src/keyword.rs
+++ /dev/null
@@ -1,210 +0,0 @@
-use crate::models::{Entry, Keyword, NewEntry, NewKeyword};
-use diesel::pg::PgConnection;
-use diesel::prelude::*;
-use failure::Error;
-use std::borrow::Cow;
-
-/// Maximum number of times we'll follow a `see: ` pointer.
-const RECURSION_LIMIT: usize = 5;
-
-pub struct KeywordDetails {
-    pub keyword: Keyword,
-    pub entries: Vec<Entry>,
-}
-
-impl KeywordDetails {
-    pub fn learn(&mut self, nick: &str, text: &str, dbc: &PgConnection) -> Result<usize, Error> {
-        let now = ::chrono::Utc::now().naive_utc();
-        let ins = NewEntry {
-            keyword_id: self.keyword.id,
-            idx: (self.entries.len() + 1) as _,
-            text,
-            creation_ts: now,
-            created_by: nick,
-        };
-        let new = {
-            use crate::schema::entries;
-            ::diesel::insert_into(entries::table)
-                .values(ins)
-                .get_result(dbc)?
-        };
-        self.entries.push(new);
-        Ok(self.entries.len())
-    }
-
-    pub fn process_moves(&mut self, moves: &[(i32, i32)], dbc: &PgConnection) -> Result<(), Error> {
-        for (oid, new_idx) in moves {
-            {
-                use crate::schema::entries::dsl::*;
-                ::diesel::update(entries.filter(id.eq(oid)))
-                    .set(idx.eq(new_idx))
-                    .execute(dbc)?;
-            }
-        }
-        self.entries = Self::get_entries(self.keyword.id, dbc)?;
-        Ok(())
-    }
-
-    pub fn swap(&mut self, idx_a: usize, idx_b: usize, dbc: &PgConnection) -> Result<(), Error> {
-        let mut moves = vec![];
-        for ent in self.entries.iter() {
-            if ent.idx == idx_a as i32 {
-                moves.push((ent.id, idx_b as i32));
-            }
-            if ent.idx == idx_b as i32 {
-                moves.push((ent.id, idx_a as i32));
-            }
-        }
-        if moves.len() != 2 {
-            Err(format_err!("Invalid swap operation."))?;
-        }
-        self.process_moves(&moves, dbc)?;
-        Ok(())
-    }
-
-    pub fn update(&mut self, idx: usize, val: &str, dbc: &PgConnection) -> Result<(), Error> {
-        let ent = self
-            .entries
-            .get_mut(idx.saturating_sub(1))
-            .ok_or(format_err!("No such element to update."))?;
-        {
-            use crate::schema::entries::dsl::*;
-            ::diesel::update(entries.filter(id.eq(ent.id)))
-                .set(text.eq(val))
-                .execute(dbc)?;
-        }
-        ent.text = val.to_string();
-        Ok(())
-    }
-
-    pub fn delete(&mut self, idx: usize, dbc: &PgConnection) -> Result<(), Error> {
-        // step 1: delete the element
-        {
-            let ent = self
-                .entries
-                .get(idx.saturating_sub(1))
-                .ok_or(format_err!("No such element to delete."))?;
-            {
-                use crate::schema::entries::dsl::*;
-                ::diesel::delete(entries.filter(id.eq(ent.id))).execute(dbc)?;
-            }
-        }
-        // step 2: move all the elements in front of it back one
-        let mut moves = vec![];
-        for ent in self.entries.iter() {
-            if idx > ent.idx as _ {
-                moves.push((ent.id, ent.idx.saturating_sub(1)));
-            }
-        }
-        self.process_moves(&moves, dbc)?;
-        Ok(())
-    }
-
-    pub fn add_zwsp_to_name(name: &str) -> Option<String> {
-        let second_index = name.char_indices().nth(1).map(|(i, _)| i)?;
-        let (start, end) = name.split_at(second_index);
-        Some(format!("{}​{}", start, end))
-    }
-
-    pub fn format_entry(&self, idx: usize) -> Option<String> {
-        if let Some(ent) = self.entries.get(idx.saturating_sub(1)) {
-            let gen_clr = if self.keyword.chan == "*" {
-                "\x0307"
-            } else {
-                ""
-            };
-            let zwsp_name = Self::add_zwsp_to_name(&self.keyword.name)
-                .unwrap_or_else(|| self.keyword.name.clone());
-            Some(format!(
-                "\x02{}{}\x0f\x0315[{}/{}]\x0f: {} \x0f\x0314[{}]\x0f",
-                gen_clr,
-                zwsp_name,
-                idx,
-                self.entries.len(),
-                ent.text,
-                ent.creation_ts.date()
-            ))
-        } else {
-            None
-        }
-    }
-
-    pub fn get_or_create(word: &str, c: &str, dbc: &PgConnection) -> Result<Self, Error> {
-        if let Some(ret) = Self::get(word, c, dbc)? {
-            Ok(ret)
-        } else {
-            Ok(Self::create(word, c, dbc)?)
-        }
-    }
-
-    pub fn create(word: &str, c: &str, dbc: &PgConnection) -> Result<Self, Error> {
-        let val = NewKeyword {
-            name: word,
-            chan: c,
-        };
-        let ret: Keyword = {
-            use crate::schema::keywords;
-            ::diesel::insert_into(keywords::table)
-                .values(val)
-                .get_result(dbc)?
-        };
-        Ok(KeywordDetails {
-            keyword: ret,
-            entries: vec![],
-        })
-    }
-
-    fn get_entries(kid: i32, dbc: &PgConnection) -> Result<Vec<Entry>, Error> {
-        let entries: Vec<Entry> = {
-            use crate::schema::entries::dsl::*;
-            entries
-                .filter(keyword_id.eq(kid))
-                .order_by(idx.asc())
-                .load(dbc)?
-        };
-        Ok(entries)
-    }
-
-    fn get_inner<'a, T: Into<Cow<'a, str>>>(
-        word: T,
-        c: &str,
-        dbc: &PgConnection,
-        recursion_count: usize,
-    ) -> Result<Option<Self>, Error> {
-        let word = word.into();
-        let keyword: Option<Keyword> = {
-            use crate::schema::keywords::dsl::*;
-            keywords
-                .filter(name.ilike(word).and(chan.eq(c).or(chan.eq("*"))))
-                .first(dbc)
-                .optional()?
-        };
-        if let Some(k) = keyword {
-            let entries = Self::get_entries(k.id, dbc)?;
-            if let Some(e0) = entries.get(0) {
-                if e0.text.starts_with("see: ") {
-                    if recursion_count > RECURSION_LIMIT {
-                        // Oh dear.
-                        Err(format_err!("Halt. You're having a bit too much fun."))?
-                    }
-                    let new_word = e0.text.replace("see: ", "");
-                    return Self::get_inner(new_word, c, dbc, recursion_count + 1);
-                }
-            }
-            Ok(Some(KeywordDetails {
-                keyword: k,
-                entries,
-            }))
-        } else {
-            Ok(None)
-        }
-    }
-
-    pub fn get<'a, T: Into<Cow<'a, str>>>(
-        word: T,
-        c: &str,
-        dbc: &PgConnection,
-    ) -> Result<Option<Self>, Error> {
-        Self::get_inner(word, c, dbc, 0)
-    }
-}
diff --git a/fun/tvldb/src/main.rs b/fun/tvldb/src/main.rs
deleted file mode 100644
index 510cf0461b89..000000000000
--- a/fun/tvldb/src/main.rs
+++ /dev/null
@@ -1,371 +0,0 @@
-extern crate irc;
-extern crate serde;
-#[macro_use]
-extern crate serde_derive;
-#[macro_use]
-extern crate diesel;
-extern crate chrono;
-extern crate config;
-extern crate env_logger;
-#[macro_use]
-extern crate log;
-#[macro_use]
-extern crate failure;
-extern crate regex;
-#[macro_use]
-extern crate lazy_static;
-extern crate rand;
-
-use crate::cfg::Config;
-use crate::keyword::KeywordDetails;
-use diesel::pg::PgConnection;
-use diesel::r2d2::{ConnectionManager, Pool};
-use failure::Error;
-use irc::client::prelude::*;
-use rand::rngs::ThreadRng;
-use rand::{thread_rng, Rng};
-use regex::{Captures, Regex};
-use std::collections::HashMap;
-use std::fmt::Display;
-
-mod cfg;
-mod keyword;
-mod models;
-mod schema;
-
-pub struct App {
-    client: IrcClient,
-    pg: Pool<ConnectionManager<PgConnection>>,
-    rng: ThreadRng,
-    cfg: Config,
-    last_msgs: HashMap<String, HashMap<String, String>>,
-}
-
-impl App {
-    pub fn report_error<T: Display>(
-        &mut self,
-        nick: &str,
-        chan: &str,
-        msg: T,
-    ) -> Result<(), Error> {
-        self.client
-            .send_notice(nick, format!("[{}] \x0304Error:\x0f {}", chan, msg))?;
-        Ok(())
-    }
-
-    pub fn keyword_from_captures(
-        &mut self,
-        learn: &::regex::Captures,
-        nick: &str,
-        chan: &str,
-    ) -> Result<KeywordDetails, Error> {
-        let db = self.pg.get()?;
-        debug!("Fetching keyword for captures: {:?}", learn);
-        let subj = &learn["subj"];
-        let learn_chan = if learn.name("gen").is_some() {
-            "*"
-        } else {
-            chan
-        };
-        if !chan.starts_with("#") && learn_chan != "*" {
-            Err(format_err!("Only general entries may be taught via PM."))?;
-        }
-        debug!("Fetching keyword '{}' for chan {}", subj, learn_chan);
-        let kwd = KeywordDetails::get_or_create(subj, learn_chan, &db)?;
-        if kwd.keyword.chan == "*" && !self.cfg.admins.contains(nick) {
-            Err(format_err!(
-                "Only administrators can create or modify general entries."
-            ))?;
-        }
-        Ok(kwd)
-    }
-
-    pub fn handle_move(
-        &mut self,
-        target: &str,
-        nick: &str,
-        chan: &str,
-        mv: Captures,
-    ) -> Result<(), Error> {
-        let db = self.pg.get()?;
-        let idx = &mv["idx"];
-        let idx = match idx[1..(idx.len() - 1)].parse::<usize>() {
-            Ok(i) => i,
-            Err(e) => Err(format_err!("Could not parse index: {}", e))?,
-        };
-        let new_idx = match mv["new_idx"].parse::<i32>() {
-            Ok(i) => i,
-            Err(e) => Err(format_err!("Could not parse target index: {}", e))?,
-        };
-        let mut kwd = self.keyword_from_captures(&mv, nick, chan)?;
-        if new_idx < 0 {
-            kwd.delete(idx, &db)?;
-            self.client.send_notice(
-                target,
-                format!("\x02{}\x0f: Deleted entry {}.", kwd.keyword.name, idx),
-            )?;
-        } else {
-            kwd.swap(idx, new_idx as _, &db)?;
-            self.client.send_notice(
-                target,
-                format!(
-                    "\x02{}\x0f: Swapped entries {} and {}.",
-                    kwd.keyword.name, idx, new_idx
-                ),
-            )?;
-        }
-        Ok(())
-    }
-
-    pub fn handle_learn(
-        &mut self,
-        target: &str,
-        nick: &str,
-        chan: &str,
-        learn: Captures,
-    ) -> Result<(), Error> {
-        let db = self.pg.get()?;
-        let val = &learn["val"];
-        let mut kwd = self.keyword_from_captures(&learn, nick, chan)?;
-        let idx = kwd.learn(nick, val, &db)?;
-        self.client
-            .send_notice(target, kwd.format_entry(idx).unwrap())?;
-        Ok(())
-    }
-
-    pub fn handle_insert_last_quote(
-        &mut self,
-        target: &str,
-        nick: &str,
-        chan: &str,
-        qlast: Captures,
-    ) -> Result<(), Error> {
-        let db = self.pg.get()?;
-        let nick_to_grab = &qlast["subj"].to_ascii_lowercase();
-        let mut kwd = self.keyword_from_captures(&qlast, nick, chan)?;
-        let chan_lastmsgs = self
-            .last_msgs
-            .entry(chan.to_string())
-            .or_insert(HashMap::new());
-        // Use `nick` here, so things like "grfn: see glittershark" work.
-        let val = if let Some(last) = chan_lastmsgs.get(nick_to_grab) {
-            if last.starts_with("\x01ACTION ") {
-                // Yes, this is inefficient, but it's better than writing some hacky CTCP parsing code
-                // I guess (also, characters are hard, so just blindly slicing seems like a bad idea)
-                format!("* {} {}", nick_to_grab, last.replace("\x01ACTION ", "").replace("\x01", ""))
-            }
-            else {
-                format!("<{}> {}", nick_to_grab, last)
-            }
-        } else {
-            Err(format_err!("I dunno what {} said...", kwd.keyword.name))?
-        };
-        let idx = kwd.learn(nick, &val, &db)?;
-        self.client
-            .send_notice(target, kwd.format_entry(idx).unwrap())?;
-        Ok(())
-    }
-
-    pub fn handle_increment(
-        &mut self,
-        target: &str,
-        nick: &str,
-        chan: &str,
-        icr: Captures,
-    ) -> Result<(), Error> {
-        let db = self.pg.get()?;
-        let mut kwd = self.keyword_from_captures(&icr, nick, chan)?;
-        let is_incr = &icr["incrdecr"] == "++";
-        let now = chrono::Utc::now().naive_utc().date();
-        let mut idx = None;
-        for (i, ent) in kwd.entries.iter().enumerate() {
-            if ent.creation_ts.date() == now {
-                if let Ok(val) = ent.text.parse::<i32>() {
-                    let val = if is_incr { val + 1 } else { val - 1 };
-                    idx = Some((i + 1, val));
-                }
-            }
-        }
-        if let Some((i, val)) = idx {
-            kwd.update(i, &val.to_string(), &db)?;
-            self.client.send_notice(target, kwd.format_entry(i).unwrap())?;
-        } else {
-            let val = if is_incr { 1 } else { -1 };
-            let idx = kwd.learn(nick, &val.to_string(), &db)?;
-            self.client
-                .send_notice(target, kwd.format_entry(idx).unwrap())?;
-        }
-        Ok(())
-    }
-
-    pub fn handle_query(
-        &mut self,
-        target: &str,
-        nick: &str,
-        chan: &str,
-        query: Captures,
-    ) -> Result<(), Error> {
-        let db = self.pg.get()?;
-        let subj = &query["subj"];
-        let idx = match query.name("idx") {
-            Some(i) => {
-                let i = i.as_str();
-                match &i[1..(i.len() - 1)] {
-                    "*" => Some(-1),
-                    x => x.parse::<usize>().map(|x| x as i32).ok(),
-                }
-            }
-            None => None,
-        };
-        debug!("Querying {} with idx {:?}", subj, idx);
-        match KeywordDetails::get(subj, chan, &db)? {
-            Some(kwd) => {
-                if let Some(mut idx) = idx {
-                    if idx == -1 {
-                        // 'get all entries' ('*' parses into this)
-                        for i in 0..kwd.entries.len() {
-                            self.client.send_notice(
-                                nick,
-                                format!("[{}] {}", chan, kwd.format_entry(i + 1).unwrap()),
-                            )?;
-                        }
-                    } else {
-                        if idx == 0 {
-                            idx = 1;
-                        }
-                        if let Some(ent) = kwd.format_entry(idx as _) {
-                            self.client.send_notice(target, ent)?;
-                        } else {
-                            let pluralised = if kwd.entries.len() == 1 {
-                                "entry"
-                            } else {
-                                "entries"
-                            };
-                            self.client.send_notice(
-                                target,
-                                format!(
-                                    "\x02{}\x0f: only has \x02\x0304{}\x0f {}",
-                                    subj,
-                                    kwd.entries.len(),
-                                    pluralised
-                                ),
-                            )?;
-                        }
-                    }
-                } else {
-                    let entry = if kwd.entries.len() < 2 {
-                        1 // because [1, 1) does not a range make
-                    } else {
-                        self.rng.gen_range(1, kwd.entries.len())
-                    };
-                    if let Some(ent) = kwd.format_entry(entry) {
-                        self.client.send_notice(target, ent)?;
-                    } else {
-                        self.client
-                            .send_notice(target, format!("\x02{}\x0f: no entries yet", subj))?;
-                    }
-                }
-            }
-            None => {
-                self.client
-                    .send_notice(target, format!("\x02{}\x0f: never heard of it", subj))?;
-            }
-        }
-        Ok(())
-    }
-
-    pub fn handle_privmsg(&mut self, from: &str, chan: &str, msg: &str) -> Result<(), Error> {
-        lazy_static! {
-            static ref LEARN_RE: Regex =
-                Regex::new(r#"^\?\?(?P<gen>!)?\s*(?P<subj>[^\[:]*):\s*(?P<val>.*)"#).unwrap();
-            static ref QUERY_RE: Regex =
-                Regex::new(r#"^\?\?\s*(?P<subj>[^\[:]*)(?P<idx>\[[^\]]+\])?"#).unwrap();
-            static ref QLAST_RE: Regex = Regex::new(r#"^\?\?\s*(?P<subj>[^\[:]*)!"#).unwrap();
-            static ref INCREMENT_RE: Regex =
-                Regex::new(r#"^\?\?(?P<gen>!)?\s*(?P<subj>[^\[:]*)(?P<incrdecr>\+\+|\-\-)"#)
-                    .unwrap();
-            static ref MOVE_RE: Regex = Regex::new(
-                r#"^\?\?(?P<gen>!)?\s*(?P<subj>[^\[:]*)(?P<idx>\[[^\]]+\])->(?P<new_idx>.*)"#
-            )
-            .unwrap();
-        }
-        let nick = from.split("!").next().ok_or(format_err!(
-            "Received PRIVMSG from a source without nickname (failed to split n!u@h)"
-        ))?;
-        let target = if chan.starts_with("#") { chan } else { nick };
-        debug!("[{}] <{}> {}", chan, nick, msg);
-        if let Some(learn) = LEARN_RE.captures(msg) {
-            self.handle_learn(target, nick, chan, learn)?;
-        } else if let Some(qlast) = QLAST_RE.captures(msg) {
-            self.handle_insert_last_quote(target, nick, chan, qlast)?;
-        } else if let Some(mv) = MOVE_RE.captures(msg) {
-            self.handle_move(target, nick, chan, mv)?;
-        } else if let Some(icr) = INCREMENT_RE.captures(msg) {
-            self.handle_increment(target, nick, chan, icr)?;
-        } else if let Some(query) = QUERY_RE.captures(msg) {
-            self.handle_query(target, nick, chan, query)?;
-        } else {
-            let chan_lastmsgs = self
-                .last_msgs
-                .entry(chan.to_string())
-                .or_insert(HashMap::new());
-            chan_lastmsgs.insert(nick.to_string().to_ascii_lowercase(), msg.to_string());
-        }
-        Ok(())
-    }
-
-    pub fn handle_msg(&mut self, m: Message) -> Result<(), Error> {
-        match m.command {
-            Command::PRIVMSG(channel, message) => {
-                if let Some(src) = m.prefix {
-                    if let Err(e) = self.handle_privmsg(&src, &channel, &message) {
-                        warn!("error handling command in {} (src {}): {}", channel, src, e);
-                        if let Some(nick) = src.split("!").next() {
-                            self.report_error(nick, &channel, e)?;
-                        }
-                    }
-                }
-            }
-            Command::INVITE(nick, channel) => {
-                if self.cfg.admins.contains(&nick) {
-                    info!("Joining {} after admin invite", channel);
-                    self.client.send_join(channel)?;
-                }
-            }
-            _ => {}
-        }
-        Ok(())
-    }
-}
-
-fn main() -> Result<(), Error> {
-    println!("[+] loading configuration");
-    let default_log_filter = "paroxysm=info".to_string();
-    let mut settings = config::Config::default();
-    settings.merge(config::Environment::with_prefix("PARX"))?;
-    let cfg: Config = settings.try_into()?;
-    let env = env_logger::Env::new()
-        .default_filter_or(cfg.log_filter.clone().unwrap_or(default_log_filter));
-    env_logger::init_from_env(env);
-    info!("paroxysm starting up");
-    info!("connecting to database at {}", cfg.database_url);
-    let pg = Pool::new(ConnectionManager::new(&cfg.database_url))?;
-    info!("connecting to IRC using config {}", cfg.irc_config_path);
-    let client = IrcClient::new(&cfg.irc_config_path)?;
-    client.identify()?;
-    let st = client.stream();
-    let mut app = App {
-        client,
-        pg,
-        cfg,
-        rng: thread_rng(),
-        last_msgs: HashMap::new(),
-    };
-    info!("running!");
-    st.for_each_incoming(|m| {
-        if let Err(e) = app.handle_msg(m) {
-            warn!("Error processing message: {}", e);
-        }
-    })?;
-    Ok(())
-}
diff --git a/fun/tvldb/src/models.rs b/fun/tvldb/src/models.rs
deleted file mode 100644
index 721efbbb2e61..000000000000
--- a/fun/tvldb/src/models.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use crate::schema::{entries, keywords};
-use chrono::NaiveDateTime;
-
-#[derive(Queryable)]
-pub struct Keyword {
-    pub id: i32,
-    pub name: String,
-    pub chan: String,
-}
-
-#[derive(Queryable)]
-pub struct Entry {
-    pub id: i32,
-    pub keyword_id: i32,
-    pub idx: i32,
-    pub text: String,
-    pub creation_ts: NaiveDateTime,
-    pub created_by: String,
-}
-
-#[derive(Insertable)]
-#[table_name = "keywords"]
-pub struct NewKeyword<'a> {
-    pub name: &'a str,
-    pub chan: &'a str,
-}
-
-#[derive(Insertable)]
-#[table_name = "entries"]
-pub struct NewEntry<'a> {
-    pub keyword_id: i32,
-    pub idx: i32,
-    pub text: &'a str,
-    pub creation_ts: NaiveDateTime,
-    pub created_by: &'a str,
-}
diff --git a/fun/tvldb/src/schema.rs b/fun/tvldb/src/schema.rs
deleted file mode 100644
index ef4044531ee7..000000000000
--- a/fun/tvldb/src/schema.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-table! {
-    entries (id) {
-        id -> Int4,
-        keyword_id -> Int4,
-        idx -> Int4,
-        text -> Varchar,
-        creation_ts -> Timestamp,
-        created_by -> Varchar,
-    }
-}
-
-table! {
-    keywords (id) {
-        id -> Int4,
-        name -> Varchar,
-        chan -> Varchar,
-    }
-}