/* 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 => (), } } }