Commit 5162ce12 authored by Samsai's avatar Samsai
Browse files

Initial commit to git, working replacement for GOLBOTv2 written in Rust

parents
/target
**/*.rs.bk
[package]
name = "golbot-rust"
version = "0.1.0"
authors = ["Sami Lahtinen <thesamsai@gmail.com>"]
[dependencies]
regex = "1"
lazy_static = "1.1.0"
reqwest = "0.9.4"
rss = { version = "1.6.0", features = ["from_url"] }
chrono = "0.4"
toml = "0.4.8"
serde = "1.0.80"
serde_derive = "1.0.80"
nick = "GOLBOTv3"
serv = "chat.freenode.net"
channel = "#golbottest"
realname = "I have none"
ident = "GOLBOT"
rss = "https://www.gamingonlinux.com/article_rss.php?mini"
url_ignore = ["GOLBOTv2"]
extern crate rss;
extern crate chrono;
extern crate regex;
extern crate toml;
#[macro_use]
extern crate serde_derive;
#[macro_use] extern crate lazy_static;
use regex::Regex;
use toml::Value;
use std::io::prelude::*;
use std::io::Error;
use std::net::TcpStream;
use std::sync::RwLock;
use std::sync::Mutex;
use std::sync::Arc;
use std::collections::VecDeque;
use std::thread;
use std::sync::atomic::AtomicBool;
use std::boxed::Box;
use std::time::Duration;
use std::path::Path;
use std::fs::File;
mod plugin;
use plugin::*;
use plugin::pingpong::PingPong;
use plugin::rss_plugin::RssReader;
use plugin::hello::Hello;
use plugin::seen::LastSeen;
use plugin::url_reader::UrlReader;
use plugin::tell::Tell;
#[derive(Deserialize)]
pub struct BotConfig {
nick: String,
serv: String,
channel: String,
realname: String,
ident: String,
password: Option<String>,
}
impl BotConfig {
fn new(nick: &str, serv: &str,
channel: &str, realname: &str,
ident: &str, password: &str) -> BotConfig {
return BotConfig {
nick: String::from(nick),
serv: String::from(serv),
channel: String::from(channel),
realname: String::from(realname),
ident: String::from(ident),
password: Some(String::from(password)),
}
}
}
fn send_sock(sock: &mut TcpStream, msg: String) -> Result<usize, Error> {
sock.write(msg.as_bytes())
}
fn recv_sock(sock: &mut TcpStream) -> Result<String, Error> {
let mut buffer: [u8; 1024] = [0 as u8; 1024];
let res = sock.read(&mut buffer);
let mut vec_buffer: Vec<u8> = Vec::new();
for byte in buffer.iter() {
vec_buffer.push(byte.clone());
}
let msg = String::from(String::from_utf8(vec_buffer).unwrap().trim());
match res {
Ok(size) => {
Ok(msg)
}
Err(e) => Err(e)
}
}
fn form_chan_msg(conf: &BotConfig, msg: String) -> String {
return format!("PRIVMSG {} :{}\r\n", conf.channel, msg);
}
fn parse_chan_msg(msg: &String) -> Option<(String, String, String)> {
if msg.contains("PRIVMSG") {
lazy_static! {
static ref chan_msg_regex: Regex = Regex::new(r":(.+)!.+(#.*) :(.+)").unwrap();
}
println!("{}", &msg);
let chan_msg = chan_msg_regex.captures(msg);
if chan_msg.is_some() {
println!("This is a chan message");
let chan_msg = chan_msg.unwrap();
let nick = chan_msg.get(1).unwrap();
let chan = chan_msg.get(2).unwrap();
let msg = chan_msg.get(3).unwrap();
return Some((String::from(nick.as_str()),
String::from(chan.as_str()),
String::from(msg.as_str()))
);
}
}
return None;
}
fn read_bot_config(file: &Path) -> Option<BotConfig> {
if file.is_file() {
let mut fob = File::open(file).unwrap();
let mut text = String::new();
fob.read_to_string(&mut text);
let result: Result<BotConfig, toml::de::Error> = toml::from_str(text.as_str());
match result {
Ok(config) => return Some(config),
Err(e) => {
println!("Couldn't load config file: {}", e);
return None;
}
}
}
println!("Couldn't load config file: File not found.");
return None;
}
fn read_plugin_config(file: &Path) -> Option<Value> {
if file.is_file() {
let mut fob = File::open(file).unwrap();
let mut text = String::new();
fob.read_to_string(&mut text);
return Some(text.as_str().parse::<Value>().unwrap());
} else {
return None;
}
}
struct IRCBot {
config: Arc<BotConfig>,
pl_config: Arc<Value>,
plugins: Vec<Box<Plugin>>,
a_plugins: Vec<Box<AsyncPlugin>>,
in_queue: Arc<RwLock<VecDeque<String>>>,
out_queue: Arc<RwLock<VecDeque<String>>>,
running: Arc<RwLock<bool>>,
}
impl IRCBot {
fn new(config: Arc<BotConfig>, pl_config: Arc<Value>) -> IRCBot {
return IRCBot {
config: config,
pl_config: pl_config,
plugins: Vec::new(),
a_plugins: Vec::new(),
in_queue: Arc::new(RwLock::new(VecDeque::new())),
out_queue: Arc::new(RwLock::new(VecDeque::new())),
running: Arc::new(RwLock::new(false)),
};
}
fn send(&self, msg: String) {
let mut queue = self.out_queue.write().unwrap();
println!("-> {}", &msg);
queue.push_back(msg);
}
fn recv(&self) -> Option<String> {
let mut queue = self.in_queue.write().unwrap();
let msg = queue.pop_front();
match msg {
Some(s) => {
println!("<- {}", s);
return Some(s);
},
None => return None,
}
}
fn sender(out_queue: Arc<RwLock<VecDeque<String>>>, running: Arc<RwLock<bool>>, socket: TcpStream) {
let mut sock = socket;
let check_running = running;
loop {
{
let running = check_running.read().unwrap();
if *running == false {
break;
}
}
{
let mut queue = out_queue.write().unwrap();
let line = queue.pop_front();
match line {
Some(s) => {
let result = send_sock(&mut sock, s);
},
None => (),
}
}
thread::sleep(Duration::from_millis(50));
}
}
fn receiver(in_queue: Arc<RwLock<VecDeque<String>>>, running: Arc<RwLock<bool>>, socket: TcpStream) {
let mut sock = socket;
let check_running = running;
loop {
{
let running = check_running.read().unwrap();
if *running == false {
break;
}
}
let line = recv_sock(&mut sock);
match line {
Ok(s) => {
let mut queue = in_queue.write().unwrap();
queue.push_back(s);
}
Err(e) => {
let mut queue = in_queue.write().unwrap();
queue.push_back(String::from(""));
},
}
thread::sleep(Duration::from_millis(50));
}
}
fn add_plugin(&mut self, plugin: Box<Plugin>) {
self.plugins.push(plugin);
}
fn add_async_plugin(&mut self, plugin: Box<AsyncPlugin>) {
self.a_plugins.push(plugin);
}
fn start(&mut self) -> Result<i32, String> {
println!("Connecting to the server...");
let mut socket = TcpStream::connect(format!("{}:6667", &self.config.serv)).expect("Couldn't connect to server!");
self.send(format!("NICK {}\r\n", self.config.nick));
self.send(format!("USER {} {} bla :{}\r\n", self.config.ident, self.config.serv, self.config.realname));
let sock2 = socket.try_clone().unwrap();
let in_queue = self.in_queue.clone();
let out_queue = self.out_queue.clone();
{
let mut running = self.running.write().unwrap();
*running = true;
}
let running1 = self.running.clone();
let running2 = self.running.clone();
thread::spawn(move || IRCBot::sender(out_queue, running1.clone(), socket));
thread::spawn(move || IRCBot::receiver(in_queue, running2.clone(), sock2));
match &self.config.password {
Some(password) => {
self.send(format!("PRIVMSG NickServ :identify {} {}\r\n", self.config.nick, password));
loop {
{
let msg = self.recv();
match msg {
Some(message) => {
if message.contains("You are now identified for ") {
break;
}
},
None => (),
}
}
thread::sleep(Duration::from_millis(20));
}
},
None => (),
}
self.send(format!("JOIN {}\r\n", self.config.channel));
for plugin in &self.a_plugins {
plugin.start();
}
'mainloop: loop {
let message = self.recv();
match message {
Some(line) => {
if line.starts_with("ERROR:") {
let mut running = self.running.write().unwrap();
*running = false;
for plugin in &self.a_plugins {
plugin.stop();
}
return Err(String::from("An error occured, line severed."))
} else if line.len() == 0 {
let mut running = self.running.write().unwrap();
*running = false;
for plugin in &self.a_plugins {
plugin.stop();
}
return Err(String::from("Received an empty message, likely broken pipe."));
}
if line.len() > 0 {
let mut responses: Vec<String> = Vec::new();
for pl in &mut self.plugins {
let pl_resp = pl.process_line(&line);
match pl_resp {
Some(msg) => {
responses.push(msg);
}
None => (),
}
}
for resp in responses {
self.send(resp);
}
}
}
None => (),
}
thread::sleep(Duration::from_millis(50));
}
let mut running = self.running.write().unwrap();
*running = false;
return Ok(0);
}
}
fn main() {
let config = read_bot_config(Path::new("botconfig.toml")).unwrap();
let pl_config = read_plugin_config(Path::new("plugins.toml")).unwrap();
println!("{}", pl_config.to_string());
let mut bot = IRCBot::new(Arc::new(config), Arc::new(pl_config));
let conf = bot.config.clone();
let pl_conf = bot.pl_config.clone();
let out_queue = bot.out_queue.clone();
bot.add_plugin(Box::new(PingPong::new()));
bot.add_plugin(Box::new(Hello::new(conf.clone())));
bot.add_plugin(Box::new(LastSeen::new(conf.clone())));
bot.add_plugin(Box::new(UrlReader::new(conf.clone(), pl_conf.clone())));
bot.add_plugin(Box::new(Tell::new(conf.clone())));
bot.add_async_plugin(Box::new(RssReader::new(conf.clone(), pl_conf.clone(), out_queue.clone())));
loop {
let resp = bot.start();
match resp {
Err(e) => println!("{}", e),
Ok(a) => {
println!("Bot exited gracefully.");
}
}
}
}
use super::*;
pub struct Hello {
config: Arc<BotConfig>,
}
impl Hello {
pub fn new(config: Arc<BotConfig>) -> Hello {
return Hello {config: config};
}
}
impl Plugin for Hello {
fn name(&self) -> String {
return String::from("Hello");
}
fn process_line(&mut self, line: &String) -> Option<String> {
let line_clone = line.clone();
let msg = parse_chan_msg(&line_clone);
match msg {
Some((nick, chan, msg)) => {
if msg.contains(format!("hello, {}", self.config.nick).as_str()) {
return Some(form_chan_msg(&self.config, format!("Hello, {}!", &nick)))
}
},
None => return None,
}
return None;
}
}
use std::sync::Arc;
use std::sync::RwLock;
use std::collections::VecDeque;
use super::*;
pub mod pingpong;
pub mod rss_plugin;
pub mod hello;
pub mod seen;
pub mod url_reader;
pub mod tell;
pub trait Plugin {
fn name(&self) -> String;
fn process_line(&mut self, line: &String) -> Option<String>;
}
pub trait AsyncPlugin {
fn name(&self) -> String;
fn give_in_queue(&mut self, in_queue: Arc<RwLock<VecDeque<String>>>);
fn give_out_queue(&mut self, out_queue: Arc<RwLock<VecDeque<String>>>);
fn start(&self);
fn stop(&self);
}
use super::*;
pub struct PingPong {
}
impl PingPong {
pub fn new() -> PingPong {
return PingPong {};
}
}
impl Plugin for PingPong {
fn name(&self) -> String {
return String::from("PingPong");
}
fn process_line(&mut self, line: &String) -> Option<String> {
let line_clone = line.clone();
let split_line: Vec<&str> = line_clone.split_whitespace().collect();
if split_line.len() > 1 && split_line[0] == "PING" {
return Some(format!("PONG {}\r\n", split_line[1]));
}
return None;
}
}
use super::*;
pub struct RssReader {
config: Arc<BotConfig>,
out_queue: Arc<RwLock<VecDeque<String>>>,
rss_feed: Arc<String>,
running: Arc<RwLock<bool>>
}
impl RssReader {
pub fn new(config: Arc<BotConfig>, pl_config: Arc<Value>, out_queue: Arc<RwLock<VecDeque<String>>>) -> RssReader {
let value = pl_config.get("rss");
let mut rss_feed: String = String::from("http://lorem-rss.herokuapp.com/feed");
match value {
Some(val) => {
if val.is_str() {
rss_feed = String::from(val.as_str().unwrap());
}
}
None => (),
}
return RssReader {
config: config,
out_queue: out_queue,
rss_feed: Arc::new(rss_feed),
running: Arc::new(RwLock::new(false)),
};
}
}
impl AsyncPlugin for RssReader {
fn name(&self) -> String {
return String::from("RSS");
}
fn give_in_queue(&mut self, in_queue: Arc<RwLock<VecDeque<String>>>) {
}
fn give_out_queue(&mut self, out_queue: Arc<RwLock<VecDeque<String>>>) {
self.out_queue = out_queue;
}
fn start(&self) {
let out_queue = self.out_queue.clone();
let config = self.config.clone();
let feed = self.rss_feed.clone();
let running = self.running.clone();
thread::spawn(move || {
let mut last_posted: i64 = 0;
let mut discarded = false;
loop {
{
let check_running = running.read().unwrap();
if *check_running == false {
break;
}
}
let mut rss_channel_result = rss::Channel::from_url(feed.as_str());
if rss_channel_result.is_ok() {
let mut rss_channel = rss_channel_result.unwrap();
let mut items = rss_channel.items_mut();
items.reverse();
for item in items.iter() {
let date = item.pub_date();