diff --git a/Cargo.lock b/Cargo.lock index 732e5d1..2b488c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + [[package]] name = "autocfg" version = "1.4.0" @@ -23,9 +29,12 @@ version = "0.1.0+1.99.2" dependencies = [ "colored", "crossterm", + "homedir", "lazy_static", "rand", "regex", + "serde", + "serde_yml", ] [[package]] @@ -46,6 +55,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "colored" version = "3.0.0" @@ -80,6 +95,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.10" @@ -102,6 +123,40 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "homedir" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" +dependencies = [ + "cfg-if", + "nix", + "widestring", + "windows", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "lazy_static" version = "1.5.0" @@ -114,6 +169,16 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libyml" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -154,6 +219,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -286,12 +363,53 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yml" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" +dependencies = [ + "indexmap", + "itoa", + "libyml", + "memchr", + "ryu", + "serde", + "version_check", +] + [[package]] name = "signal-hook" version = "0.3.17" @@ -345,6 +463,12 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -360,6 +484,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -382,6 +512,59 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 2cbc943..db9e7b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,7 @@ rand = "0.9.0" regex = "1.11.1" colored = "3.0.0" lazy_static = "1.5.0" -crossterm = "0.28.1" \ No newline at end of file +crossterm = "0.28.1" +serde = { version = "1.0.217", features = ["serde_derive"] } +serde_yml = "0.0.12" +homedir = "0.3.4" \ No newline at end of file diff --git a/README.md b/README.md index 1aebfa3..4bdfa08 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ better RAC client ## features -- cheat commands +- cheat commands (type /help) - no ip and date visible - uses TOR proxy server by default - plays sound when users receive your messages @@ -32,7 +32,8 @@ cargo run # run (builds and runs bRAC itself) ## commands `/clear` - clear chat \ -`/spam *args` - spam with text +`/spam *args` - spam with text \ +`/help` - show help message ## colored usernames diff --git a/config.yml b/config.yml deleted file mode 100644 index 23586ae..0000000 --- a/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -host: meex.lol:11234 # reverse proxy through tor -name: null # username (null - ask every time) -magic_key: "\uB9AC\u3E70" # default bRAC marker -ad_enabled: false # enable sending ad of bRAC above your message -update_time: 50 # update messages interval \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..a38af6f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,69 @@ +use std::{fs, path::{Path, PathBuf}}; +use homedir::my_home; +use serde_yml; + +use super::get_input; + +const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}"; + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Config { + pub host: String, + pub name: Option, + pub message_format: String, + pub update_time: usize, + pub max_messages: usize +} + +pub fn load_config(path: PathBuf) -> Config { + println!("Config path: {}", path.to_string_lossy()); + println!("Loading config..."); + + let 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 config = Config { + host, + name, + message_format: MESSAGE_FORMAT.to_string(), + update_time, + max_messages + }; + fs::write(path, serde_yml::to_string(&config).expect("Config save error")); + 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 { + let config_path = Path::new("config.yml").to_path_buf(); + + #[cfg(target_os = "linux")] + let config_path = { + let home_dir = my_home().ok().flatten().expect("Config find path error"); + home_dir.join(".config").join("bRAC").join("config.yml") + }; + + #[cfg(target_os = "macos")] + let config_path = { + let home_dir = my_home().ok().flatten().expect("Config find path error"); + home_dir.join(".config").join("bRAC").join("config.yml") + }; + + #[cfg(target_os = "windows")] + let config_path = { + let appdata = env::var("APPDATA").expect("Config find path error"); + Path::new(&appdata).join("bRAC").join("config.yml") + }; + + config_path +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8aea7b9..9e2f77f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,9 @@ use std::{ - error::Error, - io::{stdin, stdout, BufRead, Write}, - sync::{Arc, RwLock}, + error::Error, io::{stdin, stdout, BufRead, Write}, sync::{Arc, RwLock}, thread, time::{Duration, SystemTime, UNIX_EPOCH} }; use colored::Color; +use config::{get_config_path, load_config, Config}; use rac::{run_recv_loop, send_message}; use rand::random; use regex::Regex; @@ -12,15 +11,11 @@ use lazy_static::lazy_static; use term::run_main_loop; -const DEFAULT_HOST: &str = "meex.lol:11234"; const ADVERTISEMENT: &str = "\r\x1B[1A use bRAC client! https://github.com/MeexReay/bRAC \x1B[1B"; - -const MAX_MESSAGES: usize = 100; -const MAGIC_KEY: &str = "\u{B9AC}\u{3E70}"; const ADVERTISEMENT_ENABLED: bool = false; -const UPDATE_TIME: u64 = 50; +mod config; mod term; mod rac; @@ -28,7 +23,7 @@ mod rac; lazy_static! { static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap(); static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![ - (Regex::new(&format!(r"{}<(.*?)> (.*)", MAGIC_KEY)).unwrap(), Color::Green), + (Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green), (Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed), (Regex::new(r"(.*?): (.*)").unwrap(), Color::Magenta), (Regex::new(r"<(.*?)> (.*)").unwrap(), Color::Cyan), @@ -37,38 +32,32 @@ lazy_static! { +fn get_input(prompt: &str) -> Option { + let mut out = stdout().lock(); + out.write_all(prompt.as_bytes()).ok()?; + out.flush().ok()?; + let input = stdin().lock().lines().next() + .map(|o| o.ok()) + .flatten()?; -fn get_input(prompt: &str, default: &str) -> String { - let input = || -> Option { - let mut out = stdout().lock(); - out.write_all(prompt.as_bytes()).ok()?; - out.flush().ok()?; - stdin().lock().lines().next() - .map(|o| o.ok()) - .flatten() - }(); - - if let Some(input) = &input { - if input.is_empty() { - default - } else { - input - } + if input.is_empty() { + None } else { - default - }.to_string() + Some(input.to_string()) + } } -fn on_command(host: &str, command: &str) -> Result<(), Box> { + +fn on_command(config: Arc, host: &str, 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(MAX_MESSAGES))?; + send_message(host, &format!("\r\x1B[1A{}", " ".repeat(64)).repeat(config.max_messages))?; // *input.write().unwrap() = "/ заспамлено)))".to_string(); } else if command == "spam" { - send_message(host, &format!("\r\x1B[1A{}{}", args.join(" "), " ".repeat(10)).repeat(MAX_MESSAGES))?; + send_message(host, &format!("\r\x1B[1A{}{}", args.join(" "), " ".repeat(10)).repeat(config.max_messages))?; // *input.write().unwrap() = "/ заспамлено)))".to_string(); } else if command == "help" { write!(stdout(), "/clear - clear console; /spam *args - spam console with text; /help - show help message")?; @@ -79,13 +68,26 @@ fn on_command(host: &str, command: &str) -> Result<(), Box> { } fn main() { - let host = get_input(&format!("Host (default: {}) > ", DEFAULT_HOST), DEFAULT_HOST); - let anon_name = format!("Anon#{:X}", random::()); - let name = get_input(&format!("Name (default: {}) > ", anon_name), &anon_name); + let start_time = SystemTime::now(); + let config = load_config(get_config_path()); + + let name = match 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 messages = Arc::new(RwLock::new(String::new())); let input = Arc::new(RwLock::new(String::new())); + let config = Arc::new(config); - run_recv_loop(host.clone(), messages.clone(), input.clone()); - run_main_loop(messages.clone(), input.clone(), host.clone(), name.clone()); + 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()); + run_main_loop(config.clone(), messages.clone(), input.clone(), config.host.clone(), name.clone()); } diff --git a/src/rac.rs b/src/rac.rs index 106fcd2..6f4c5dd 100644 --- a/src/rac.rs +++ b/src/rac.rs @@ -1,6 +1,6 @@ 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}; +use crate::{config::Config, term::print_console, ADVERTISEMENT, ADVERTISEMENT_ENABLED}; pub fn send_message(host: &str, message: &str) -> Result<(), Box> { let mut stream = TcpStream::connect(host)?; @@ -67,23 +67,23 @@ fn read_messages(host: &str) -> Result> { Ok(packet_data) } -fn recv_loop(host: &str, cache: Arc>, input: Arc>) -> Result<(), Box> { +fn recv_loop(config: Arc, 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)); + print_console(config.clone(), &cache.read().unwrap(), &input.read().unwrap())?; + thread::sleep(Duration::from_millis(config.update_time as u64)); } Ok(()) } -pub fn run_recv_loop(host: String, messages: Arc>, input: Arc>) { +pub fn run_recv_loop(config: Arc, host: String, messages: Arc>, input: Arc>) { thread::spawn({ move || { - let _ = recv_loop(&host, messages, input); + let _ = recv_loop(config.clone(), &host, messages, input); println!("Connection closed"); } }); diff --git a/src/term.rs b/src/term.rs index 0a23032..d968dfc 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,22 +1,22 @@ -use std::{error::Error, io::{stdout, Write}, sync::{Arc, RwLock}, thread, time::Duration}; +use std::{error::Error, io::{stdout, Write}, sync::{Arc, RwLock}, 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}; +use crate::{config::Config, on_command, rac::send_message, ADVERTISEMENT, COLORED_USERNAMES, DATE_REGEX}; -pub fn print_console(messages: &str, input: &str) -> Result<(), Box> { +pub fn print_console(config: Arc, 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.truncate(config.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()), + "\n".repeat(config.max_messages - messages.len()), messages.join("\n"), // if sound { "\x07" } else { "" }, input @@ -80,7 +80,7 @@ fn find_username_color(message: &str) -> Option<(String, String, Color)> { None } -fn poll_events(input: Arc>, messages: Arc>, host: String, name: String) { +fn poll_events(config: Arc, input: Arc>, messages: Arc>, host: String, name: String) { loop { if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue } @@ -107,12 +107,14 @@ fn poll_events(input: Arc>, messages: Arc>, host: input.write().unwrap().clear(); if message.starts_with("/") { - on_command(&host, &message).expect("Error on command"); + on_command(config.clone(), &host, &message).expect("Error on command"); } else { - send_message(&host, &format!("{}<{}> {}", MAGIC_KEY, name, message)).expect("Error sending message"); + let message = config.message_format.replace("{name}", &name).replace("{text}", &message); + send_message(&host, &message).expect("Error sending message"); } } else { print_console( + config.clone(), &messages.read().unwrap(), &input.read().unwrap() ).expect("Error printing console"); @@ -147,7 +149,7 @@ fn poll_events(input: Arc>, messages: Arc>, host: } } -pub fn run_main_loop(messages: Arc>, input: Arc>, host: String, name: String) { +pub fn run_main_loop(config: Arc, messages: Arc>, input: Arc>, host: String, name: String) { enable_raw_mode().unwrap(); // thread::spawn({ @@ -165,5 +167,5 @@ pub fn run_main_loop(messages: Arc>, input: Arc>, // } // }); - poll_events(input.clone(), messages.clone(), host, name); + poll_events(config, input.clone(), messages.clone(), host, name); } \ No newline at end of file