201 lines
5.2 KiB
Rust
201 lines
5.2 KiB
Rust
/* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
pub mod connect;
|
|
|
|
use std::{
|
|
format,
|
|
env::args,
|
|
path::Path,
|
|
io::Read,
|
|
net::TcpStream,
|
|
collections::VecDeque,
|
|
thread,
|
|
time,
|
|
sync::mpsc::channel,
|
|
};
|
|
use irc_proto::command::{
|
|
CapSubCommand,
|
|
Command,
|
|
};
|
|
use irc_proto::message::Message;
|
|
use irc_stream::{IrcStream, IrcRead, IrcWrite};
|
|
use toml::value::Value;
|
|
use native_tls::{TlsConnector,TlsStream};
|
|
|
|
trait IrcReadWrite: IrcRead + IrcWrite {}
|
|
impl<T: IrcRead + IrcWrite> IrcReadWrite for T {}
|
|
|
|
fn get_config<P: AsRef<Path>>(config_path: P)
|
|
-> Result<Value, Box<dyn std::error::Error>> {
|
|
|
|
// 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::<Value>()?;
|
|
// 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))
|
|
}
|
|
|
|
fn get_irc_identify_messages (config: &Value)
|
|
-> Result<VecDeque<Message>, Box<dyn std::error::Error>> {
|
|
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)
|
|
}
|
|
|
|
fn get_irc_join_messages(config: &Value, queue: VecDeque<Message>)
|
|
-> Result<VecDeque<Message>, Box<dyn std::error::Error>> {
|
|
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 main() {
|
|
let config_path = args().nth(1)
|
|
.expect("no config given");
|
|
let config = get_config(config_path).expect("Could not get config");
|
|
|
|
let 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();
|
|
|
|
//TODO remove this botch
|
|
let (tx, rx) = channel();
|
|
thread::spawn(move|| {
|
|
thread::sleep(time::Duration::from_secs(10));
|
|
while let Some(message) = message_queue.pop_front() {
|
|
println!("Sending: {}", message.clone().to_string());
|
|
tx.send(message).unwrap();
|
|
}
|
|
});
|
|
|
|
loop {
|
|
let message = match stream.read() {
|
|
Ok(m) => m,
|
|
Err(e) => Message::from(
|
|
Command::PRIVMSG(
|
|
String::from("Error"),
|
|
format!("{}", e)
|
|
)
|
|
)
|
|
};
|
|
let sender = match message.clone().source_nickname() {
|
|
Some(s) => String::from(s),
|
|
None => String::from("anonymous")
|
|
};
|
|
match message.clone().command {
|
|
Command::PING(ref data, _) => {
|
|
stream.write(Message::from(
|
|
Command::PONG(data.to_owned(), None)
|
|
)).unwrap();
|
|
},
|
|
Command::PRIVMSG(ref rec, ref msg) => println!(
|
|
"{} -> {}: {}",
|
|
rec,
|
|
msg,
|
|
sender
|
|
),
|
|
_ => println!("{}", message.clone().to_string())
|
|
}
|
|
match rx.try_recv() {
|
|
Ok(m) => stream.write(m).unwrap(),
|
|
Err(_e) => ()
|
|
}
|
|
}
|
|
}
|
|
|