diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..64d7111 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bRAC" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4d53bbc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "bRAC" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f6a484d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,109 @@ +use std::{error::Error, io::{stdin, stdout, BufRead, Read, Write}, net::TcpStream, thread}; + +const MAX_MESSAGES: usize = 100; + +type E = Box; + +fn send_message(host: &str, message: &str) -> Result<(), E> { + let mut stream = TcpStream::connect(host)?; + stream.write_all(&[0x01])?; + stream.write_all(message.as_bytes())?; + Ok(()) +} + +fn sanitize_string(s: &str, sanitize_newlines: bool) -> String { + let mut sanitized = s.replace(&['\x08', '\x0D', '\x1B'][..], ""); + + if sanitize_newlines { + sanitized = sanitized.replace("\n", "\\\\n"); + if !s.ends_with('\n') { + sanitized.push('\n'); + } + } + + sanitized +} + + +/// max messages count: 100 +fn read_messages(host: &str) -> Result, E> { + let mut stream = TcpStream::connect(host)?; + stream.write_all(&[0x00])?; + let packet_size = { + let mut buf= vec![0; 10]; + stream.read(&mut buf)?; + String::from_utf8(buf)?.trim_matches(char::from(0)).parse()? + }; + stream.write_all(&[0x01])?; + let packet_data = { + let mut buf = vec![0; packet_size]; + stream.read_exact(&mut buf)?; + let buf_str = String::from_utf8_lossy(&buf).to_string(); + let start_null = buf_str.len() - buf_str.trim_start_matches(char::from(0)).len(); + let mut buf = vec![0; start_null]; + stream.read_exact(&mut buf)?; + format!("{}{}", &buf_str, String::from_utf8_lossy(&buf).to_string()) + }; + let packet_data = sanitize_string(&packet_data, false); + let mut lines: Vec = packet_data.split("\n").map(|o| o.to_string()).collect(); + lines.reverse(); + lines.truncate(MAX_MESSAGES); + lines.reverse(); + Ok(lines) +} + +fn print_console(messages: Vec) -> Result<(), E> { + let mut out = stdout().lock(); + let text = format!("{}\n> ", messages.join("\n")); + out.write_all(text.as_bytes())?; + out.flush()?; + Ok(()) +} + +fn recv_loop(host: &str) -> Result<(), E> { + let mut cache = Vec::new(); + while let Ok(messages) = read_messages(host) { + if cache == messages { continue } + print_console(messages.clone())?; + cache = messages; + } + Ok(()) +} + +fn read_host() -> Option { + let mut out = stdout().lock(); + out.write_all("Host (default: meex.lol:11234) > ".as_bytes()).ok()?; + out.flush().ok()?; + stdin().lock().lines().next() + .map(|o| o.ok()) + .flatten() + .map(|o| o.trim().to_string()) +} + +fn main() { + let host = read_host(); + + let host = if let Some(host) = &host { + if host.is_empty() { + "meex.lol:11234" + } else { + host.as_str() + } + } else { + "meex.lol:11234" + }.to_string(); + + thread::spawn({ + let host = host.clone(); + + move || { + let _ = recv_loop(&host); + println!("Connection closed"); + } + }); + + let mut lines = stdin().lock().lines(); + while let Some(Ok(message)) = lines.next() { + send_message(&host, &message).expect("Error sending message"); + } +}