From 5290d2d2e28c5e7bc72974ff66bca36e446d60d0 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 10 Feb 2025 09:01:06 +0300 Subject: [PATCH] refactoring --- build-all.sh | 16 ---- shell.nix | 13 --- src/main.rs | 252 ++------------------------------------------------- src/rac.rs | 90 ++++++++++++++++++ src/term.rs | 158 ++++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 271 deletions(-) delete mode 100644 build-all.sh delete mode 100644 shell.nix create mode 100644 src/rac.rs create mode 100644 src/term.rs diff --git a/build-all.sh b/build-all.sh deleted file mode 100644 index f6b31a0..0000000 --- a/build-all.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -e - -TARGETS=( - x86_64-unknown-linux-gnu - i686-unknown-linux-gnu - x86_64-pc-windows-gnu - i686-pc-windows-gnu - x86_64-apple-darwin - aarch64-apple-darwin -) - -for TARGET in "${TARGETS[@]}"; do - cargo build --release --target "$TARGET" -done \ No newline at end of file diff --git a/shell.nix b/shell.nix deleted file mode 100644 index e405ebc..0000000 --- a/shell.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ pkgs ? import {} }: -pkgs.mkShell { - buildInputs = with pkgs; [ - rustup - gcc_multi - pkg-config - zlib - openssl - pkgsCross.gnu32.buildPackages.gcc - pkgsCross.mingw32.buildPackages.gcc - pkgsCross.mingwW64.buildPackages.gcc - ]; -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f9d5c13..49d7f66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,15 @@ use std::{ error::Error, - io::{stdin, stdout, BufRead, Read, Write}, - net::TcpStream, + io::{stdin, stdout, BufRead, Write}, sync::{Arc, RwLock}, - thread, - time::Duration }; -use colored::{Color, Colorize}; -use crossterm::{ - cursor::MoveLeft, - event::{self, Event, KeyCode}, - terminal::{disable_raw_mode, enable_raw_mode}, - ExecutableCommand -}; +use colored::Color; +use rac::{run_recv_loop, send_message}; use rand::random; use regex::Regex; use lazy_static::lazy_static; +use term::run_main_loop; const DEFAULT_HOST: &str = "meex.lol:11234"; @@ -28,6 +21,10 @@ const ADVERTISEMENT_ENABLED: bool = false; const UPDATE_TIME: u64 = 50; +mod term; +mod rac; + + lazy_static! { static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap(); static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![ @@ -39,83 +36,7 @@ lazy_static! { } -fn send_message(host: &str, message: &str) -> Result<(), Box> { - let mut stream = TcpStream::connect(host)?; - stream.write_all(&[0x01])?; - let data = format!("\r\x07{}{}{}", - message, - if message.chars().count() < 39 { - " ".repeat(39-message.chars().count()) - } else { - String::new() - }, - if ADVERTISEMENT_ENABLED {ADVERTISEMENT} else {""} - ); - stream.write_all(data.as_bytes())?; - Ok(()) -} -fn skip_null(stream: &mut TcpStream) -> Result, Box> { - loop { - let mut buf = vec![0; 1]; - stream.read_exact(&mut buf)?; - if buf[0] != 0 { - break Ok(buf) - } - } -} - -fn read_messages(host: &str) -> Result> { - let mut stream = TcpStream::connect(host)?; - - stream.write_all(&[0x00])?; - - let packet_size = { - let mut data = skip_null(&mut stream)?; - - loop { - let mut buf = vec![0; 1]; - stream.read_exact(&mut buf)?; - let ch = buf[0]; - if ch == 0 { - break - } - data.push(ch); - } - - String::from_utf8(data)? - .trim_matches(char::from(0)) - .parse()? - }; - - stream.write_all(&[0x01])?; - - let packet_data = { - let mut data = skip_null(&mut stream)?; - while data.len() < packet_size { - let mut buf = vec![0; packet_size - data.len()]; - let read_bytes = stream.read(&mut buf)?; - buf.truncate(read_bytes); - data.append(&mut buf); - } - String::from_utf8_lossy(&data).to_string() - }; - - Ok(packet_data) -} - -fn recv_loop(host: &str, cache: Arc>, input: Arc>) -> Result<(), Box> { - while let Ok(data) = read_messages(host) { - if data == cache.read().unwrap().clone() { - continue - } - - *cache.write().unwrap() = data; - print_console(&cache.read().unwrap(), &input.read().unwrap())?; - thread::sleep(Duration::from_millis(UPDATE_TIME)); - } - Ok(()) -} fn get_input(prompt: &str, default: &str) -> String { let input = || -> Option { @@ -138,58 +59,6 @@ fn get_input(prompt: &str, default: &str) -> String { }.to_string() } -fn sanitize_text(input: &str) -> String { - let ansi_regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap(); - let control_chars_regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap(); - let without_ansi = ansi_regex.replace_all(input, ""); - let cleaned_text = control_chars_regex.replace_all(&without_ansi, ""); - cleaned_text.into_owned() -} - -/// nick content nick_color -fn find_username_color(message: &str) -> Option<(String, String, Color)> { - for (re, color) in COLORED_USERNAMES.iter() { - if let Some(captures) = re.captures(message) { - return Some((captures[1].to_string(), captures[2].to_string(), color.clone())) - } - } - None -} - -fn format_message(message: String) -> Option { - let message = message.trim_end_matches(ADVERTISEMENT); - let message = Regex::new(r"\{[^}]*\}\ ").unwrap().replace(&message, "").to_string(); - let message = sanitize_text(&message); - if ADVERTISEMENT.len() > 0 && - message.starts_with(ADVERTISEMENT - .trim_start_matches("\r") - .trim_start_matches("\n")) { - return None - } - - let date = DATE_REGEX.captures(&message)?; - let (date, message) = (date.get(1)?.as_str().to_string(), date.get(2)?.as_str().to_string()); - - Some(if let Some(captures) = find_username_color(&message) { - let nick = captures.0; - let content = captures.1; - let color = captures.2; - - format!( - "{} {} {}", - format!("[{}]", date).white().dimmed(), - format!("<{}>", nick).color(color).bold(), - content.white().blink() - ) - } else { - format!( - "{} {}", - format!("[{}]", date).white().dimmed(), - message.white().blink() - ) - }) -} - fn on_command(host: &str, command: &str) -> Result<(), Box> { let command = command.trim_start_matches("/"); let (command, args) = command.split_once(" ").unwrap_or((&command, "")); @@ -206,28 +75,6 @@ fn on_command(host: &str, command: &str) -> Result<(), Box> { Ok(()) } -fn print_console(messages: &str, input: &str) -> Result<(), Box> { - let mut messages = messages.split("\n") - .map(|o| o.to_string()) - .collect::>(); - messages.reverse(); - messages.truncate(MAX_MESSAGES); - messages.reverse(); - let messages: Vec = messages.into_iter().filter_map(format_message).collect(); - let text = format!( - "{}{}\n> {}", - "\n".repeat(MAX_MESSAGES - messages.len()), - messages.join("\n"), - // if sound { "\x07" } else { "" }, - input - ); - for line in text.lines() { - write!(stdout().lock(), "\r\n{}", line)?; - stdout().lock().flush()?; - } - Ok(()) -} - fn main() { let host = get_input(&format!("Host (default: {}) > ", DEFAULT_HOST), DEFAULT_HOST); let anon_name = format!("Anon#{:X}", random::()); @@ -236,85 +83,6 @@ fn main() { let messages = Arc::new(RwLock::new(String::new())); let input = Arc::new(RwLock::new(String::new())); - enable_raw_mode().unwrap(); - - thread::spawn({ - let host = host.clone(); - let messages = messages.clone(); - let input = input.clone(); - - move || { - let _ = recv_loop(&host, messages, input); - println!("Connection closed"); - } - }); - - thread::spawn({ - let messages = messages.clone(); - let input = input.clone(); - - move || { - print_console( - &messages.read().unwrap(), - &input.read().unwrap() - ).expect("Error printing console"); - thread::sleep(Duration::from_millis(UPDATE_TIME)); - } - }); - - loop { - if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue } - - let event = match event::read() { - Ok(i) => i, - Err(_) => { continue }, - }; - - match event { - Event::Key(event) => { - match event.code { - KeyCode::Enter => { - let message = input.read().unwrap().clone(); - - let input_len = input.read().unwrap().len(); - stdout().lock().execute(MoveLeft(input_len as u16)).unwrap(); - write!(stdout(), "{}{}", " ".repeat(input_len), MoveLeft(input_len as u16).to_string()).unwrap(); - stdout().lock().flush().unwrap(); - input.write().unwrap().clear(); - - if !message.is_empty() { - if message.starts_with("/") { - on_command(&host, &message).expect("Error on command"); - } else { - send_message(&host, &format!("{}<{}> {}", MAGIC_KEY, name, message)).expect("Error sending message"); - } - } - } - KeyCode::Backspace => { - if input.write().unwrap().pop().is_some() { - stdout().lock().execute(MoveLeft(1)).unwrap(); - write!(stdout(), " {}", MoveLeft(1).to_string()).unwrap(); - stdout().lock().flush().unwrap(); - } - } - KeyCode::Char(c) => { - input.write().unwrap().push(c); - write!(stdout(), "{}", c).unwrap(); - stdout().lock().flush().unwrap(); - } - KeyCode::Esc => { - disable_raw_mode().unwrap(); - break; - }, - _ => {} - } - }, - Event::Paste(data) => { - input.write().unwrap().push_str(&data); - write!(stdout(), "{}", &data).unwrap(); - stdout().lock().flush().unwrap(); - } - _ => {} - } - } + run_recv_loop(host.clone(), messages.clone(), input.clone()); + run_main_loop(messages.clone(), input.clone(), host.clone(), name.clone()); } diff --git a/src/rac.rs b/src/rac.rs new file mode 100644 index 0000000..106fcd2 --- /dev/null +++ b/src/rac.rs @@ -0,0 +1,90 @@ +use std::{error::Error, io::{Read, Write}, net::TcpStream, sync::{Arc, RwLock}, thread, time::Duration}; + +use super::{ADVERTISEMENT, ADVERTISEMENT_ENABLED, term::print_console, UPDATE_TIME}; + +pub fn send_message(host: &str, message: &str) -> Result<(), Box> { + let mut stream = TcpStream::connect(host)?; + stream.write_all(&[0x01])?; + let data = format!("\r\x07{}{}{}", + message, + if message.chars().count() < 39 { + " ".repeat(39-message.chars().count()) + } else { + String::new() + }, + if ADVERTISEMENT_ENABLED {ADVERTISEMENT} else {""} + ); + stream.write_all(data.as_bytes())?; + Ok(()) +} + +fn skip_null(stream: &mut TcpStream) -> Result, Box> { + loop { + let mut buf = vec![0; 1]; + stream.read_exact(&mut buf)?; + if buf[0] != 0 { + break Ok(buf) + } + } +} + +fn read_messages(host: &str) -> Result> { + let mut stream = TcpStream::connect(host)?; + + stream.write_all(&[0x00])?; + + let packet_size = { + let mut data = skip_null(&mut stream)?; + + loop { + let mut buf = vec![0; 1]; + stream.read_exact(&mut buf)?; + let ch = buf[0]; + if ch == 0 { + break + } + data.push(ch); + } + + String::from_utf8(data)? + .trim_matches(char::from(0)) + .parse()? + }; + + stream.write_all(&[0x01])?; + + let packet_data = { + let mut data = skip_null(&mut stream)?; + while data.len() < packet_size { + let mut buf = vec![0; packet_size - data.len()]; + let read_bytes = stream.read(&mut buf)?; + buf.truncate(read_bytes); + data.append(&mut buf); + } + String::from_utf8_lossy(&data).to_string() + }; + + Ok(packet_data) +} + +fn recv_loop(host: &str, cache: Arc>, input: Arc>) -> Result<(), Box> { + while let Ok(data) = read_messages(host) { + if data == cache.read().unwrap().clone() { + continue + } + + *cache.write().unwrap() = data; + print_console(&cache.read().unwrap(), &input.read().unwrap())?; + thread::sleep(Duration::from_millis(UPDATE_TIME)); + } + Ok(()) +} + +pub fn run_recv_loop(host: String, messages: Arc>, input: Arc>) { + thread::spawn({ + move || { + let _ = recv_loop(&host, messages, input); + println!("Connection closed"); + } + }); +} \ No newline at end of file diff --git a/src/term.rs b/src/term.rs new file mode 100644 index 0000000..5943387 --- /dev/null +++ b/src/term.rs @@ -0,0 +1,158 @@ +use std::{error::Error, io::{stdout, Write}, sync::{Arc, RwLock}, thread, time::Duration}; + +use colored::{Color, Colorize}; +use crossterm::{cursor::MoveLeft, event::{self, Event, KeyCode}, terminal::{disable_raw_mode, enable_raw_mode}, ExecutableCommand}; +use regex::Regex; + +use crate::{on_command, rac::send_message, ADVERTISEMENT, COLORED_USERNAMES, DATE_REGEX, MAGIC_KEY, MAX_MESSAGES, UPDATE_TIME}; + +pub fn print_console(messages: &str, input: &str) -> Result<(), Box> { + let mut messages = messages.split("\n") + .map(|o| o.to_string()) + .collect::>(); + messages.reverse(); + messages.truncate(MAX_MESSAGES); + messages.reverse(); + let messages: Vec = messages.into_iter().filter_map(format_message).collect(); + let text = format!( + "{}{}\n> {}", + "\n".repeat(MAX_MESSAGES - messages.len()), + messages.join("\n"), + // if sound { "\x07" } else { "" }, + input + ); + for line in text.lines() { + write!(stdout().lock(), "\r\n{}", line)?; + stdout().lock().flush()?; + } + Ok(()) +} + +fn format_message(message: String) -> Option { + let message = message.trim_end_matches(ADVERTISEMENT); + let message = Regex::new(r"\{[^}]*\}\ ").unwrap().replace(&message, "").to_string(); + let message = sanitize_text(&message); + if ADVERTISEMENT.len() > 0 && + message.starts_with(ADVERTISEMENT + .trim_start_matches("\r") + .trim_start_matches("\n")) { + return None + } + + let date = DATE_REGEX.captures(&message)?; + let (date, message) = (date.get(1)?.as_str().to_string(), date.get(2)?.as_str().to_string()); + + Some(if let Some(captures) = find_username_color(&message) { + let nick = captures.0; + let content = captures.1; + let color = captures.2; + + format!( + "{} {} {}", + format!("[{}]", date).white().dimmed(), + format!("<{}>", nick).color(color).bold(), + content.white().blink() + ) + } else { + format!( + "{} {}", + format!("[{}]", date).white().dimmed(), + message.white().blink() + ) + }) +} + +fn sanitize_text(input: &str) -> String { + let ansi_regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap(); + let control_chars_regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap(); + let without_ansi = ansi_regex.replace_all(input, ""); + let cleaned_text = control_chars_regex.replace_all(&without_ansi, ""); + cleaned_text.into_owned() +} + +/// nick content nick_color +fn find_username_color(message: &str) -> Option<(String, String, Color)> { + for (re, color) in COLORED_USERNAMES.iter() { + if let Some(captures) = re.captures(message) { + return Some((captures[1].to_string(), captures[2].to_string(), color.clone())) + } + } + None +} + +fn poll_events(input: Arc>, host: String, name: String) { + loop { + if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue } + + let event = match event::read() { + Ok(i) => i, + Err(_) => { continue }, + }; + + match event { + Event::Key(event) => { + match event.code { + KeyCode::Enter => { + let message = input.read().unwrap().clone(); + + let input_len = input.read().unwrap().chars().count(); + stdout().lock().execute(MoveLeft(input_len as u16)).unwrap(); + write!(stdout(), "{}{}", " ".repeat(input_len), MoveLeft(input_len as u16).to_string()).unwrap(); + stdout().lock().flush().unwrap(); + input.write().unwrap().clear(); + + if !message.is_empty() { + if message.starts_with("/") { + on_command(&host, &message).expect("Error on command"); + } else { + send_message(&host, &format!("{}<{}> {}", MAGIC_KEY, name, message)).expect("Error sending message"); + } + } + } + KeyCode::Backspace => { + if input.write().unwrap().pop().is_some() { + stdout().lock().execute(MoveLeft(1)).unwrap(); + write!(stdout(), " {}", MoveLeft(1).to_string()).unwrap(); + stdout().lock().flush().unwrap(); + } + } + KeyCode::Char(c) => { + input.write().unwrap().push(c); + write!(stdout(), "{}", c).unwrap(); + stdout().lock().flush().unwrap(); + } + KeyCode::Esc => { + disable_raw_mode().unwrap(); + break; + }, + _ => {} + } + }, + Event::Paste(data) => { + input.write().unwrap().push_str(&data); + write!(stdout(), "{}", &data).unwrap(); + stdout().lock().flush().unwrap(); + } + _ => {} + } + } +} + +pub fn run_main_loop(messages: Arc>, input: Arc>, host: String, name: String) { + enable_raw_mode().unwrap(); + + thread::spawn({ + let messages = messages.clone(); + let input = input.clone(); + + move || { + print_console( + &messages.read().unwrap(), + &input.read().unwrap() + ).expect("Error printing console"); + thread::sleep(Duration::from_millis(UPDATE_TIME)); + } + }); + + poll_events(input.clone(), host, name); +} \ No newline at end of file