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