From d5353fb0193505caab5d5a7e7bf7e01a1ede86a1 Mon Sep 17 00:00:00 2001 From: Thelie Date: Fri, 7 May 2021 19:12:19 +0200 Subject: [PATCH 01/21] Added skeleton for program entry. --- src/main.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6b7f18e..988c2df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -168,9 +168,37 @@ fn handle_message(message: Message) -> Option { } fn main() { - let config_path = args().nth(1) - .expect("no config given"); - let config = get_config(config_path).expect("Could not get config"); + let mut server_conf_flag = false; + let mut config_path = None; + let mut server_conf_path = None; + for arg in args().skip(1) { + match arg.as_str() { + "-s" => server_conf_flag = true, + "-h" => { + println!("Usage: mention2mail [config file] [-s server config directory]"); + }, + _ => if server_conf_flag { + server_conf_path = Some(arg); + } else { + config_path = Some(arg); + } + } + } + + let mut config; + + match config_path { + Some(p) => config = get_config(p) + .expect("Could not get config"), + None => config = get_config("/etc/Mention2Mail/config.toml") + .expect("Could not get default config in /etc/Mention2Mail/default.toml"), + } + + match server_conf_path { + Some(p) => config = get_server_config(config, p) + .expect("Could not get server config."), + None => (), + } let mut stream = connect::connect_irc(&config).unwrap(); From 716584841ec857d6739e4752774d34fde0423a03 Mon Sep 17 00:00:00 2001 From: Thelie Date: Tue, 11 May 2021 00:56:54 +0200 Subject: [PATCH 02/21] Split config commands into a module and broke the code a bit. --- src/config.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 41 +++--------------------------- 2 files changed, 74 insertions(+), 37 deletions(-) create mode 100644 src/config.rs diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5dd2761 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,70 @@ +/* Copyright 2021 Daniel Mowitz + * This file is part of Mention2Mail. + * + * Mention2Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mention2Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mention2Mail. If not, see . + */ + +use std::{ + path::Path, + io::Read, + fs::read_dir, +}; +use toml::value::Value; + +/// parses a toml config file and sets some default values +/// if the corresponding fields are not set. +// TODO: Document config file options once they are stable. +pub fn get_main_config(config_path: &Path) + -> Result> { + + // I once read that this multiple let style + // is the "rusty" way to do things. + // Personally, I find this specific instance to be somewhat sketchy. + let mut config = String::new(); + std::fs::File::open(config_path)? + .read_to_string(&mut config)?; + let mut config = config.parse::()?; + // The important thing here is that config is a mut Table in the end. + let mut config = config.as_table_mut().unwrap().clone(); + + if config.get("port") == None { + config.insert(String::from("port"), Value::from("6697")); + } + + if config.get("tls") == None { + if config["port"].as_integer() == Some(194) || + config["port"].as_integer() == Some(6667) { + config.insert(String::from("tls"), Value::from(false)); + }else if config["port"].as_integer() == Some(6696) || + config["port"].as_integer() == Some(6697) { + config.insert(String::from("tls"), Value::from(true)); + } + } + + Ok(Value::from(config)) +} + +pub fn get_server_config (config: Value, config_path: &Path) + -> Result> { + if config_path.is_dir() { + for entry in read_dir(config_path)? { + let entry = entry?; + let server_path = entry.path(); + if server_path.is_dir(){ + config = server_config_helper(config, server_path)?; + } + } + } + Ok(config) +} diff --git a/src/main.rs b/src/main.rs index 988c2df..a402bc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,12 +16,12 @@ */ pub mod connect; +pub mod config; use std::{ format, env::args, path::Path, - io::Read, collections::VecDeque, }; use irc_proto::command::{ @@ -38,39 +38,6 @@ use toml::value::Value; trait IrcReadWrite: IrcRead + IrcWrite {} impl IrcReadWrite for T {} -/// parses a toml config file and sets some default values -/// if the corresponding fields are not set. -// TODO: Document config file options once they are stable. -fn get_config>(config_path: P) - -> Result> { - - // I once read that this multiple let style - // is the "rusty" way to do things. - // Personally, I find this specific instance to be somewhat sketchy. - let mut config = String::new(); - std::fs::File::open(config_path)? - .read_to_string(&mut config)?; - let mut config = config.parse::()?; - // The important thing here is that config is a mut Table in the end. - let mut config = config.as_table_mut().unwrap().clone(); - - if config.get("port") == None { - config.insert(String::from("port"), Value::from("6697")); - } - - if config.get("tls") == None { - if config["port"].as_integer() == Some(194) || - config["port"].as_integer() == Some(6667) { - config.insert(String::from("tls"), Value::from(false)); - }else if config["port"].as_integer() == Some(6696) || - config["port"].as_integer() == Some(6697) { - config.insert(String::from("tls"), Value::from(true)); - } - } - - Ok(Value::from(config)) -} - /// Constructs a VecDeque with the messages /// required to identify with an IRC server. /// Uses the credentials set in -`config.toml`. @@ -188,14 +155,14 @@ fn main() { let mut config; match config_path { - Some(p) => config = get_config(p) + Some(p) => config = config::get_main_config(&Path::from(p)) .expect("Could not get config"), - None => config = get_config("/etc/Mention2Mail/config.toml") + None => config = config::get_main_config(&Path::from("/etc/Mention2Mail/config.toml")) .expect("Could not get default config in /etc/Mention2Mail/default.toml"), } match server_conf_path { - Some(p) => config = get_server_config(config, p) + Some(p) => config = config::get_server_config(config, p) .expect("Could not get server config."), None => (), } From 78d5bafae34f3c1c356ece3ca73f7667d1230a26 Mon Sep 17 00:00:00 2001 From: Thelie Date: Wed, 12 May 2021 00:13:16 +0200 Subject: [PATCH 03/21] Added function for reading server config dirs. It's not complete yet. --- src/config.rs | 26 ++++++++++++++++++++++---- src/main.rs | 8 ++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5dd2761..318ea4e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,16 +16,34 @@ */ use std::{ - path::Path, + path::PathBuf, io::Read, fs::read_dir, }; use toml::value::Value; +fn parse_server_dir(mut config: Value, server_config_path: PathBuf) + -> Result> { + let mut config = config.as_table_mut().unwrap().clone(); + if server_config_path.is_dir() { + for entry in read_dir(server_config_path)? { + let entry = entry?; + match entry.file_name().to_str().ok_or("Could not read file name")? { + "config.toml" => (), + "clients.toml" => (), + "channels.toml" => (), + _ => (), + } + } + } + + Ok(Value::from(config)) +} + /// parses a toml config file and sets some default values /// if the corresponding fields are not set. // TODO: Document config file options once they are stable. -pub fn get_main_config(config_path: &Path) +pub fn get_main_config(config_path: PathBuf) -> Result> { // I once read that this multiple let style @@ -55,14 +73,14 @@ pub fn get_main_config(config_path: &Path) Ok(Value::from(config)) } -pub fn get_server_config (config: Value, config_path: &Path) +pub fn get_server_config(mut config: Value, config_path: PathBuf) -> Result> { if config_path.is_dir() { for entry in read_dir(config_path)? { let entry = entry?; let server_path = entry.path(); if server_path.is_dir(){ - config = server_config_helper(config, server_path)?; + config = parse_server_dir(config, server_path)?; } } } diff --git a/src/main.rs b/src/main.rs index a402bc2..3d7f23b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ pub mod config; use std::{ format, env::args, - path::Path, + path::PathBuf, collections::VecDeque, }; use irc_proto::command::{ @@ -155,14 +155,14 @@ fn main() { let mut config; match config_path { - Some(p) => config = config::get_main_config(&Path::from(p)) + Some(p) => config = config::get_main_config(PathBuf::from(p)) .expect("Could not get config"), - None => config = config::get_main_config(&Path::from("/etc/Mention2Mail/config.toml")) + None => config = config::get_main_config(PathBuf::from("/etc/Mention2Mail/config.toml")) .expect("Could not get default config in /etc/Mention2Mail/default.toml"), } match server_conf_path { - Some(p) => config = config::get_server_config(config, p) + Some(p) => config = config::get_server_config(config, PathBuf::from(p)) .expect("Could not get server config."), None => (), } From c47b511e04b7e2972b6991e0e10e1cc1d55dfebe Mon Sep 17 00:00:00 2001 From: Thelie Date: Fri, 14 May 2021 20:55:38 +0200 Subject: [PATCH 04/21] Broke the code some more and created some helper functions for parsing a config tree. --- src/config.rs | 69 +++++++++++++++++++++++++++++++++++++-------------- src/main.rs | 6 +++-- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/config.rs b/src/config.rs index 318ea4e..187fb9f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,7 +20,35 @@ use std::{ io::Read, fs::read_dir, }; -use toml::value::Value; +use toml::{ + value::Value, + map::Map, +}; + +fn parse_toml_to_table(path: PathBuf) + -> Result, Box> { + // I once read that this multiple let style + // is the "rusty" way to do things. + // Personally, I find this specific instance to be somewhat sketchy. + let mut config = String::new(); + std::fs::File::open(path)? + .read_to_string(&mut config)?; + let mut config = config.parse::()?; + // The important thing here is that config is a mut Table in the end. + Ok(config.as_table_mut().unwrap().clone()) +} + +fn merge_config_maps(mut receiver: Map, donor: Map) + -> Result, Box> { + for key in donor.keys() { + if receiver.contains_key(&key.clone()) { + receiver[&key.clone()] = donor[key].clone(); + } else { + receiver.insert(key.clone(), donor[key].clone()); + } + } + Ok(receiver) +} fn parse_server_dir(mut config: Value, server_config_path: PathBuf) -> Result> { @@ -29,9 +57,22 @@ fn parse_server_dir(mut config: Value, server_config_path: PathBuf) for entry in read_dir(server_config_path)? { let entry = entry?; match entry.file_name().to_str().ok_or("Could not read file name")? { - "config.toml" => (), - "clients.toml" => (), - "channels.toml" => (), + "config.toml" => { + let server_config = parse_toml_to_table(entry.path())?; + config = merge_config_maps(config, server_config)?; + }, + "clients.toml" => { + let client_config = parse_toml_to_table(entry.path())?; + config = merge_config_maps(config, client_config)?; + }, + "channels.toml" => { + let channels = Value::from(parse_toml_to_table(entry.path())?); + if config.contains_key(&"channels".to_owned()) { + config[&"channels".to_owned()] = channels; + } else { + config.insert("channels".to_owned(), channels); + } + }, _ => (), } } @@ -45,17 +86,8 @@ fn parse_server_dir(mut config: Value, server_config_path: PathBuf) // TODO: Document config file options once they are stable. pub fn get_main_config(config_path: PathBuf) -> Result> { + let mut config = parse_toml_to_table(config_path)?; - // I once read that this multiple let style - // is the "rusty" way to do things. - // Personally, I find this specific instance to be somewhat sketchy. - let mut config = String::new(); - std::fs::File::open(config_path)? - .read_to_string(&mut config)?; - let mut config = config.parse::()?; - // The important thing here is that config is a mut Table in the end. - let mut config = config.as_table_mut().unwrap().clone(); - if config.get("port") == None { config.insert(String::from("port"), Value::from("6697")); } @@ -73,16 +105,17 @@ pub fn get_main_config(config_path: PathBuf) Ok(Value::from(config)) } -pub fn get_server_config(mut config: Value, config_path: PathBuf) - -> Result> { +pub fn get_server_configs(config: Value, config_path: PathBuf) + -> Result, Box> { + let mut config_vec = vec![]; if config_path.is_dir() { for entry in read_dir(config_path)? { let entry = entry?; let server_path = entry.path(); if server_path.is_dir(){ - config = parse_server_dir(config, server_path)?; + config_vec.push(parse_server_dir(config.clone(), server_path)?); } } } - Ok(config) + Ok(config_vec) } diff --git a/src/main.rs b/src/main.rs index 3d7f23b..d1c86b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -152,7 +152,7 @@ fn main() { } } - let mut config; + let config; match config_path { Some(p) => config = config::get_main_config(PathBuf::from(p)) @@ -161,8 +161,10 @@ fn main() { .expect("Could not get default config in /etc/Mention2Mail/default.toml"), } + let mut server_configs; + match server_conf_path { - Some(p) => config = config::get_server_config(config, PathBuf::from(p)) + Some(p) => server_configs = config::get_server_configs(config, PathBuf::from(p)) .expect("Could not get server config."), None => (), } From 8ab294277b5b1ad61cae7025b5adfac9e0b92125 Mon Sep 17 00:00:00 2001 From: Thelie Date: Fri, 14 May 2021 21:16:20 +0200 Subject: [PATCH 05/21] Added the beginning of the threaded code. --- src/main.rs | 85 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/src/main.rs b/src/main.rs index d1c86b8..57e7e52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,11 @@ use std::{ env::args, path::PathBuf, collections::VecDeque, + sync::mpsc::{ + channel, + Sender, + }, + thread, }; use irc_proto::command::{ CapSubCommand, @@ -134,41 +139,7 @@ fn handle_message(message: Message) -> Option { return None } -fn main() { - let mut server_conf_flag = false; - let mut config_path = None; - let mut server_conf_path = None; - for arg in args().skip(1) { - match arg.as_str() { - "-s" => server_conf_flag = true, - "-h" => { - println!("Usage: mention2mail [config file] [-s server config directory]"); - }, - _ => if server_conf_flag { - server_conf_path = Some(arg); - } else { - config_path = Some(arg); - } - } - } - - let config; - - match config_path { - Some(p) => config = config::get_main_config(PathBuf::from(p)) - .expect("Could not get config"), - None => config = config::get_main_config(PathBuf::from("/etc/Mention2Mail/config.toml")) - .expect("Could not get default config in /etc/Mention2Mail/default.toml"), - } - - let mut server_configs; - - match server_conf_path { - Some(p) => server_configs = config::get_server_configs(config, PathBuf::from(p)) - .expect("Could not get server config."), - None => (), - } - +fn handle_server(config: Value, tx: Sender<[String; 4]>) { let mut stream = connect::connect_irc(&config).unwrap(); let mut message_queue = get_irc_identify_messages(&config).unwrap(); @@ -225,3 +196,47 @@ fn main() { } } +fn main() { + let mut server_conf_flag = false; + let mut config_path = None; + let mut server_conf_path = None; + for arg in args().skip(1) { + match arg.as_str() { + "-s" => server_conf_flag = true, + "-h" => { + println!("Usage: mention2mail [config file] [-s server config directory]"); + }, + _ => if server_conf_flag { + server_conf_path = Some(arg); + } else { + config_path = Some(arg); + } + } + } + + let config; + + match config_path { + Some(p) => config = config::get_main_config(PathBuf::from(p)) + .expect("Could not get config"), + None => config = config::get_main_config(PathBuf::from("/etc/Mention2Mail/config.toml")) + .expect("Could not get default config in /etc/Mention2Mail/default.toml"), + } + + let mut server_configs; + + match server_conf_path { + Some(p) => server_configs = config::get_server_configs(config, PathBuf::from(p)) + .expect("Could not get server config."), + None => (), + } + + let (tx,rx) = channel(); + + for s_conf in server_configs { + // TODO: create channel and spawn server threads + handle_server(s_conf, tx.clone()); + } + +} + From 500d87d65b4d724c3ed1eb66222c72b2f2f06d01 Mon Sep 17 00:00:00 2001 From: Thelie Date: Mon, 17 May 2021 22:50:21 +0200 Subject: [PATCH 06/21] Added threading and beautified the code a bit. --- src/main.rs | 77 +++++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/main.rs b/src/main.rs index 57e7e52..e4a1347 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,16 +117,19 @@ fn get_irc_join_messages(config: &Value, queue: VecDeque) Ok(queue) } -fn handle_message(message: Message) -> Option { +fn handle_message(message: Message) -> (Option, Option<[String; 4]>) { let sender = match message.clone().source_nickname() { Some(s) => String::from(s), None => String::from("anonymous") }; match message.clone().command { Command::PING(ref data, _) => { - return Some(Message::from( - Command::PONG(data.to_owned(), None) - )); + return ( + Some(Message::from( + Command::PONG(data.to_owned(), None) + )), + None + ); }, Command::PRIVMSG(ref rec, ref msg) => println!( "{} -> {}: {}", @@ -136,10 +139,11 @@ fn handle_message(message: Message) -> Option { ), _ => println!("{}", message.clone().to_string()) } - return None + return (None, None) } -fn handle_server(config: Value, tx: Sender<[String; 4]>) { +fn handle_server(config: Value, tx: Sender<[String; 4]>) + -> Result<(), Box> { let mut stream = connect::connect_irc(&config).unwrap(); let mut message_queue = get_irc_identify_messages(&config).unwrap(); @@ -151,17 +155,8 @@ fn handle_server(config: Value, tx: Sender<[String; 4]>) { message_queue = get_irc_join_messages(&config, message_queue).unwrap(); // Wait for first ping and join channels after sending pong. - // TODO this approach is not very DRY! loop { - let message = match stream.read() { - Ok(m) => m, - Err(e) => Message::from( - Command::PRIVMSG( - String::from("Error"), - format!("{}", e) - ) - ) - }; + let message = stream.read()?; match message.command { Command::PING(ref data, _) => { stream.write(Message::from( @@ -172,27 +167,22 @@ fn handle_server(config: Value, tx: Sender<[String; 4]>) { } break; } - _ => match handle_message(message) { - Some(m) => stream.write(m).unwrap(), - None => () - } + _ => () } } + // Handle all incoming messages after joining the channels. loop { - let message = match stream.read() { - Ok(m) => m, - Err(e) => Message::from( - Command::PRIVMSG( - String::from("Error"), - format!("{}", e) - ) - ) - }; - match handle_message(message) { - Some(m) => stream.write(m).unwrap(), + let message = stream.read()?; + let (answer, data) = handle_message(message); + match answer { + Some(a) => stream.write(a).unwrap(), None => () } + match data { + Some(d) => tx.send(d)?, + None => (), + } } } @@ -223,19 +213,36 @@ fn main() { .expect("Could not get default config in /etc/Mention2Mail/default.toml"), } - let mut server_configs; + let server_configs; match server_conf_path { Some(p) => server_configs = config::get_server_configs(config, PathBuf::from(p)) .expect("Could not get server config."), - None => (), + None => server_configs = vec![config], } let (tx,rx) = channel(); + let mut server_threads = vec![]; for s_conf in server_configs { - // TODO: create channel and spawn server threads - handle_server(s_conf, tx.clone()); + let t = tx.clone(); + server_threads.push(thread::Builder::new() + .name("server name here".to_string()) + .spawn(move || { + handle_server(s_conf, t).unwrap(); + }) + ); + } + + loop { + match rx.recv() { + Ok(data) => println!("{},{},{},{}", + data[0], + data[1], + data[2], + data[3]), + Err(_e) => (), + } } } From 5cd35e2e8e3085c37a9bc7ddb31c1d2803d1d346 Mon Sep 17 00:00:00 2001 From: Thelie Date: Tue, 18 May 2021 20:40:22 +0200 Subject: [PATCH 07/21] Passes the messages from channels correctly now. --- .gitignore | 1 + src/main.rs | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index f044ebe..596273a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ target/ # Dont track the client config config.toml +config/ diff --git a/src/main.rs b/src/main.rs index e4a1347..fc7db93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,6 @@ pub mod connect; pub mod config; use std::{ - format, env::args, path::PathBuf, collections::VecDeque, @@ -117,7 +116,7 @@ fn get_irc_join_messages(config: &Value, queue: VecDeque) Ok(queue) } -fn handle_message(message: Message) -> (Option, Option<[String; 4]>) { +fn handle_message(message: Message) -> (Option, Option<[String; 3]>) { let sender = match message.clone().source_nickname() { Some(s) => String::from(s), None => String::from("anonymous") @@ -131,12 +130,9 @@ fn handle_message(message: Message) -> (Option, Option<[String; 4]>) { None ); }, - Command::PRIVMSG(ref rec, ref msg) => println!( - "{} -> {}: {}", - rec, - msg, - sender - ), + Command::PRIVMSG(ref rec, ref msg) => { + return (None, Some([rec.clone(), sender.clone(), msg.clone()])) + }, _ => println!("{}", message.clone().to_string()) } return (None, None) @@ -144,8 +140,9 @@ fn handle_message(message: Message) -> (Option, Option<[String; 4]>) { fn handle_server(config: Value, tx: Sender<[String; 4]>) -> Result<(), Box> { + let server_name = config.get("server").unwrap().as_str() + .ok_or("Could not get server adress from config")?; let mut stream = connect::connect_irc(&config).unwrap(); - let mut message_queue = get_irc_identify_messages(&config).unwrap(); while let Some(message) = message_queue.pop_front() { @@ -180,7 +177,16 @@ fn handle_server(config: Value, tx: Sender<[String; 4]>) None => () } match data { - Some(d) => tx.send(d)?, + Some(d) => { + // There must be a better way to do this… + let d = [ + server_name.to_owned(), + d[0].clone(), + d[1].clone(), + d[2].clone() + ]; + tx.send(d)? + }, None => (), } } From a8ddf2009877960a3fba7b543f67cc1c8fa4736d Mon Sep 17 00:00:00 2001 From: Thelie Date: Tue, 18 May 2021 20:54:22 +0200 Subject: [PATCH 08/21] Split the code again and cleaned up a little. --- src/main.rs | 172 +---------------------------------------------- src/servers.rs | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 169 deletions(-) create mode 100644 src/servers.rs diff --git a/src/main.rs b/src/main.rs index fc7db93..852e25a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,180 +17,14 @@ pub mod connect; pub mod config; +pub mod servers; use std::{ env::args, path::PathBuf, - collections::VecDeque, - sync::mpsc::{ - channel, - Sender, - }, + sync::mpsc::channel, thread, }; -use irc_proto::command::{ - CapSubCommand, - Command, -}; -use irc_proto::message::Message; -use irc_stream::{ - IrcRead, - IrcWrite, -}; -use toml::value::Value; - -trait IrcReadWrite: IrcRead + IrcWrite {} -impl IrcReadWrite for T {} - -/// Constructs a VecDeque with the messages -/// required to identify with an IRC server. -/// Uses the credentials set in -`config.toml`. -fn get_irc_identify_messages (config: &Value) - -> Result, Box> { - let mut queue = VecDeque::new(); - - queue.push_back(Message::from( - Command::CAP( - None, - CapSubCommand::END, - None, - None - ))); - - match config.get("password") { - Some(p) => queue.push_back(Message::from( - Command::PASS( - String::from(p.as_str().ok_or("Could not parse password.")?) - ) - )), - None => () - } - - let nick: String; - match config.get("nickname") { - Some(n) => { - nick = String::from(n.as_str().ok_or("Could not parse nickname.")?); - queue.push_back(Message::from(Command::NICK(nick.clone()))); - }, - None => return Err("No nickname supplied!".into()), - } - - match config.get("username") { - Some(u) => queue.push_back(Message::from( - Command::USER( - String::from(u.as_str().ok_or("Could not parse username.")?), - "0".to_owned(), - String::from(u.as_str().ok_or("Could not parse username.")?) - ) - )), - None => queue.push_back(Message::from( - // nick.clone() is only used once because the value - // can be moved the second time - Command::USER(nick.clone(), "0".to_owned(), nick) - )) - } - Ok(queue) -} - -/// Appends a given VecDeque to include the messages -/// used to join the channels defined in `config.toml`. -fn get_irc_join_messages(config: &Value, queue: VecDeque) - -> Result, Box> { - let mut queue = queue; - - match config.get("channels") { - Some(c) => { - for channel in c.as_array().ok_or("Could not parse channels.")? { - queue.push_back(Message::from( - Command::JOIN( - String::from(channel.as_str().ok_or("Could not parse one of the channels")?), - None, - None - ) - )) - } - }, - None => () - } - - Ok(queue) -} - -fn handle_message(message: Message) -> (Option, Option<[String; 3]>) { - let sender = match message.clone().source_nickname() { - Some(s) => String::from(s), - None => String::from("anonymous") - }; - match message.clone().command { - Command::PING(ref data, _) => { - return ( - Some(Message::from( - Command::PONG(data.to_owned(), None) - )), - None - ); - }, - Command::PRIVMSG(ref rec, ref msg) => { - return (None, Some([rec.clone(), sender.clone(), msg.clone()])) - }, - _ => println!("{}", message.clone().to_string()) - } - return (None, None) -} - -fn handle_server(config: Value, tx: Sender<[String; 4]>) - -> Result<(), Box> { - let server_name = config.get("server").unwrap().as_str() - .ok_or("Could not get server adress from config")?; - let mut stream = connect::connect_irc(&config).unwrap(); - let mut message_queue = get_irc_identify_messages(&config).unwrap(); - - while let Some(message) = message_queue.pop_front() { - stream.write(message).unwrap(); - } - - message_queue = get_irc_join_messages(&config, message_queue).unwrap(); - - // Wait for first ping and join channels after sending pong. - loop { - let message = stream.read()?; - match message.command { - Command::PING(ref data, _) => { - stream.write(Message::from( - Command::PONG(data.to_owned(), None) - )).unwrap(); - while let Some(message) = message_queue.pop_front() { - stream.write(message).unwrap(); - } - break; - } - _ => () - } - } - - // Handle all incoming messages after joining the channels. - loop { - let message = stream.read()?; - let (answer, data) = handle_message(message); - match answer { - Some(a) => stream.write(a).unwrap(), - None => () - } - match data { - Some(d) => { - // There must be a better way to do this… - let d = [ - server_name.to_owned(), - d[0].clone(), - d[1].clone(), - d[2].clone() - ]; - tx.send(d)? - }, - None => (), - } - } -} fn main() { let mut server_conf_flag = false; @@ -235,7 +69,7 @@ fn main() { server_threads.push(thread::Builder::new() .name("server name here".to_string()) .spawn(move || { - handle_server(s_conf, t).unwrap(); + servers::handle_server(s_conf, t).unwrap(); }) ); } diff --git a/src/servers.rs b/src/servers.rs new file mode 100644 index 0000000..0e319dc --- /dev/null +++ b/src/servers.rs @@ -0,0 +1,178 @@ +/* Copyright 2021 Daniel Mowitz + * This file is part of Mention2Mail. + * + * Mention2Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mention2Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mention2Mail. If not, see . + */ + +use std::{ + collections::VecDeque, + sync::mpsc::Sender, +}; +use irc_proto::command::{ + CapSubCommand, + Command, +}; +use irc_proto::message::Message; +use toml::value::Value; + +/// Constructs a VecDeque with the messages +/// required to identify with an IRC server. +/// Uses the credentials set in -`config.toml`. +fn get_irc_identify_messages (config: &Value) + -> Result, Box> { + let mut queue = VecDeque::new(); + + queue.push_back(Message::from( + Command::CAP( + None, + CapSubCommand::END, + None, + None + ))); + + match config.get("password") { + Some(p) => queue.push_back(Message::from( + Command::PASS( + String::from(p.as_str().ok_or("Could not parse password.")?) + ) + )), + None => () + } + + let nick: String; + match config.get("nickname") { + Some(n) => { + nick = String::from(n.as_str().ok_or("Could not parse nickname.")?); + queue.push_back(Message::from(Command::NICK(nick.clone()))); + }, + None => return Err("No nickname supplied!".into()), + } + + match config.get("username") { + Some(u) => queue.push_back(Message::from( + Command::USER( + String::from(u.as_str().ok_or("Could not parse username.")?), + "0".to_owned(), + String::from(u.as_str().ok_or("Could not parse username.")?) + ) + )), + None => queue.push_back(Message::from( + // nick.clone() is only used once because the value + // can be moved the second time + Command::USER(nick.clone(), "0".to_owned(), nick) + )) + } + Ok(queue) +} + +/// Appends a given VecDeque to include the messages +/// used to join the channels defined in `config.toml`. +fn get_irc_join_messages(config: &Value, queue: VecDeque) + -> Result, Box> { + let mut queue = queue; + + match config.get("channels") { + Some(c) => { + for channel in c.as_array().ok_or("Could not parse channels.")? { + queue.push_back(Message::from( + Command::JOIN( + String::from(channel.as_str().ok_or("Could not parse one of the channels")?), + None, + None + ) + )) + } + }, + None => () + } + + Ok(queue) +} + +fn handle_message(message: Message) -> (Option, Option<[String; 3]>) { + let sender = match message.clone().source_nickname() { + Some(s) => String::from(s), + None => String::from("anonymous") + }; + match message.clone().command { + Command::PING(ref data, _) => { + return ( + Some(Message::from( + Command::PONG(data.to_owned(), None) + )), + None + ); + }, + Command::PRIVMSG(ref rec, ref msg) => { + return (None, Some([rec.clone(), sender.clone(), msg.clone()])) + }, + _ => println!("{}", message.clone().to_string()) + } + return (None, None) +} + +pub fn handle_server(config: Value, tx: Sender<[String; 4]>) + -> Result<(), Box> { + let server_name = config.get("server").unwrap().as_str() + .ok_or("Could not get server adress from config")?; + let mut stream = super::connect::connect_irc(&config).unwrap(); + let mut message_queue = get_irc_identify_messages(&config).unwrap(); + + while let Some(message) = message_queue.pop_front() { + stream.write(message).unwrap(); + } + + message_queue = get_irc_join_messages(&config, message_queue).unwrap(); + + // Wait for first ping and join channels after sending pong. + loop { + let message = stream.read()?; + match message.command { + Command::PING(ref data, _) => { + stream.write(Message::from( + Command::PONG(data.to_owned(), None) + )).unwrap(); + while let Some(message) = message_queue.pop_front() { + stream.write(message).unwrap(); + } + break; + } + _ => () + } + } + + // Handle all incoming messages after joining the channels. + loop { + let message = stream.read()?; + let (answer, data) = handle_message(message); + match answer { + Some(a) => stream.write(a).unwrap(), + None => () + } + match data { + Some(d) => { + // There must be a better way to do this… + let d = [ + server_name.to_owned(), + d[0].clone(), + d[1].clone(), + d[2].clone() + ]; + tx.send(d)? + }, + None => (), + } + } +} + From 620b901bc3179cd429dd2d1b6a8a2b71822afbef Mon Sep 17 00:00:00 2001 From: Thelie Date: Tue, 18 May 2021 21:35:57 +0200 Subject: [PATCH 09/21] Split the code up even more and added beginnings of the mailing process. --- src/main.rs | 17 +++++++++++++++++ src/servers.rs | 21 +++++++++++++++++++-- src/texts.rs | 21 +++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/texts.rs diff --git a/src/main.rs b/src/main.rs index 852e25a..7d3df5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ pub mod connect; pub mod config; pub mod servers; +pub mod texts; use std::{ env::args, @@ -25,6 +26,11 @@ use std::{ sync::mpsc::channel, thread, }; +use lettre::{ + Message, + SendmailTransport, + Transport +}; fn main() { let mut server_conf_flag = false; @@ -73,6 +79,8 @@ fn main() { }) ); } + + let mailer = SendmailTransport::new(); loop { match rx.recv() { @@ -81,6 +89,15 @@ fn main() { data[1], data[2], data[3]), + // let mail = Message::Builder() + // .from("m2m.chaostreff-alzye.de".parse().unwrap()) + // .to("Put adress here") + // .subject("You were mentioned in Channel on Server") + // .message("Put mention here"); + // match mailer.send(&email) { + // Ok(_) => println!("Email sent successfully!"), + // Err(e) => panic!("Could not send email: {:?}", e), + // } Err(_e) => (), } } diff --git a/src/servers.rs b/src/servers.rs index 0e319dc..b40a097 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -100,7 +100,8 @@ fn get_irc_join_messages(config: &Value, queue: VecDeque) Ok(queue) } -fn handle_message(message: Message) -> (Option, Option<[String; 3]>) { +fn handle_message(message: Message, nick: &String) + -> (Option, Option<[String; 3]>) { let sender = match message.clone().source_nickname() { Some(s) => String::from(s), None => String::from("anonymous") @@ -115,6 +116,19 @@ fn handle_message(message: Message) -> (Option, Option<[String; 3]>) { ); }, Command::PRIVMSG(ref rec, ref msg) => { + // Send bot info on receiving a private message. + if rec.as_str() == nick.as_str() { + return ( + Some(Message::from( + Command::PRIVMSG( + sender.clone(), + super::texts::get_bot_info(), + ) + ) + ), + None + ) + } return (None, Some([rec.clone(), sender.clone(), msg.clone()])) }, _ => println!("{}", message.clone().to_string()) @@ -126,6 +140,9 @@ pub fn handle_server(config: Value, tx: Sender<[String; 4]>) -> Result<(), Box> { let server_name = config.get("server").unwrap().as_str() .ok_or("Could not get server adress from config")?; + let nick = config.get("nickname").unwrap().as_str() + .ok_or("Could not get nickname from config")?.to_owned(); + let mut stream = super::connect::connect_irc(&config).unwrap(); let mut message_queue = get_irc_identify_messages(&config).unwrap(); @@ -155,7 +172,7 @@ pub fn handle_server(config: Value, tx: Sender<[String; 4]>) // Handle all incoming messages after joining the channels. loop { let message = stream.read()?; - let (answer, data) = handle_message(message); + let (answer, data) = handle_message(message, &nick); match answer { Some(a) => stream.write(a).unwrap(), None => () diff --git a/src/texts.rs b/src/texts.rs new file mode 100644 index 0000000..782a63c --- /dev/null +++ b/src/texts.rs @@ -0,0 +1,21 @@ +/* Copyright 2021 Daniel Mowitz + * This file is part of Mention2Mail. + * + * Mention2Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mention2Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mention2Mail. If not, see . + */ + +pub fn get_bot_info() -> String{ +"Hello! I'm an instance of Mention2Mail. I am licensed under the AGPLv3. You can find my source code at: https://gitea.chaostreff-alzey.de/Thelie/Mention2Mail/".to_owned() +} + From db6f753d6e69870acb46a610f8d32c973fde6ba7 Mon Sep 17 00:00:00 2001 From: Thelie Date: Tue, 18 May 2021 21:53:10 +0200 Subject: [PATCH 10/21] =?UTF-8?q?I=20just=20realized=20I=20need=20to=20do?= =?UTF-8?q?=20the=20detection=20of=20usernames=E2=80=A6=20Some=20other=20d?= =?UTF-8?q?ay.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/servers.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/servers.rs b/src/servers.rs index b40a097..48eded4 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -23,6 +23,7 @@ use irc_proto::command::{ CapSubCommand, Command, }; +use toml::map::Map; use irc_proto::message::Message; use toml::value::Value; @@ -142,7 +143,8 @@ pub fn handle_server(config: Value, tx: Sender<[String; 4]>) .ok_or("Could not get server adress from config")?; let nick = config.get("nickname").unwrap().as_str() .ok_or("Could not get nickname from config")?.to_owned(); - + let clients = config.get("clients").unwrap().as_table() + .get_or_insert(&Map::new()); let mut stream = super::connect::connect_irc(&config).unwrap(); let mut message_queue = get_irc_identify_messages(&config).unwrap(); @@ -178,16 +180,19 @@ pub fn handle_server(config: Value, tx: Sender<[String; 4]>) None => () } match data { - Some(d) => { - // There must be a better way to do this… - let d = [ - server_name.to_owned(), - d[0].clone(), - d[1].clone(), - d[2].clone() - ]; - tx.send(d)? - }, + Some(d) => match clients.get(d[0].clone()) { + Some(addr) => { + // There must be a better way to do this… + let out = [ + server_name.to_owned(), + d[0].clone(), + d[1].clone(), + d[2].clone(), + ]; + tx.send(out)? + }, + None => (), + }, None => (), } } From 66ca846ebb3acf757c63e298b389a832fe5a3f6f Mon Sep 17 00:00:00 2001 From: Thelie Date: Sat, 22 May 2021 20:32:41 +0200 Subject: [PATCH 11/21] Basic email sending implemented. --- Cargo.lock | 8 ++++---- src/main.rs | 37 ++++++++++++++++++------------------- src/servers.rs | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bffb171..d135573 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,9 +309,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lettre" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338d9a248c4b3ef60c51941c678bb8f64e244c0a98f1eb71db027d1e777a5700" +checksum = "86ed8677138975b573ab4949c35613931a4addeadd0a8a6aa0327e2a979660de" dependencies = [ "base64", "bufstream", @@ -583,9 +583,9 @@ checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote", diff --git a/src/main.rs b/src/main.rs index 7d3df5f..31728ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,9 +26,8 @@ use std::{ sync::mpsc::channel, thread, }; -use lettre::{ - Message, - SendmailTransport, +use lettre::{ + SendmailTransport, Transport }; @@ -80,27 +79,27 @@ fn main() { ); } - let mailer = SendmailTransport::new(); + let mut mailer = SendmailTransport::new(); loop { match rx.recv() { - Ok(data) => println!("{},{},{},{}", - data[0], - data[1], - data[2], - data[3]), - // let mail = Message::Builder() - // .from("m2m.chaostreff-alzye.de".parse().unwrap()) - // .to("Put adress here") - // .subject("You were mentioned in Channel on Server") - // .message("Put mention here"); - // match mailer.send(&email) { - // Ok(_) => println!("Email sent successfully!"), - // Err(e) => panic!("Could not send email: {:?}", e), - // } + Ok(data) => { + let mail = lettre::SendableEmail::new( + lettre::Envelope::new( + Some("m2m.chaostreff-alzye.de".parse().unwrap()), + vec![data[0].parse().unwrap()] + ).unwrap(), + format!("You were mentioned in {} on {}", data[2], data[1]) + .parse().unwrap(), + format!("{}: {}", data[3], data[4]).into() + ); + match mailer.send(mail) { + Ok(_) => println!("Email sent successfully!"), + Err(e) => panic!("Could not send email: {:?}", e), + } + } Err(_e) => (), } } - } diff --git a/src/servers.rs b/src/servers.rs index 48eded4..cc7081f 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -23,7 +23,10 @@ use irc_proto::command::{ CapSubCommand, Command, }; -use toml::map::Map; +use toml::map::{ + Map, + Keys +}; use irc_proto::message::Message; use toml::value::Value; @@ -101,8 +104,8 @@ fn get_irc_join_messages(config: &Value, queue: VecDeque) Ok(queue) } -fn handle_message(message: Message, nick: &String) - -> (Option, Option<[String; 3]>) { +fn handle_message(message: Message, nick: &String, client_names: Keys) + -> (Option, Option<[String; 4]>) { let sender = match message.clone().source_nickname() { Some(s) => String::from(s), None => String::from("anonymous") @@ -130,21 +133,34 @@ fn handle_message(message: Message, nick: &String) None ) } - return (None, Some([rec.clone(), sender.clone(), msg.clone()])) + for c_name in client_names { + if msg.contains(c_name) { + return (None, Some([c_name.clone(), + rec.clone(), + sender.clone(), + msg.clone() + ])) + } + } }, _ => println!("{}", message.clone().to_string()) } return (None, None) } -pub fn handle_server(config: Value, tx: Sender<[String; 4]>) +pub fn handle_server(config: Value, tx: Sender<[String; 5]>) -> Result<(), Box> { let server_name = config.get("server").unwrap().as_str() .ok_or("Could not get server adress from config")?; let nick = config.get("nickname").unwrap().as_str() .ok_or("Could not get nickname from config")?.to_owned(); - let clients = config.get("clients").unwrap().as_table() - .get_or_insert(&Map::new()); + + let clients; + match config.get("clients").unwrap().as_table() { + Some(t) => clients = t.clone(), + None => clients = Map::new(), + } + let mut stream = super::connect::connect_irc(&config).unwrap(); let mut message_queue = get_irc_identify_messages(&config).unwrap(); @@ -174,20 +190,21 @@ pub fn handle_server(config: Value, tx: Sender<[String; 4]>) // Handle all incoming messages after joining the channels. loop { let message = stream.read()?; - let (answer, data) = handle_message(message, &nick); + let (answer, data) = handle_message(message, &nick, clients.keys()); match answer { Some(a) => stream.write(a).unwrap(), None => () } match data { - Some(d) => match clients.get(d[0].clone()) { + Some(d) => match clients.get(&d[0]) { Some(addr) => { // There must be a better way to do this… let out = [ + addr.to_string(), server_name.to_owned(), - d[0].clone(), d[1].clone(), - d[2].clone(), + d[2].clone(), + d[3].clone(), ]; tx.send(out)? }, From e7b0925a643cf0a104207043b74206503822d693 Mon Sep 17 00:00:00 2001 From: Thelie Date: Sat, 22 May 2021 23:27:25 +0200 Subject: [PATCH 12/21] Fixed an issue with string conversion of the client addresses. --- src/main.rs | 16 ++++++++++------ src/servers.rs | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 31728ac..0bdc104 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,15 +83,19 @@ fn main() { loop { match rx.recv() { - Ok(data) => { + Ok(data) => { + println!("Sending mail to: {}", data[0]); let mail = lettre::SendableEmail::new( lettre::Envelope::new( - Some("m2m.chaostreff-alzye.de".parse().unwrap()), - vec![data[0].parse().unwrap()] + Some( + lettre::EmailAddress::new("m2m@chaostreff-alzey.de".to_owned()) + .unwrap() + ), + vec![ + lettre::EmailAddress::new(data[0].clone()).unwrap()] ).unwrap(), - format!("You were mentioned in {} on {}", data[2], data[1]) - .parse().unwrap(), - format!("{}: {}", data[3], data[4]).into() + format!("You were mentioned in {} on {}", data[2], data[1]), + format!("{}: {}", data[3], data[4]).into_bytes() ); match mailer.send(mail) { Ok(_) => println!("Email sent successfully!"), diff --git a/src/servers.rs b/src/servers.rs index cc7081f..47fe91d 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -200,7 +200,9 @@ pub fn handle_server(config: Value, tx: Sender<[String; 5]>) Some(addr) => { // There must be a better way to do this… let out = [ - addr.to_string(), + addr.as_str() + .ok_or("Could not parse email address.")? + .to_owned(), server_name.to_owned(), d[1].clone(), d[2].clone(), From af769100e72428f2b95528648dd9583e3e68f884 Mon Sep 17 00:00:00 2001 From: Thelie Date: Sun, 23 May 2021 02:08:13 +0200 Subject: [PATCH 13/21] Fixed a fun bug where the mail body got stuck in the header. --- src/main.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0bdc104..6f7ce39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,11 +80,13 @@ fn main() { } let mut mailer = SendmailTransport::new(); - + loop { match rx.recv() { Ok(data) => { println!("Sending mail to: {}", data[0]); + let title = format!("You were mentioned in {} on {}", data[2], data[1]); + let body = format!("{} wrote: {}", data[3], data[4]).into_bytes(); let mail = lettre::SendableEmail::new( lettre::Envelope::new( Some( @@ -94,13 +96,14 @@ fn main() { vec![ lettre::EmailAddress::new(data[0].clone()).unwrap()] ).unwrap(), - format!("You were mentioned in {} on {}", data[2], data[1]), - format!("{}: {}", data[3], data[4]).into_bytes() + title, + body ); - match mailer.send(mail) { - Ok(_) => println!("Email sent successfully!"), - Err(e) => panic!("Could not send email: {:?}", e), - } + + match mailer.send(mail) { + Ok(_) => println!("Email sent successfully!"), + Err(e) => panic!("Could not send email: {:?}", e), + } } Err(_e) => (), } From 2c26ab95def2dd2b51fde5d293eed55835751656 Mon Sep 17 00:00:00 2001 From: Thelie Date: Sun, 23 May 2021 22:44:24 +0200 Subject: [PATCH 14/21] Made mail subjects work properly. --- src/main.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6f7ce39..729b0d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,8 +85,9 @@ fn main() { match rx.recv() { Ok(data) => { println!("Sending mail to: {}", data[0]); - let title = format!("You were mentioned in {} on {}", data[2], data[1]); - let body = format!("{} wrote: {}", data[3], data[4]).into_bytes(); + let id = "notarealiid@mention2mail".to_owned(); + let body = format!("Subject: You were mentioned in {} on {}\n{} wrote: {}", + data[2], data[1], data[3], data[4]).into_bytes(); let mail = lettre::SendableEmail::new( lettre::Envelope::new( Some( @@ -96,7 +97,7 @@ fn main() { vec![ lettre::EmailAddress::new(data[0].clone()).unwrap()] ).unwrap(), - title, + id, body ); From 9fcbb5ad674e46f9ead4af0a3cb05651576c963e Mon Sep 17 00:00:00 2001 From: Thelie Date: Sun, 23 May 2021 22:45:30 +0200 Subject: [PATCH 15/21] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 534b3cf..74a8f5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mention_2_mail" -version = "0.1.0" +version = "0.2.0" authors = ["Thelie "] edition = "2018" From 4980c9e24c4b4acf631058560ef673ac12b629ca Mon Sep 17 00:00:00 2001 From: Thelie Date: Mon, 31 May 2021 01:04:09 +0200 Subject: [PATCH 16/21] Got closer to getting chat context to work and added doc comments. --- Cargo.lock | 4 +- src/main.rs | 26 +++-- src/servers.rs | 150 +++++++++++++++++++++------- ßqa | 263 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 400 insertions(+), 43 deletions(-) create mode 100644 ßqa diff --git a/Cargo.lock b/Cargo.lock index d135573..3699c22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ascii_utils" version = "0.9.3" @@ -348,7 +350,7 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "mention_2_mail" -version = "0.1.0" +version = "0.2.0" dependencies = [ "irc-proto", "irc_stream", diff --git a/src/main.rs b/src/main.rs index 729b0d8..22e1871 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,8 +65,9 @@ fn main() { .expect("Could not get server config."), None => server_configs = vec![config], } - - let (tx,rx) = channel(); + + // TODO: This line has many problems… + let (tx,rx): (std::sync::mpsc::Sender<(String, String, String, [Option<[String; 2]>; 5])>, std::sync::mpsc::Receiver<(String, String, String, [Option<[String; 2]>; 5])>)= channel(); let mut server_threads = vec![]; for s_conf in server_configs { @@ -83,11 +84,22 @@ fn main() { loop { match rx.recv() { - Ok(data) => { - println!("Sending mail to: {}", data[0]); + Ok((email_address, server, channel, context)) => { + println!("Sending mail to: {}", email_address); let id = "notarealiid@mention2mail".to_owned(); - let body = format!("Subject: You were mentioned in {} on {}\n{} wrote: {}", - data[2], data[1], data[3], data[4]).into_bytes(); + + let mut body = format!("Subject: You were mentioned in {} on {}", + channel, server); + for opt in context.iter() { + match opt{ + Some([sender, msg]) => body.push_str( + &format!("{} wrote: {}", sender, msg) + ), + None => (), + } + } + let body = body.into_bytes(); + let mail = lettre::SendableEmail::new( lettre::Envelope::new( Some( @@ -95,7 +107,7 @@ fn main() { .unwrap() ), vec![ - lettre::EmailAddress::new(data[0].clone()).unwrap()] + lettre::EmailAddress::new(email_address.clone()).unwrap()] ).unwrap(), id, body diff --git a/src/servers.rs b/src/servers.rs index 47fe91d..1dd1393 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -17,6 +17,7 @@ use std::{ collections::VecDeque, + collections::HashMap, sync::mpsc::Sender, }; use irc_proto::command::{ @@ -30,6 +31,52 @@ use toml::map::{ use irc_proto::message::Message; use toml::value::Value; +/// A static implementation of a ringbuffer. +/// It holds arrays of three strings each, +/// which are meant to represent the author and message respectively. +struct MessageRingBuf { + buf: [Option<[String; 2]>; SIZE], + ptr: usize, +} + +impl MessageRingBuf { + pub fn new() -> Self { + Self{ + buf: [None; SIZE], + ptr: 0 + } + } + + /// Adds a message to the end of the buffer, + /// overwriting the first value if the buffer is full. + pub fn push(&mut self, val: [String; 2]) { + self.ptr = (self.ptr + 1) % SIZE; + self.buf[self.ptr] = Some(val); + } + + /// Returns the last element of the buffer, + /// replacing it with none. + pub fn pop(&mut self) -> Option<[String; 2]> { + self.ptr = (self.ptr + SIZE - 1) % SIZE; + self.buf[self.ptr].take() + } + + /// Returns the contained buffer in the correct order. + pub fn get_queue(& self) -> [Option<[String; 2]>; SIZE] { + let mut queue = self.buf.clone(); + + for i in self.ptr .. SIZE { + queue[i - self.ptr] = self.buf[i].clone(); + } + + for i in 0 .. self.ptr { + queue[SIZE - self.ptr + i] = self.buf[i].clone(); + } + + return queue + } +} + /// Constructs a VecDeque with the messages /// required to identify with an IRC server. /// Uses the credentials set in -`config.toml`. @@ -82,30 +129,32 @@ fn get_irc_identify_messages (config: &Value) /// Appends a given VecDeque to include the messages /// used to join the channels defined in `config.toml`. -fn get_irc_join_messages(config: &Value, queue: VecDeque) +fn get_irc_join_messages(channels: &Vec, queue: VecDeque) -> Result, Box> { let mut queue = queue; - match config.get("channels") { - Some(c) => { - for channel in c.as_array().ok_or("Could not parse channels.")? { - queue.push_back(Message::from( - Command::JOIN( - String::from(channel.as_str().ok_or("Could not parse one of the channels")?), - None, - None - ) - )) - } - }, - None => () + for channel in channels { + queue.push_back(Message::from( + Command::JOIN( + String::from(channel.as_str().ok_or("Could not parse one of the channels")?), + None, + None + ) + )) } Ok(queue) } -fn handle_message(message: Message, nick: &String, client_names: Keys) - -> (Option, Option<[String; 4]>) { +/// Checks messages for mentions of users or commands in private messages. +/// Returns either one of: +/// - None and None in the case no mention or command is recognized +/// - The appropriate answer as Message and None for a command +/// - None and the mentioned user and channel as String and the context as a MessageRingBuf for mentions. +fn handle_message (message: Message, nick: &String, + client_names: Keys, contexts: &mut HashMap>) + -> (Option, Option<(String, String, [Option<[String; 2]>; SIZE])>) { + let sender = match message.clone().source_nickname() { Some(s) => String::from(s), None => String::from("anonymous") @@ -132,14 +181,23 @@ fn handle_message(message: Message, nick: &String, client_names: Keys) ), None ) - } - for c_name in client_names { - if msg.contains(c_name) { - return (None, Some([c_name.clone(), - rec.clone(), - sender.clone(), - msg.clone() - ])) + } else { + match contexts.get_mut(&rec.clone()) { + Some(ref buf) => { + buf.push([sender.clone(), msg.clone()]); + for c_name in client_names { + if msg.contains(c_name) { + return (None, Some(( + c_name.clone(), + rec.clone(), + buf.get_queue(), + ))) + } + } + } + None => { + println!("Channel not recognized: {}", rec.clone()); + } } } }, @@ -148,18 +206,36 @@ fn handle_message(message: Message, nick: &String, client_names: Keys) return (None, None) } -pub fn handle_server(config: Value, tx: Sender<[String; 5]>) +/// Connects to a server and indefinitely iterates the messages received. +/// Calls handle_message on each one and pushes +/// email, server, channel as Strings +/// and the context as MessageRingBuf to the given stream. +pub fn handle_server + (config: Value, tx: Sender<(String, String, String, [Option<[String; 2]>; SIZE])>) -> Result<(), Box> { + let server_name = config.get("server").unwrap().as_str() .ok_or("Could not get server adress from config")?; let nick = config.get("nickname").unwrap().as_str() .ok_or("Could not get nickname from config")?.to_owned(); - + let channels = config.get("channels").unwrap().as_array() + .ok_or("Could not get channels from config")?; + let clients; match config.get("clients").unwrap().as_table() { Some(t) => clients = t.clone(), None => clients = Map::new(), } + + let mut chat_contexts = HashMap::new(); + for channel in channels { + chat_contexts.insert( + channel.as_str() + .ok_or("Could not parse one of the channels")? + .to_owned(), + MessageRingBuf::new(), + ); + } let mut stream = super::connect::connect_irc(&config).unwrap(); let mut message_queue = get_irc_identify_messages(&config).unwrap(); @@ -168,7 +244,7 @@ pub fn handle_server(config: Value, tx: Sender<[String; 5]>) stream.write(message).unwrap(); } - message_queue = get_irc_join_messages(&config, message_queue).unwrap(); + message_queue = get_irc_join_messages(&channels, message_queue).unwrap(); // Wait for first ping and join channels after sending pong. loop { @@ -190,24 +266,28 @@ pub fn handle_server(config: Value, tx: Sender<[String; 5]>) // Handle all incoming messages after joining the channels. loop { let message = stream.read()?; - let (answer, data) = handle_message(message, &nick, clients.keys()); + let (answer, data) = handle_message( + message, + &nick, + clients.keys(), + &mut chat_contexts + ); + match answer { Some(a) => stream.write(a).unwrap(), None => () } match data { - Some(d) => match clients.get(&d[0]) { + Some((client, channel, context)) => match clients.get(&client) { Some(addr) => { - // There must be a better way to do this… - let out = [ + let out = ( addr.as_str() .ok_or("Could not parse email address.")? .to_owned(), server_name.to_owned(), - d[1].clone(), - d[2].clone(), - d[3].clone(), - ]; + channel.clone(), + context.clone(), + ); tx.send(out)? }, None => (), diff --git a/ßqa b/ßqa new file mode 100644 index 0000000..56be040 --- /dev/null +++ b/ßqa @@ -0,0 +1,263 @@ +/* Copyright 2021 Daniel Mowitz + * This file is part of Mention2Mail. + * + * Mention2Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mention2Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mention2Mail. If not, see . + */ + +use std::{ + collections::VecDeque, + collections::HashMap, + sync::mpsc::Sender, +}; +use irc_proto::command::{ + CapSubCommand, + Command, +}; +use toml::map::{ + Map, + Keys +}; +use irc_proto::message::Message; +use toml::value::Value; + +struct MessageRingBuf { + buf: [Option<[String; 3]>; SIZE], + ptr: usize, +} + +impl MessageRingBuf { + pub fn new() -> Self { + Self{ + buf: [None; SIZE], + ptr: 0 + } + } + + pub fn push(&mut self, val: [String; 3]) { + self.ptr = (self.ptr + 1) % SIZE; + self.buf[self.ptr] = Some(val); + } + + pub fn pop(&mut self) -> Option<[String; 3]> { + self.buf[self.ptr].take() + } + + pub fn get_queue(& self) -> [Option; SIZE] { + let mut queue = self.buf.clone(); + + for i in self.ptr .. SIZE { + queue[i - self.ptr] = self.buf[i]; + } + + for i in 0 .. self.ptr { + queue[SIZE - self.ptr + i] = self.buf[i]; + } + + return queue + } +} + +/// Constructs a VecDeque with the messages +/// required to identify with an IRC server. +/// Uses the credentials set in -`config.toml`. +fn get_irc_identify_messages (config: &Value) + -> Result, Box> { + let mut queue = VecDeque::new(); + + queue.push_back(Message::from( + Command::CAP( + None, + CapSubCommand::END, + None, + None + ))); + + match config.get("password") { + Some(p) => queue.push_back(Message::from( + Command::PASS( + String::from(p.as_str().ok_or("Could not parse password.")?) + ) + )), + None => () + } + + let nick: String; + match config.get("nickname") { + Some(n) => { + nick = String::from(n.as_str().ok_or("Could not parse nickname.")?); + queue.push_back(Message::from(Command::NICK(nick.clone()))); + }, + None => return Err("No nickname supplied!".into()), + } + + match config.get("username") { + Some(u) => queue.push_back(Message::from( + Command::USER( + String::from(u.as_str().ok_or("Could not parse username.")?), + "0".to_owned(), + String::from(u.as_str().ok_or("Could not parse username.")?) + ) + )), + None => queue.push_back(Message::from( + // nick.clone() is only used once because the value + // can be moved the second time + Command::USER(nick.clone(), "0".to_owned(), nick) + )) + } + Ok(queue) +} + +/// Appends a given VecDeque to include the messages +/// used to join the channels defined in `config.toml`. +fn get_irc_join_messages(config: &Value, queue: VecDeque) + -> Result, Box> { + let mut queue = queue; + + match config.get("channels") { + Some(c) => { + for channel in c.as_array().ok_or("Could not parse channels.")? { + queue.push_back(Message::from( + Command::JOIN( + String::from(channel.as_str().ok_or("Could not parse one of the channels")?), + None, + None + ) + )) + } + }, + None => () + } + + Ok(queue) +} + +fn handle_message(message: Message, nick: &String, + client_names: Keys, contexts: HashMap>) + -> (Option, Option<[String; 4]>) { + let sender = match message.clone().source_nickname() { + Some(s) => String::from(s), + None => String::from("anonymous") + }; + match message.clone().command { + Command::PING(ref data, _) => { + return ( + Some(Message::from( + Command::PONG(data.to_owned(), None) + )), + None + ); + }, + Command::PRIVMSG(ref rec, ref msg) => { + // Send bot info on receiving a private message. + if rec.as_str() == nick.as_str() { + return ( + Some(Message::from( + Command::PRIVMSG( + sender.clone(), + super::texts::get_bot_info(), + ) + ) + ), + None + ) + } else { + match contexts.get_mut(sender.clone()) { + Some(ref buf) => buf.push( + for c_name in client_names { + if msg.contains(c_name) { + return (None, Some([c_name.clone(), + rec.clone(), + sender.clone(), + msg.clone() + ])) + } + } + } + }, + _ => println!("{}", message.clone().to_string()) + } + return (None, None) +} + +pub fn handle_server(config: Value, tx: Sender<[String; 5]>) + -> Result<(), Box> { + let server_name = config.get("server").unwrap().as_str() + .ok_or("Could not get server adress from config")?; + let nick = config.get("nickname").unwrap().as_str() + .ok_or("Could not get nickname from config")?.to_owned(); + + let clients; + match config.get("clients").unwrap().as_table() { + Some(t) => clients = t.clone(), + None => clients = Map::new(), + } + + let mut chat_context = HashMap::new(); + + let mut stream = super::connect::connect_irc(&config).unwrap(); + let mut message_queue = get_irc_identify_messages(&config).unwrap(); + + while let Some(message) = message_queue.pop_front() { + stream.write(message).unwrap(); + } + + message_queue = get_irc_join_messages(&config, message_queue).unwrap(); + + // Wait for first ping and join channels after sending pong. + loop { + let message = stream.read()?; + match message.command { + Command::PING(ref data, _) => { + stream.write(Message::from( + Command::PONG(data.to_owned(), None) + )).unwrap(); + while let Some(message) = message_queue.pop_front() { + stream.write(message).unwrap(); + } + break; + } + _ => () + } + } + + // Handle all incoming messages after joining the channels. + loop { + let message = stream.read()?; + let (answer, data) = handle_message(message, &nick, clients.keys()); + match answer { + Some(a) => stream.write(a).unwrap(), + None => () + } + match data { + Some(d) => match clients.get(&d[0]) { + Some(addr) => { + // There must be a better way to do this… + let out = [ + addr.as_str() + .ok_or("Could not parse email address.")? + .to_owned(), + server_name.to_owned(), + d[1].clone(), + d[2].clone(), + d[3].clone(), + ]; + tx.send(out)? + }, + None => (), + }, + None => (), + } + } +} + From a1e1a0df5f36877bc29f10a1d4d107eccb4d89b2 Mon Sep 17 00:00:00 2001 From: Thelie Date: Wed, 9 Jun 2021 18:45:44 +0200 Subject: [PATCH 17/21] Changed email transport from sendmail to smtp. --- src/main.rs | 48 ++++++++++++++++++++++++++++++++++++------------ src/servers.rs | 43 +++++++++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/main.rs b/src/main.rs index 22e1871..54bdb2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,8 +27,12 @@ use std::{ thread, }; use lettre::{ - SendmailTransport, - Transport + SmtpClient, + smtp::authentication::{ + Credentials, + Mechanism, + }, + Transport, }; fn main() { @@ -57,17 +61,19 @@ fn main() { None => config = config::get_main_config(PathBuf::from("/etc/Mention2Mail/config.toml")) .expect("Could not get default config in /etc/Mention2Mail/default.toml"), } + + let mail_config = &config.get("email").expect("No email config found."); let server_configs; match server_conf_path { - Some(p) => server_configs = config::get_server_configs(config, PathBuf::from(p)) + Some(p) => server_configs = config::get_server_configs(config.clone(), PathBuf::from(p)) .expect("Could not get server config."), - None => server_configs = vec![config], + None => server_configs = vec![config.clone()], } // TODO: This line has many problems… - let (tx,rx): (std::sync::mpsc::Sender<(String, String, String, [Option<[String; 2]>; 5])>, std::sync::mpsc::Receiver<(String, String, String, [Option<[String; 2]>; 5])>)= channel(); + let (tx,rx): (std::sync::mpsc::Sender<(String, String, String, Vec>)>, std::sync::mpsc::Receiver<(String, String, String, Vec>)>)= channel(); let mut server_threads = vec![]; for s_conf in server_configs { @@ -75,12 +81,30 @@ fn main() { server_threads.push(thread::Builder::new() .name("server name here".to_string()) .spawn(move || { - servers::handle_server(s_conf, t).unwrap(); + servers::handle_server(s_conf, t, 5).unwrap(); }) ); } - - let mut mailer = SendmailTransport::new(); + + let smtp_address = mail_config.get("address") + .expect("No SMTP server address found.") + .as_str() + .unwrap(); + let smpt_user = mail_config.get("user") + .expect("No SMTP user name found.") + .as_str() + .unwrap(); + let smtp_pass = mail_config.get("pass") + .expect("No SMTP password found.") + .as_str() + .unwrap(); + + let mail_client = SmtpClient::new_simple(smtp_address).unwrap(); + let mail_client = mail_client.credentials( + Credentials::new(smpt_user.to_owned(), smtp_pass.to_owned()) + ); + let mail_client = mail_client.authentication_mechanism(Mechanism::Plain); + let mut mailer = mail_client.transport(); loop { match rx.recv() { @@ -92,9 +116,9 @@ fn main() { channel, server); for opt in context.iter() { match opt{ - Some([sender, msg]) => body.push_str( - &format!("{} wrote: {}", sender, msg) - ), + Some([sender, msg]) => { + body.push_str(&format!("{} wrote: {}\n", sender, msg)) + }, None => (), } } @@ -115,7 +139,7 @@ fn main() { match mailer.send(mail) { Ok(_) => println!("Email sent successfully!"), - Err(e) => panic!("Could not send email: {:?}", e), + Err(e) => println!("Could not send email: {:?}", e), } } Err(_e) => (), diff --git a/src/servers.rs b/src/servers.rs index 1dd1393..cdc86f0 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -34,43 +34,46 @@ use toml::value::Value; /// A static implementation of a ringbuffer. /// It holds arrays of three strings each, /// which are meant to represent the author and message respectively. -struct MessageRingBuf { - buf: [Option<[String; 2]>; SIZE], +pub struct MessageRingBuf { + buf: Vec>, ptr: usize, } -impl MessageRingBuf { - pub fn new() -> Self { +impl MessageRingBuf { + pub fn new(size: usize) -> Self { + Self{ - buf: [None; SIZE], + buf: vec!(None; size), ptr: 0 } } /// Adds a message to the end of the buffer, /// overwriting the first value if the buffer is full. - pub fn push(&mut self, val: [String; 2]) { - self.ptr = (self.ptr + 1) % SIZE; + pub fn push(&mut self, val: T) { + self.ptr = (self.ptr + 1) % self.buf.len(); self.buf[self.ptr] = Some(val); } /// Returns the last element of the buffer, /// replacing it with none. - pub fn pop(&mut self) -> Option<[String; 2]> { - self.ptr = (self.ptr + SIZE - 1) % SIZE; - self.buf[self.ptr].take() + pub fn pop(&mut self) -> Option { + let l = self.buf.len(); + self.ptr = (self.ptr + l - 1) % l; + self.buf[self.ptr].take() } /// Returns the contained buffer in the correct order. - pub fn get_queue(& self) -> [Option<[String; 2]>; SIZE] { + pub fn get_queue(& self) -> Vec> { + let l = self.buf.len(); let mut queue = self.buf.clone(); - for i in self.ptr .. SIZE { + for i in self.ptr .. l { queue[i - self.ptr] = self.buf[i].clone(); } for i in 0 .. self.ptr { - queue[SIZE - self.ptr + i] = self.buf[i].clone(); + queue[l - self.ptr + i] = self.buf[i].clone(); } return queue @@ -151,9 +154,9 @@ fn get_irc_join_messages(channels: &Vec, queue: VecDeque) /// - None and None in the case no mention or command is recognized /// - The appropriate answer as Message and None for a command /// - None and the mentioned user and channel as String and the context as a MessageRingBuf for mentions. -fn handle_message (message: Message, nick: &String, - client_names: Keys, contexts: &mut HashMap>) - -> (Option, Option<(String, String, [Option<[String; 2]>; SIZE])>) { +fn handle_message (message: Message, nick: &String, + client_names: Keys, contexts: &mut HashMap>) + -> (Option, Option<(String, String, Vec>)>) { let sender = match message.clone().source_nickname() { Some(s) => String::from(s), @@ -183,7 +186,7 @@ fn handle_message (message: Message, nick: &String, ) } else { match contexts.get_mut(&rec.clone()) { - Some(ref buf) => { + Some(ref mut buf) => { buf.push([sender.clone(), msg.clone()]); for c_name in client_names { if msg.contains(c_name) { @@ -210,8 +213,8 @@ fn handle_message (message: Message, nick: &String, /// Calls handle_message on each one and pushes /// email, server, channel as Strings /// and the context as MessageRingBuf to the given stream. -pub fn handle_server - (config: Value, tx: Sender<(String, String, String, [Option<[String; 2]>; SIZE])>) +pub fn handle_server + (config: Value, tx: Sender<(String, String, String, Vec>)>,context_size: usize) -> Result<(), Box> { let server_name = config.get("server").unwrap().as_str() @@ -233,7 +236,7 @@ pub fn handle_server channel.as_str() .ok_or("Could not parse one of the channels")? .to_owned(), - MessageRingBuf::new(), + MessageRingBuf::new(context_size), ); } From 86b98d33ba9095b7993f16aaef3cb05570cd64c0 Mon Sep 17 00:00:00 2001 From: Thelie Date: Wed, 9 Jun 2021 21:53:30 +0200 Subject: [PATCH 18/21] Create the emails the right way. --- Cargo.lock | 270 +++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/main.rs | 29 ++---- src/servers.rs | 8 +- ßa | 15 +++ 5 files changed, 292 insertions(+), 31 deletions(-) create mode 100644 ßa diff --git a/Cargo.lock b/Cargo.lock index 3699c22..7a40914 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,28 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + [[package]] name = "base64" version = "0.10.1" @@ -59,6 +75,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -75,6 +113,21 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "email" +version = "0.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4" +dependencies = [ + "base64 0.9.3", + "chrono", + "encoding", + "lazy_static", + "rand 0.4.6", + "time", + "version_check", +] + [[package]] name = "encoding" version = "0.2.33" @@ -163,6 +216,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures" version = "0.3.14" @@ -315,7 +374,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86ed8677138975b573ab4949c35613931a4addeadd0a8a6aa0327e2a979660de" dependencies = [ - "base64", + "base64 0.10.1", "bufstream", "fast_chemail", "hostname", @@ -327,6 +386,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "lettre_email" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5" +dependencies = [ + "base64 0.10.1", + "email", + "lettre", + "mime", + "time", + "uuid", +] + [[package]] name = "libc" version = "0.2.93" @@ -355,10 +428,17 @@ dependencies = [ "irc-proto", "irc_stream", "lettre", + "lettre_email", "native-tls", "toml", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "native-tls" version = "0.2.7" @@ -387,6 +467,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg 1.0.1", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg 1.0.1", +] + [[package]] name = "once_cell" version = "1.7.2" @@ -419,7 +518,7 @@ version = "0.9.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" dependencies = [ - "autocfg", + "autocfg 1.0.1", "cc", "libc", "pkg-config", @@ -480,6 +579,38 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + [[package]] name = "rand" version = "0.8.3" @@ -487,9 +618,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", ] [[package]] @@ -499,9 +640,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.2", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.2" @@ -511,13 +667,84 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "rand_hc" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ - "rand_core", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", ] [[package]] @@ -544,6 +771,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "schannel" version = "0.1.19" @@ -630,7 +863,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", - "rand", + "rand 0.8.3", "redox_syscall", "remove_dir_all", "winapi", @@ -656,13 +889,23 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tokio" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ - "autocfg", + "autocfg 1.0.1", "pin-project-lite", ] @@ -695,6 +938,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "uuid" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +dependencies = [ + "rand 0.6.5", +] + [[package]] name = "vcpkg" version = "0.2.12" diff --git a/Cargo.toml b/Cargo.toml index 74a8f5d..3ffb5b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" irc-proto = "0.15" irc_stream = { path = "irc-stream", version = "0.1" } lettre = "0.9" +lettre_email = "0.9" native-tls = "0.2" toml = "0.5" diff --git a/src/main.rs b/src/main.rs index 54bdb2d..6b155d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ use lettre::{ }, Transport, }; +use lettre_email::EmailBuilder; fn main() { let mut server_conf_flag = false; @@ -110,10 +111,8 @@ fn main() { match rx.recv() { Ok((email_address, server, channel, context)) => { println!("Sending mail to: {}", email_address); - let id = "notarealiid@mention2mail".to_owned(); - let mut body = format!("Subject: You were mentioned in {} on {}", - channel, server); + let mut body = "".to_owned(); for opt in context.iter() { match opt{ Some([sender, msg]) => { @@ -122,22 +121,16 @@ fn main() { None => (), } } - let body = body.into_bytes(); - let mail = lettre::SendableEmail::new( - lettre::Envelope::new( - Some( - lettre::EmailAddress::new("m2m@chaostreff-alzey.de".to_owned()) - .unwrap() - ), - vec![ - lettre::EmailAddress::new(email_address.clone()).unwrap()] - ).unwrap(), - id, - body - ); - - match mailer.send(mail) { + let mail = EmailBuilder::new() + .to((email_address.clone(), "Someone")) + .from("m2m@chaostreff-alzey.de") + .subject(format!("You were mentioned in {} on {}", channel, server)) + .text(body) + .build() + .unwrap(); + + match mailer.send(mail.into()) { Ok(_) => println!("Email sent successfully!"), Err(e) => println!("Could not send email: {:?}", e), } diff --git a/src/servers.rs b/src/servers.rs index cdc86f0..b5f31a6 100644 --- a/src/servers.rs +++ b/src/servers.rs @@ -68,12 +68,12 @@ impl MessageRingBuf { let l = self.buf.len(); let mut queue = self.buf.clone(); - for i in self.ptr .. l { - queue[i - self.ptr] = self.buf[i].clone(); + for i in self.ptr + 1 .. l { + queue[i - self.ptr - 1] = self.buf[i].clone(); } - for i in 0 .. self.ptr { - queue[l - self.ptr + i] = self.buf[i].clone(); + for i in 0 .. self.ptr + 1{ + queue[l - self.ptr - 1 + i] = self.buf[i].clone(); } return queue diff --git a/ßa b/ßa new file mode 100644 index 0000000..1ccebba --- /dev/null +++ b/ßa @@ -0,0 +1,15 @@ +" Press ? for help + +.. (up a dir) + Date: Wed, 9 Jun 2021 21:54:51 +0200 Subject: [PATCH 19/21] =?UTF-8?q?Delete=20'=C3=9Fa'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ßa | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 ßa diff --git a/ßa b/ßa deleted file mode 100644 index 1ccebba..0000000 --- a/ßa +++ /dev/null @@ -1,15 +0,0 @@ -" Press ? for help - -.. (up a dir) - Date: Wed, 9 Jun 2021 21:54:56 +0200 Subject: [PATCH 20/21] =?UTF-8?q?Delete=20'=C3=9Fqa'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ßqa | 263 ------------------------------------------------------------ 1 file changed, 263 deletions(-) delete mode 100644 ßqa diff --git a/ßqa b/ßqa deleted file mode 100644 index 56be040..0000000 --- a/ßqa +++ /dev/null @@ -1,263 +0,0 @@ -/* Copyright 2021 Daniel Mowitz - * This file is part of Mention2Mail. - * - * Mention2Mail is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mention2Mail is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mention2Mail. If not, see . - */ - -use std::{ - collections::VecDeque, - collections::HashMap, - sync::mpsc::Sender, -}; -use irc_proto::command::{ - CapSubCommand, - Command, -}; -use toml::map::{ - Map, - Keys -}; -use irc_proto::message::Message; -use toml::value::Value; - -struct MessageRingBuf { - buf: [Option<[String; 3]>; SIZE], - ptr: usize, -} - -impl MessageRingBuf { - pub fn new() -> Self { - Self{ - buf: [None; SIZE], - ptr: 0 - } - } - - pub fn push(&mut self, val: [String; 3]) { - self.ptr = (self.ptr + 1) % SIZE; - self.buf[self.ptr] = Some(val); - } - - pub fn pop(&mut self) -> Option<[String; 3]> { - self.buf[self.ptr].take() - } - - pub fn get_queue(& self) -> [Option; SIZE] { - let mut queue = self.buf.clone(); - - for i in self.ptr .. SIZE { - queue[i - self.ptr] = self.buf[i]; - } - - for i in 0 .. self.ptr { - queue[SIZE - self.ptr + i] = self.buf[i]; - } - - return queue - } -} - -/// Constructs a VecDeque with the messages -/// required to identify with an IRC server. -/// Uses the credentials set in -`config.toml`. -fn get_irc_identify_messages (config: &Value) - -> Result, Box> { - let mut queue = VecDeque::new(); - - queue.push_back(Message::from( - Command::CAP( - None, - CapSubCommand::END, - None, - None - ))); - - match config.get("password") { - Some(p) => queue.push_back(Message::from( - Command::PASS( - String::from(p.as_str().ok_or("Could not parse password.")?) - ) - )), - None => () - } - - let nick: String; - match config.get("nickname") { - Some(n) => { - nick = String::from(n.as_str().ok_or("Could not parse nickname.")?); - queue.push_back(Message::from(Command::NICK(nick.clone()))); - }, - None => return Err("No nickname supplied!".into()), - } - - match config.get("username") { - Some(u) => queue.push_back(Message::from( - Command::USER( - String::from(u.as_str().ok_or("Could not parse username.")?), - "0".to_owned(), - String::from(u.as_str().ok_or("Could not parse username.")?) - ) - )), - None => queue.push_back(Message::from( - // nick.clone() is only used once because the value - // can be moved the second time - Command::USER(nick.clone(), "0".to_owned(), nick) - )) - } - Ok(queue) -} - -/// Appends a given VecDeque to include the messages -/// used to join the channels defined in `config.toml`. -fn get_irc_join_messages(config: &Value, queue: VecDeque) - -> Result, Box> { - let mut queue = queue; - - match config.get("channels") { - Some(c) => { - for channel in c.as_array().ok_or("Could not parse channels.")? { - queue.push_back(Message::from( - Command::JOIN( - String::from(channel.as_str().ok_or("Could not parse one of the channels")?), - None, - None - ) - )) - } - }, - None => () - } - - Ok(queue) -} - -fn handle_message(message: Message, nick: &String, - client_names: Keys, contexts: HashMap>) - -> (Option, Option<[String; 4]>) { - let sender = match message.clone().source_nickname() { - Some(s) => String::from(s), - None => String::from("anonymous") - }; - match message.clone().command { - Command::PING(ref data, _) => { - return ( - Some(Message::from( - Command::PONG(data.to_owned(), None) - )), - None - ); - }, - Command::PRIVMSG(ref rec, ref msg) => { - // Send bot info on receiving a private message. - if rec.as_str() == nick.as_str() { - return ( - Some(Message::from( - Command::PRIVMSG( - sender.clone(), - super::texts::get_bot_info(), - ) - ) - ), - None - ) - } else { - match contexts.get_mut(sender.clone()) { - Some(ref buf) => buf.push( - for c_name in client_names { - if msg.contains(c_name) { - return (None, Some([c_name.clone(), - rec.clone(), - sender.clone(), - msg.clone() - ])) - } - } - } - }, - _ => println!("{}", message.clone().to_string()) - } - return (None, None) -} - -pub fn handle_server(config: Value, tx: Sender<[String; 5]>) - -> Result<(), Box> { - let server_name = config.get("server").unwrap().as_str() - .ok_or("Could not get server adress from config")?; - let nick = config.get("nickname").unwrap().as_str() - .ok_or("Could not get nickname from config")?.to_owned(); - - let clients; - match config.get("clients").unwrap().as_table() { - Some(t) => clients = t.clone(), - None => clients = Map::new(), - } - - let mut chat_context = HashMap::new(); - - let mut stream = super::connect::connect_irc(&config).unwrap(); - let mut message_queue = get_irc_identify_messages(&config).unwrap(); - - while let Some(message) = message_queue.pop_front() { - stream.write(message).unwrap(); - } - - message_queue = get_irc_join_messages(&config, message_queue).unwrap(); - - // Wait for first ping and join channels after sending pong. - loop { - let message = stream.read()?; - match message.command { - Command::PING(ref data, _) => { - stream.write(Message::from( - Command::PONG(data.to_owned(), None) - )).unwrap(); - while let Some(message) = message_queue.pop_front() { - stream.write(message).unwrap(); - } - break; - } - _ => () - } - } - - // Handle all incoming messages after joining the channels. - loop { - let message = stream.read()?; - let (answer, data) = handle_message(message, &nick, clients.keys()); - match answer { - Some(a) => stream.write(a).unwrap(), - None => () - } - match data { - Some(d) => match clients.get(&d[0]) { - Some(addr) => { - // There must be a better way to do this… - let out = [ - addr.as_str() - .ok_or("Could not parse email address.")? - .to_owned(), - server_name.to_owned(), - d[1].clone(), - d[2].clone(), - d[3].clone(), - ]; - tx.send(out)? - }, - None => (), - }, - None => (), - } - } -} - From 3c8437891060543f995dd3c7d42a3b4eba55c0a0 Mon Sep 17 00:00:00 2001 From: Thelie Date: Wed, 9 Jun 2021 23:13:55 +0200 Subject: [PATCH 21/21] Whatever this is --- src/config.rs | 8 ++------ src/main.rs | 4 +++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index 187fb9f..a5382a6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -66,12 +66,8 @@ fn parse_server_dir(mut config: Value, server_config_path: PathBuf) config = merge_config_maps(config, client_config)?; }, "channels.toml" => { - let channels = Value::from(parse_toml_to_table(entry.path())?); - if config.contains_key(&"channels".to_owned()) { - config[&"channels".to_owned()] = channels; - } else { - config.insert("channels".to_owned(), channels); - } + let channel_config = parse_toml_to_table(entry.path())?; + config = merge_config_maps(config, channel_config)?; }, _ => (), } diff --git a/src/main.rs b/src/main.rs index 6b155d2..b757f6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,8 +79,10 @@ fn main() { for s_conf in server_configs { let t = tx.clone(); + let addr = s_conf.get("server").unwrap().as_str() + .ok_or("Could not get server adress from config").unwrap(); server_threads.push(thread::Builder::new() - .name("server name here".to_string()) + .name(addr.to_string()) .spawn(move || { servers::handle_server(s_conf, t, 5).unwrap(); })