From c6dc3f15b85985884ea143f2b4e1b3e130b4064b Mon Sep 17 00:00:00 2001 From: Adrian Malacoda Date: Sun, 25 Feb 2018 04:47:04 -0600 Subject: [PATCH] add a rudimentary irc module --- Cargo.toml | 2 + src/lib.rs | 2 + src/modules/irc.rs | 154 ++++++++++++++++++++++++++++++++++++++++++ src/modules/loader.rs | 2 + src/modules/mod.rs | 1 + tenquestionmarks.toml | 26 ++++--- 6 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 src/modules/irc.rs diff --git a/Cargo.toml b/Cargo.toml index 9d19031..f688ca3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ time = "0.1" regex = "0.2" multimap = "0.4.0" notify = "4.0.0" +irc = "0.11.8" # latest version which supports old openssl version (required by discord) +thread_local = "0.3" pvn = { git = "http://gitlab.monarch-pass.net/malacoda/pvn.git" } echobox = { git = "http://gitlab.monarch-pass.net/malacoda/echobox.git" } stc = { git = "http://gitlab.monarch-pass.net/malacoda/stc.git" } diff --git a/src/lib.rs b/src/lib.rs index db1304f..fc8941b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ extern crate transformable_channels; extern crate stc; extern crate regex; extern crate multimap; +extern crate irc; +extern crate thread_local; #[macro_use] extern crate hlua; diff --git a/src/modules/irc.rs b/src/modules/irc.rs new file mode 100644 index 0000000..abaeee4 --- /dev/null +++ b/src/modules/irc.rs @@ -0,0 +1,154 @@ +use irc::client::prelude::*; +use irc::client::data::command::Command; + +use modules::EventLoop; +use toml::value::Table; + +use event; + +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::Receiver; +use transformable_channels::mpsc::ExtSender; + +use std::fmt; +use std::fmt::{Debug, Formatter}; + +use crossbeam; + +use thread_local::CachedThreadLocal; +use {MessageSender, Message, User, Channel}; + +pub struct IrcHandler { + config: Config +} + +const DEFAULT_NICK: &'static str = "tenquestionmarks"; + +impl IrcHandler { + pub fn new (_: &Table, configuration: &Table) -> Box { + Box::new(IrcHandler { + config: Config { + nickname: Some(configuration.get("nickname") + .and_then(|value| value.as_str()) + .unwrap_or(DEFAULT_NICK) + .to_owned()), + server: Some(configuration.get("server") + .and_then(|value| value.as_str()) + .expect("Expected server in IRC config") + .to_owned()), + channels: Some(configuration.get("channels") + .and_then(|value| value.as_array()) + .map(|value| value.to_vec()) + .unwrap_or(vec![]) + .into_iter() + .map(|value| { String::from(value.as_str().unwrap()) }) + .collect()), + .. Default::default() + } + }) + } +} + +pub struct IrcMessageSender { + irc: IrcServerWrapper, + channel: String +} + +impl MessageSender for IrcMessageSender { + fn send_message (&self, message: &str) { + debug!("Send message to channel {:?}: {:?}", self.channel, message); + match self.irc.get().send_privmsg(&self.channel, message) { + Ok(_) => { debug!("Send message succeeded"); }, + Err(err) => { error!("Send message failed: {:?}", err) } + } + } +} + +impl Debug for IrcMessageSender { + fn fmt (&self, formatter: &mut Formatter) -> fmt::Result { + write!(formatter, "IrcMessageSender {{ channel: {:?} }}", self.channel) + } +} + +fn make_user (prefix: &Option, server: &IrcServer) -> Option { + prefix.as_ref().and_then(|prefix| prefix.split("!").next()).map(|name| User { + name: name.to_owned(), + sender: Box::new(IrcMessageSender { + irc: IrcServerWrapper::new(&server), + channel: name.to_owned() + }) + }) +} + +struct IrcServerWrapper { + server: IrcServer, + thread_local: CachedThreadLocal +} + +impl IrcServerWrapper { + pub fn new (server: &IrcServer) -> IrcServerWrapper { + IrcServerWrapper { + server: server.clone(), + thread_local: CachedThreadLocal::new() + } + } + + pub fn get (&self) -> &IrcServer { + self.thread_local.get_or(|| Box::new(self.server.clone())) + } +} + +unsafe impl Sync for IrcServerWrapper {} + +impl EventLoop for IrcHandler { + fn run (&self, sender: Box>, receiver: Receiver>) { + let server = IrcServer::from_config(self.config.clone()).unwrap(); + server.identify().unwrap(); + + crossbeam::scope(|scope| { + let server_sender = server.clone(); + scope.spawn(move || { + loop { + if let Ok(envelope) = receiver.recv() { + if let event::Event::Message { ref message } = envelope.event { + if let Some(ref channel) = message.channel { + server_sender.send_privmsg(&channel.name, &message.content); + } + } + } else { + break; + } + } + }); + + for server_message in server.iter() { + if let Ok(irc_message) = server_message { + match irc_message.command { + Command::PRIVMSG(channel, message) => { + if let Some(author) = make_user(&irc_message.prefix, &server) { + let message = Message { + author: author, + content: message, + channel: Some(Channel { + name: channel.clone(), + description: "".to_owned(), + topic: "".to_owned(), + sender: Box::new(IrcMessageSender { + irc: IrcServerWrapper::new(&server), + channel: channel.clone() + }) + }) + }; + + if let Err(err) = sender.send(event::Event::Message { message: message }) { + error!("Error sending message event: {:?}", err) + } + } + }, + _ => {} + } + } + } + }); + } +} diff --git a/src/modules/loader.rs b/src/modules/loader.rs index 0e56035..d92fe07 100644 --- a/src/modules/loader.rs +++ b/src/modules/loader.rs @@ -14,6 +14,7 @@ use modules::pvn::PvnModule; use modules::echobox::EchoboxModule; use modules::autolink::AutolinkModule; use modules::logger::LoggerModule; +use modules::irc::IrcHandler; use std::sync::{Arc, Mutex}; @@ -33,6 +34,7 @@ impl ModuleLoader { types.insert("echobox", EchoboxModule::new as fn(&Table, &Table) -> Box); types.insert("autolink", AutolinkModule::new as fn(&Table, &Table) -> Box); types.insert("logger", LoggerModule::new as fn(&Table, &Table) -> Box); + types.insert("irc", IrcHandler::new as fn(&Table, &Table) -> Box); ModuleLoader { types: types } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index cf6e284..a9d2315 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -7,6 +7,7 @@ pub mod pvn; pub mod echobox; pub mod autolink; pub mod logger; +pub mod irc; pub mod loader; diff --git a/tenquestionmarks.toml b/tenquestionmarks.toml index 6489b25..0068fdd 100644 --- a/tenquestionmarks.toml +++ b/tenquestionmarks.toml @@ -1,18 +1,22 @@ [general] foo = "bar" +[irc] +server = "irc.rizon.net" +channels = ["#eightbar"] + [discord] token = "your token here" [stdin] [echo] -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] prefix = "?echo" [no] type = "random" -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] prefix = "?no" responses = [ "https://www.youtube.com/watch?v=WWaLxFIVX1s", # Darth Vader @@ -38,7 +42,7 @@ responses = [ [yes] type = "random" -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] prefix = "?yes" responses = [ "https://www.youtube.com/watch?v=JPVaDaynNKM", # Captain Falcon @@ -61,15 +65,15 @@ responses = [ [chk] type = "random" -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] prefix = "?chk" responses = ["ack"] [pvn] -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] [echobox] -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] [lua] parents = ["stdin", "discord"] @@ -91,20 +95,20 @@ end """ [autolink] -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] [logger] -parents = ["stdin", "discord"] -filters = [{ username = "Dave" }, { username = "Kevin" }] +parents = ["stdin", "discord", "irc"] +#filters = [{ username = "Dave" }, { username = "Kevin" }] [icced] type = "random" -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] pattern = "\\bicc?ed?\\b" responses = ["Did some carbon-based lifeform just say **I C E**?"] [trout] type = "random" -parents = ["stdin", "discord"] +parents = ["stdin", "discord", "irc"] pattern = "^?slap (.*)" responses = ["/me slaps $1 around a bit with a large trout", "/me slaps $1 around a bit with a large brick"]