From a84018fd17d5d291fe354b5344f7e2c9c1497956 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 10 Feb 2025 18:19:14 +0300 Subject: [PATCH] view users ip param --- src/config.rs | 42 ++++++++++----- src/main.rs | 141 +++++++++++++++++++++++++++++++------------------- src/rac.rs | 46 ++++++++-------- src/term.rs | 104 ++++++++++++++----------------------- 4 files changed, 175 insertions(+), 158 deletions(-) diff --git a/src/config.rs b/src/config.rs index 1489b26..b7ff50d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use std::{fs, path::{Path, PathBuf}}; +use std::{fs, path::{Path, PathBuf}, thread, time::Duration}; use homedir::my_home; use serde_yml; @@ -8,45 +8,59 @@ const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}"; #[derive(serde::Serialize, serde::Deserialize)] pub struct Config { + #[serde(default = "default_host")] pub host: String, + #[serde(default)] pub name: Option, + #[serde(default = "default_message_format")] pub message_format: String, + #[serde(default = "default_update_time")] pub update_time: usize, - pub max_messages: usize + #[serde(default = "default_max_messages")] + pub max_messages: usize, + #[serde(default)] + pub enable_ip_viewing: bool, + #[serde(default)] + pub disable_ip_hiding: bool, } -pub fn load_config(path: PathBuf) -> Config { - // println!("Config path: {}", path.to_string_lossy()); - // println!("Loading config..."); +fn default_max_messages() -> usize { 100 } +fn default_update_time() -> usize { 50 } +fn default_host() -> String { "meex.lol:11234".to_string() } +fn default_message_format() -> String { MESSAGE_FORMAT.to_string() } - let config = if !fs::exists(&path).unwrap_or_default() { +pub fn load_config(path: PathBuf) -> Config { + if !fs::exists(&path).unwrap_or_default() { let host = get_input("Host (default: meex.lol:11234) > ").unwrap_or("meex.lol:11234".to_string()); let name = get_input("Name (default: ask every time) > "); - let update_time = get_input("Update Interval (default: 50) > ").map(|o| o.parse().ok()).flatten().unwrap_or(50); - let max_messages = get_input("Max Messages (default: 100) > ").map(|o| o.parse().ok()).flatten().unwrap_or(100); + let update_time = get_input("Update interval (default: 50) > ").map(|o| o.parse().ok()).flatten().unwrap_or(50); + let max_messages = get_input("Max messages (default: 100) > ").map(|o| o.parse().ok()).flatten().unwrap_or(100); + let enable_ip_viewing = get_input("Enable users IP viewing? (Y/N, default: N) > ").map(|o| o.to_lowercase() != "n").unwrap_or(false); + let disable_ip_hiding = get_input("Enable your IP viewing? (Y/N, default: N) > ").map(|o| o.to_lowercase() != "n").unwrap_or(false); let config = Config { host, name, message_format: MESSAGE_FORMAT.to_string(), update_time, - max_messages + max_messages, + enable_ip_viewing, + disable_ip_hiding }; let config_text = serde_yml::to_string(&config).expect("Config save error"); fs::create_dir_all(&path.parent().expect("Config save error")).expect("Config save error"); fs::write(&path, config_text).expect("Config save error"); + println!("Config saved! You can edit it in the path got with `bRAC --config-path`"); + thread::sleep(Duration::from_secs(4)); config } else { let config = &fs::read_to_string(&path).expect("Config load error"); serde_yml::from_str(config).expect("Config load error") - }; - - // println!("Config loaded successfully!"); - - config + } } pub fn get_config_path() -> PathBuf { + #[allow(unused_variables)] let config_path = Path::new("config.yml").to_path_buf(); #[cfg(target_os = "linux")] diff --git a/src/main.rs b/src/main.rs index cdc4258..e464192 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ use std::{ - error::Error, io::{stdin, stdout, BufRead, Write}, sync::{atomic::AtomicUsize, Arc, RwLock} + error::Error, io::{stdin, stdout, BufRead, Write}, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, time::SystemTime }; use colored::Color; -use config::{get_config_path, load_config, Config}; +use config::{get_config_path, load_config}; use rac::{read_messages, run_recv_loop, send_message}; use rand::random; use regex::Regex; @@ -16,7 +16,7 @@ const ADVERTISEMENT: &str = "\r\x1B[1A use bRAC client! https://github.com/MeexR const ADVERTISEMENT_ENABLED: bool = false; lazy_static! { - static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap(); + static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] \{(.*?)\} (.*)").unwrap(); static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![ (Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green), (Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed), @@ -46,17 +46,15 @@ fn get_input(prompt: &str) -> Option { } } -fn on_command(config: Arc, host: &str, disable_hiding_ip: bool, command: &str) -> Result<(), Box> { +fn on_command(ctx: Arc, command: &str) -> Result<(), Box> { let command = command.trim_start_matches("/"); let (command, args) = command.split_once(" ").unwrap_or((&command, "")); let args = args.split(" ").collect::>(); if command == "clear" { - send_message(host, &format!("\r\x1B[1A{}", " ".repeat(64)).repeat(config.max_messages), disable_hiding_ip)?; - // *input.write().unwrap() = "/ заспамлено)))".to_string(); + send_message(ctx.clone(), &format!("\r\x1B[1A{}", " ".repeat(64)).repeat(ctx.max_messages))?; } else if command == "spam" { - send_message(host, &format!("\r\x1B[1A{}{}", args.join(" "), " ".repeat(10)).repeat(config.max_messages), disable_hiding_ip)?; - // *input.write().unwrap() = "/ заспамлено)))".to_string(); + send_message(ctx.clone(), &format!("\r\x1B[1A{}{}", args.join(" "), " ".repeat(10)).repeat(ctx.max_messages))?; } else if command == "help" { write!(stdout(), "Help message:\r /clear - clear console\r @@ -65,6 +63,28 @@ fn on_command(config: Arc, host: &str, disable_hiding_ip: bool, command: \r Press enter to close")?; stdout().flush()?; + } else if command == "ping" { + let mut before = ctx.messages.1.load(Ordering::SeqCst); + let start = SystemTime::now(); + let message = format!("Checking ping... {:X}", random::()); + send_message(ctx.clone(), &message)?; + loop { + let data = read_messages(ctx.clone(), before).ok().flatten(); + + if let Some((data, size)) = data { + if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) { + println!("{}", last); + if last.contains(&message) { + break; + } else { + before = size; + } + } else { + before = size; + } + } + } + send_message(ctx.clone(), &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?; } Ok(()) @@ -109,64 +129,77 @@ struct Args { /// Disable ip hiding #[arg(short='i', long)] disable_ip_hiding: bool, + + /// Enable users IP viewing + #[arg(short='v', long)] + enable_users_ip_viewing: bool, +} + + +struct Context { + messages: Arc<(RwLock>, AtomicUsize)>, + input: Arc>, + host: String, + name: String, + disable_formatting: bool, + disable_commands: bool, + disable_hiding_ip: bool, + message_format: String, + update_time: usize, + max_messages: usize, + enable_ip_viewing: bool } fn main() { let args = Args::parse(); - - let config_path = get_config_path(); - - if args.config_path { - print!("{}", config_path.to_string_lossy()); - return; - } - // let start_time = SystemTime::now(); - let mut config = load_config(config_path); + let context = { + let config_path = get_config_path(); + + if args.config_path { + print!("{}", config_path.to_string_lossy()); + return; + } - let name = match args.name.clone().or(config.name.clone()) { - Some(i) => i, - None => { - let anon_name = format!("Anon#{:X}", random::()); - get_input(&format!("Name (default: {}) > ", anon_name)).unwrap_or(anon_name) - }, + let config = load_config(config_path); + + Context { + messages: Arc::new((RwLock::new(Vec::new()), AtomicUsize::new(0))), + input: Arc::new(RwLock::new(String::new())), + + message_format: args.message_format.clone().unwrap_or(config.message_format.clone()), + host: args.host.clone().unwrap_or(config.host.clone()), + name: match args.name.clone().or(config.name.clone()) { + Some(i) => i, + None => { + let anon_name = format!("Anon#{:X}", random::()); + get_input(&format!("Name (default: {}) > ", anon_name)).unwrap_or(anon_name) + }, + }, + disable_formatting: args.disable_formatting, + disable_commands: args.disable_commands, + disable_hiding_ip: args.disable_ip_hiding, + update_time: config.update_time, + max_messages: config.max_messages, + enable_ip_viewing: args.enable_users_ip_viewing || config.enable_ip_viewing + } }; - if let Some(host) = args.host { - config.host = host; - } - - if let Some(message_format) = args.message_format { - config.message_format = message_format; - } - - let disable_hiding_ip = args.disable_ip_hiding; - - if let Some(message) = args.send_message { - send_message(&config.host, &message, disable_hiding_ip).expect("Error sending message"); - return; - } + let context = Arc::new(context); if args.read_messages { - print!("{}", read_messages(&config.host, config.max_messages, 0).ok().flatten().expect("Error reading messages").0.join("\n")); + print!("{}", read_messages(context.clone(), 0).ok().flatten().expect("Error reading messages").0.join("\n")); + } + + if let Some(message) = &args.send_message { + send_message(context.clone(), message).expect("Error sending message"); + } + + if args.send_message.is_some() || args.read_messages { return; } - let disable_formatting = args.disable_formatting; - let disable_commands = args.disable_commands; - - let messages = Arc::new((RwLock::new(Vec::new()), AtomicUsize::new(0))); - let input = Arc::new(RwLock::new(String::new())); - let config = Arc::new(config); - - - - // let elapsed = start_time.elapsed().unwrap().as_millis(); - // if elapsed < 1500 { - // thread::sleep(Duration::from_millis((1500 - elapsed) as u64)); - // } - - run_recv_loop(config.clone(), config.host.clone(), messages.clone(), input.clone(), disable_formatting); - run_main_loop(config.clone(), messages.clone(), input.clone(), config.host.clone(), name.clone(), disable_formatting, disable_commands, disable_hiding_ip); + run_recv_loop(context.clone()); + run_main_loop(context.clone()); } diff --git a/src/rac.rs b/src/rac.rs index 1ac618f..42557d8 100644 --- a/src/rac.rs +++ b/src/rac.rs @@ -1,18 +1,18 @@ -use std::{error::Error, io::{Read, Write}, net::TcpStream, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, thread, time::Duration}; +use std::{error::Error, io::{Read, Write}, net::TcpStream, sync::{atomic::Ordering, Arc}, thread, time::Duration}; -use crate::{config::Config, term::print_console, ADVERTISEMENT, ADVERTISEMENT_ENABLED}; +use super::{term::print_console, Context, ADVERTISEMENT, ADVERTISEMENT_ENABLED}; -pub fn send_message(host: &str, message: &str, disable_hiding_ip: bool) -> Result<(), Box> { - let mut stream = TcpStream::connect(host)?; +pub fn send_message(context: Arc, message: &str) -> Result<(), Box> { + let mut stream = TcpStream::connect(&context.host)?; stream.write_all(&[0x01])?; let data = format!("{}{}{}{}", - if !disable_hiding_ip { + if !context.disable_hiding_ip { "\r\x07" } else { "" }, message, - if !disable_hiding_ip && message.chars().count() < 39 { + if !context.disable_hiding_ip && message.chars().count() < 39 { " ".repeat(39-message.chars().count()) } else { String::new() @@ -33,8 +33,8 @@ fn skip_null(stream: &mut TcpStream) -> Result, Box> { } } -pub fn read_messages(host: &str, max_messages: usize, last_size: usize) -> Result, usize)>, Box> { - let mut stream = TcpStream::connect(host)?; +pub fn read_messages(context: Arc, last_size: usize) -> Result, usize)>, Box> { + let mut stream = TcpStream::connect(&context.host)?; stream.write_all(&[0x00])?; @@ -75,30 +75,28 @@ pub fn read_messages(host: &str, max_messages: usize, last_size: usize) -> Resul let lines: Vec<&str> = packet_data.split("\n").collect(); let lines: Vec = lines.clone().into_iter() - .skip(lines.len() - max_messages) + .skip(lines.len() - context.max_messages) .map(|o| o.to_string()) .collect(); Ok(Some((lines, packet_size))) } -fn recv_loop(config: Arc, host: &str, cache: Arc<(RwLock>, AtomicUsize)>, input: Arc>, disable_formatting: bool) -> Result<(), Box> { - while let Ok(data) = read_messages(host, config.max_messages, cache.1.load(Ordering::SeqCst)) { - if let Some(data) = data { - *cache.0.write().unwrap() = data.0.clone(); - cache.1.store(data.1, Ordering::SeqCst); - print_console(config.clone(), data.0, &input.read().unwrap(), disable_formatting)?; - thread::sleep(Duration::from_millis(config.update_time as u64)); - } - } - Ok(()) -} - -pub fn run_recv_loop(config: Arc, host: String, messages: Arc<(RwLock>, AtomicUsize)>, input: Arc>, disable_formatting: bool) { +pub fn run_recv_loop(context: Arc) { thread::spawn({ + let cache = context.messages.clone(); + let update_time = context.update_time; + let input = context.input.clone(); + move || { - let _ = recv_loop(config.clone(), &host, messages, input, disable_formatting); - println!("Connection closed"); + loop { + if let Ok(Some(data)) = read_messages(context.clone(), cache.1.load(Ordering::SeqCst)) { + *cache.0.write().unwrap() = data.0.clone(); + cache.1.store(data.1, Ordering::SeqCst); + print_console(context.clone(), data.0, &input.read().unwrap()).expect("Error printing console"); + thread::sleep(Duration::from_millis(update_time as u64)); + } + } } }); } \ No newline at end of file diff --git a/src/term.rs b/src/term.rs index 4b328c6..a6b59d9 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,21 +1,20 @@ -use std::{error::Error, io::{stdout, Write}, sync::{atomic::AtomicUsize, Arc, RwLock}, time::Duration}; +use std::{error::Error, io::{stdout, Write}, sync::Arc, time::Duration}; use colored::{Color, Colorize}; -use crossterm::{cursor::MoveLeft, event::{self, Event, KeyCode, KeyModifiers, ModifierKeyCode}, terminal::{disable_raw_mode, enable_raw_mode}, ExecutableCommand}; +use crossterm::{cursor::MoveLeft, event::{self, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode}, ExecutableCommand}; use regex::Regex; -use crate::{config::Config, on_command, rac::send_message, ADVERTISEMENT, COLORED_USERNAMES, DATE_REGEX}; +use super::{on_command, rac::send_message, Context, ADVERTISEMENT, COLORED_USERNAMES, DATE_REGEX}; -pub fn print_console(config: Arc, messages: Vec, input: &str, disable_formatting: bool) -> Result<(), Box> { +pub fn print_console(context: Arc, messages: Vec, input: &str) -> Result<(), Box> { let text = format!( "{}{}\n> {}", - "\n".repeat(config.max_messages - messages.len()), - if disable_formatting { + "\n".repeat(context.max_messages - messages.len()), + if context.disable_formatting { messages } else { - messages.into_iter().filter_map(format_message).collect() + messages.into_iter().filter_map(|o| format_message(context.clone(), o)).collect() }.join("\n"), - // if sound { "\x07" } else { "" }, input ); for line in text.lines() { @@ -25,9 +24,8 @@ pub fn print_console(config: Arc, messages: Vec, input: &str, di Ok(()) } -fn format_message(message: String) -> Option { +fn format_message(ctx: Arc, 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 @@ -37,23 +35,33 @@ fn format_message(message: String) -> Option { } let date = DATE_REGEX.captures(&message)?; - let (date, message) = (date.get(1)?.as_str().to_string(), date.get(2)?.as_str().to_string()); + let (date, ip, message) = ( + date.get(1)?.as_str().to_string(), + date.get(2)?.as_str().to_string(), + date.get(3)?.as_str().to_string() + ); + + let prefix = if ctx.enable_ip_viewing { + format!("{}{} [{}]", ip, " ".repeat(15-ip.len()), date) + } else { + format!("[{}]", date) + }; Some(if let Some(captures) = find_username_color(&message) { let nick = captures.0; let content = captures.1; let color = captures.2; - format!( + format!( "{} {} {}", - format!("[{}]", date).white().dimmed(), + prefix.white().dimmed(), format!("<{}>", nick).color(color).bold(), content.white().blink() ) } else { format!( "{} {}", - format!("[{}]", date).white().dimmed(), + prefix.white().dimmed(), message.white().blink() ) }) @@ -67,7 +75,6 @@ fn sanitize_text(input: &str) -> String { 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) { @@ -77,16 +84,7 @@ fn find_username_color(message: &str) -> Option<(String, String, Color)> { None } -fn poll_events( - config: Arc, - input: Arc>, - messages: Arc<(RwLock>, AtomicUsize)>, - host: String, - name: String, - disable_formatting: bool, - disable_commands: bool, - disable_hiding_ip: bool -) { +fn poll_events(ctx: Arc) { loop { if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue } @@ -99,10 +97,10 @@ fn poll_events( Event::Key(event) => { match event.code { KeyCode::Enter => { - let message = input.read().unwrap().clone(); + let message = ctx.input.read().unwrap().clone(); if !message.is_empty() { - let input_len = input.read().unwrap().chars().count(); + let input_len = ctx.input.read().unwrap().chars().count(); write!(stdout(), "{}{}{}", MoveLeft(1).to_string().repeat(input_len), @@ -110,25 +108,24 @@ fn poll_events( MoveLeft(1).to_string().repeat(input_len) ).unwrap(); stdout().lock().flush().unwrap(); - input.write().unwrap().clear(); + ctx.input.write().unwrap().clear(); - if message.starts_with("/") && !disable_commands { - on_command(config.clone(), &host, disable_hiding_ip, &message).expect("Error on command"); + if message.starts_with("/") && !ctx.disable_commands { + on_command(ctx.clone(), &message).expect("Error on command"); } else { - let message = config.message_format.replace("{name}", &name).replace("{text}", &message); - send_message(&host, &message, disable_hiding_ip).expect("Error sending message"); + let message = ctx.message_format.replace("{name}", &ctx.name).replace("{text}", &message); + send_message(ctx.clone(), &message).expect("Error sending message"); } } else { print_console( - config.clone(), - messages.0.read().unwrap().clone(), - &input.read().unwrap(), - disable_formatting + ctx.clone(), + ctx.messages.0.read().unwrap().clone(), + &ctx.input.read().unwrap() ).expect("Error printing console"); } } KeyCode::Backspace => { - if input.write().unwrap().pop().is_some() { + if ctx.input.write().unwrap().pop().is_some() { stdout().lock().execute(MoveLeft(1)).unwrap(); write!(stdout(), " {}", MoveLeft(1).to_string()).unwrap(); stdout().lock().flush().unwrap(); @@ -143,7 +140,7 @@ fn poll_events( disable_raw_mode().unwrap(); break; } - input.write().unwrap().push(c); + ctx.input.write().unwrap().push(c); write!(stdout(), "{}", c).unwrap(); stdout().lock().flush().unwrap(); } @@ -151,7 +148,7 @@ fn poll_events( } }, Event::Paste(data) => { - input.write().unwrap().push_str(&data); + ctx.input.write().unwrap().push_str(&data); write!(stdout(), "{}", &data).unwrap(); stdout().lock().flush().unwrap(); } @@ -160,32 +157,7 @@ fn poll_events( } } -pub fn run_main_loop( - config: Arc, - messages: Arc<(RwLock>, AtomicUsize)>, - input: Arc>, - host: String, - name: String, - disable_formatting: bool, - disable_commands: bool, - disable_hiding_ip: bool -) { +pub fn run_main_loop(ctx: Arc) { enable_raw_mode().unwrap(); - - // thread::spawn({ - // let messages = messages.clone(); - // let input = input.clone(); - - // move || { - // loop { - // print_console( - // &messages.read().unwrap(), - // &input.read().unwrap() - // ).expect("Error printing console"); - // thread::sleep(Duration::from_secs(5)); - // } - // } - // }); - - poll_events(config, input.clone(), messages.clone(), host, name, disable_formatting, disable_commands, disable_hiding_ip); + poll_events(ctx); } \ No newline at end of file