diff --git a/src/irc_handler.rs b/src/irc_handler.rs index 125c973..d23902b 100644 --- a/src/irc_handler.rs +++ b/src/irc_handler.rs @@ -1,12 +1,15 @@ use color_eyre::eyre::Result; use futures::StreamExt; +use irc::proto::Capability; use tokio::{ select, sync::mpsc::{UnboundedReceiver, UnboundedSender}, }; pub async fn connect() -> Result { - Ok(irc::client::Client::new("config.toml").await?) + let client = irc::client::Client::new("config.toml").await?; + client.send_cap_req(&[Capability::EchoMessage, Capability::ServerTime])?; + Ok(client) } pub async fn message_loop( @@ -21,13 +24,13 @@ pub async fn message_loop( select! { val = irc_stream.next() => { if let Some(message) = val.transpose()? { - print!("[Rx] {message}"); + println!("[Rx] {} {:?}", message.to_string().trim(), message.tags); message_tx.send(message)?; } } val = input_rx.recv() => { let message = val.unwrap(); - print!("[Tx] {message}"); + println!("[Tx] {} {:?}", message.to_string().trim(), message.tags); client.send(message)?; } } diff --git a/src/main.rs b/src/main.rs index 0177c49..576780a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use iced::{ widget::{column, container, mouse_area, row, text, text_input}, Application, Background, Color, Element, Length, Settings, }; -use irc::proto::Command as IrcCommand; +use irc::proto::{Command as IrcCommand, Response}; use once_cell::sync::Lazy; use std::cell::RefCell; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -56,6 +56,7 @@ struct Cri { input_tx: RefCell>, message_log: MessageLog, input_value: String, + nickname: String, } impl Cri { @@ -63,15 +64,6 @@ impl Cri { if let Some(active_channel) = &self.message_log.active_channel { let command = IrcCommand::PRIVMSG(active_channel.to_string(), input_value.to_string()); let message: irc::proto::Message = command.into(); - - self.message_log - .get_mut(self.message_log.active_channel.clone()) - .messages - .push(IrcMessage::Privmsg { - nickname: String::from("cri"), - message: input_value.to_string(), - }); - self.input_tx.borrow().send(message.clone()).unwrap(); } } @@ -81,66 +73,69 @@ impl Cri { let command = tokens.next().unwrap(); match command { - "/join" => { - let channel = tokens - .next() - .map(str::to_string) - .or_else(|| self.message_log.active_channel.clone()); - if channel.is_none() { - // TODO error message - return; - } - - let channel = channel.unwrap(); - if !channel.starts_with('#') { - // TODO error message - return; - } - - self.input_tx - .borrow() - .send( - IrcCommand::JOIN(channel.clone(), tokens.next().map(str::to_string), None) - .into(), - ) - .unwrap(); - - self.message_log.set_active(Some(channel)); - } - "/part" => { - let channel = tokens - .next() - .map(str::to_string) - .or_else(|| self.message_log.active_channel.clone()); - if channel.is_none() { - // TODO error message - return; - } - - let channel = channel.unwrap(); - if !channel.starts_with('#') { - // TODO error message - return; - } - - let reason = tokens.collect::>().join(" "); - let reason = if reason.is_empty() { - None - } else { - Some(reason) - }; - - self.input_tx - .borrow() - .send(IrcCommand::PART(channel, reason).into()) - .unwrap(); - } - "/query" => self - .message_log - .set_active(Some(tokens.next().unwrap().to_string())), + "/join" => self.handle_join_command(&mut tokens), + "/part" => self.handle_part_command(&mut tokens), + "/query" => self.handle_query_command(tokens), _ => todo!(), } } + + fn handle_part_command(&mut self, tokens: &mut std::str::SplitWhitespace<'_>) { + let channel = tokens + .next() + .map(str::to_string) + .or_else(|| self.message_log.active_channel.clone()); + if channel.is_none() { + // TODO error message + return; + } + + let channel = channel.unwrap(); + if !channel.starts_with('#') { + // TODO error message + return; + } + + let reason = tokens.collect::>().join(" "); + let reason = if reason.is_empty() { + None + } else { + Some(reason) + }; + + self.input_tx + .borrow() + .send(IrcCommand::PART(channel, reason).into()) + .unwrap(); + } + + fn handle_join_command(&mut self, tokens: &mut std::str::SplitWhitespace<'_>) { + let channel = tokens + .next() + .map(str::to_string) + .or_else(|| self.message_log.active_channel.clone()); + if channel.is_none() { + // TODO error message + return; + } + + let channel = channel.unwrap(); + if !channel.starts_with('#') { + // TODO error message + return; + } + + self.input_tx + .borrow() + .send(IrcCommand::JOIN(channel.clone(), tokens.next().map(str::to_string), None).into()) + .unwrap(); + self.message_log.set_active(Some(channel)); + } + + fn handle_query_command(&mut self, mut tokens: std::str::SplitWhitespace<'_>) { + self.message_log + .set_active(Some(tokens.next().unwrap().to_string())); + } } impl Application for Cri { @@ -156,6 +151,7 @@ impl Application for Cri { input_tx: RefCell::new(flags.input_tx), message_log: MessageLog::new(), input_value: String::new(), + nickname: "cri".to_string(), // TODO take default value from config }, iced::Command::none(), ) @@ -169,7 +165,10 @@ impl Application for Cri { match message { UiMessage::IrcMessageReceived(message) => { // TODO use actual nickname - let source_nickname = message.source_nickname().unwrap_or("cri").to_string(); + let source_nickname = message + .source_nickname() + .unwrap_or(&self.nickname) + .to_string(); match &message.command { IrcCommand::JOIN(chanlist, _, _) => { @@ -190,12 +189,19 @@ impl Application for Cri { .on_quit(&source_nickname, comment.as_deref()); } - IrcCommand::PRIVMSG(msgtarget, content) | IrcCommand::NOTICE(msgtarget, content) => { + IrcCommand::PRIVMSG(msgtarget, content) + | IrcCommand::NOTICE(msgtarget, content) => { let channel = message.response_target().unwrap_or(msgtarget).to_string(); - self.message_log - .on_privmsg(&channel, &source_nickname, content); + self.message_log.on_privmsg( + &self.nickname, + &channel, + &source_nickname, + content, + ); + } + IrcCommand::Response(Response::RPL_WELCOME, args) => { + self.nickname = args[0].clone() } - _ => self.message_log.on_other(&message.to_string()), } } @@ -232,7 +238,7 @@ impl Application for Cri { let light_blue = Color::new(0.26, 0.62, 0.85, 1.0); let light_red = Color::new(0.99, 0.36, 0.40, 1.0); - let log = self.message_log.view(&self.message_log.active_channel); + let log = self.message_log.view(&self.nickname); let message_box = text_input("your magnum opus", &self.input_value) .id(INPUT_ID.clone()) diff --git a/src/message_log.rs b/src/message_log.rs index c699a98..07f59ba 100644 --- a/src/message_log.rs +++ b/src/message_log.rs @@ -94,7 +94,13 @@ impl<'a> MessageLog { } } - pub fn on_privmsg(&mut self, channel_name: &str, nickname: &str, message: &str) { + pub fn on_privmsg( + &mut self, + current_nickname: &str, + channel_name: &str, + nickname: &str, + message: &str, + ) { let is_active = self.active_channel.as_deref() == Some(channel_name); let channel = self.get_mut(Some(channel_name.to_string())); @@ -104,8 +110,8 @@ impl<'a> MessageLog { }); if !is_active { - // TODO Configurable nickname - let highlight_regex = Regex::new(r"\bcri\b").unwrap(); + let highlight_regex = + Regex::new(&format!(r"\b{}\b", regex::escape(current_nickname))).unwrap(); if highlight_regex.is_match(message) { channel.unread_highlights += 1; } else { @@ -128,7 +134,7 @@ impl<'a> MessageLog { channel.unread_highlights = 0; } - pub fn view(&self, active_channel: &Option) -> Container<'_, crate::UiMessage> { + pub fn view(&self, current_nickname: &str) -> Container<'_, crate::UiMessage> { let lighter_grey = Color::new(0.93, 0.94, 0.95, 1.0); let dark_grey = Color::new(0.58, 0.65, 0.65, 1.0); let lighter_green = Color::new(0.94, 0.99, 0.87, 1.0); @@ -154,7 +160,7 @@ impl<'a> MessageLog { }; let messages = self - .get(active_channel) + .get(&self.active_channel) .unwrap() .messages .iter() @@ -218,8 +224,7 @@ impl<'a> MessageLog { ) } IrcMessage::Privmsg { nickname, message } => { - // TODO don't hardcode nickname lol - let is_self = nickname == "cri"; + let is_self = nickname == current_nickname; let mut elements = Vec::new(); if !is_self {