This commit is contained in:
MeexReay 2025-02-10 13:04:37 +03:00
parent f23963c8b8
commit 46b4b84ed4
8 changed files with 314 additions and 59 deletions

183
Cargo.lock generated
View File

@ -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"

View File

@ -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"
crossterm = "0.28.1"
serde = { version = "1.0.217", features = ["serde_derive"] }
serde_yml = "0.0.12"
homedir = "0.3.4"

View File

@ -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

View File

@ -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

69
src/config.rs Normal file
View File

@ -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<String>,
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
}

View File

@ -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<String> {
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<String> {
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<dyn Error>> {
fn on_command(config: Arc<Config>, host: &str, command: &str) -> Result<(), Box<dyn Error>> {
let command = command.trim_start_matches("/");
let (command, args) = command.split_once(" ").unwrap_or((&command, ""));
let args = args.split(" ").collect::<Vec<&str>>();
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<dyn Error>> {
}
fn main() {
let host = get_input(&format!("Host (default: {}) > ", DEFAULT_HOST), DEFAULT_HOST);
let anon_name = format!("Anon#{:X}", random::<u16>());
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::<u16>());
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());
}

View File

@ -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<dyn Error>> {
let mut stream = TcpStream::connect(host)?;
@ -67,23 +67,23 @@ fn read_messages(host: &str) -> Result<String, Box<dyn Error>> {
Ok(packet_data)
}
fn recv_loop(host: &str, cache: Arc<RwLock<String>>, input: Arc<RwLock<String>>) -> Result<(), Box<dyn Error>> {
fn recv_loop(config: Arc<Config>, host: &str, cache: Arc<RwLock<String>>, input: Arc<RwLock<String>>) -> Result<(), Box<dyn Error>> {
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<RwLock<String>>, input: Arc<RwLock<String>>) {
pub fn run_recv_loop(config: Arc<Config>, host: String, messages: Arc<RwLock<String>>, input: Arc<RwLock<String>>) {
thread::spawn({
move || {
let _ = recv_loop(&host, messages, input);
let _ = recv_loop(config.clone(), &host, messages, input);
println!("Connection closed");
}
});

View File

@ -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<dyn Error>> {
pub fn print_console(config: Arc<Config>, messages: &str, input: &str) -> Result<(), Box<dyn Error>> {
let mut messages = messages.split("\n")
.map(|o| o.to_string())
.collect::<Vec<String>>();
messages.reverse();
messages.truncate(MAX_MESSAGES);
messages.truncate(config.max_messages);
messages.reverse();
let messages: Vec<String> = 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<RwLock<String>>, messages: Arc<RwLock<String>>, host: String, name: String) {
fn poll_events(config: Arc<Config>, input: Arc<RwLock<String>>, messages: Arc<RwLock<String>>, 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<RwLock<String>>, messages: Arc<RwLock<String>>, 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<RwLock<String>>, messages: Arc<RwLock<String>>, host:
}
}
pub fn run_main_loop(messages: Arc<RwLock<String>>, input: Arc<RwLock<String>>, host: String, name: String) {
pub fn run_main_loop(config: Arc<Config>, messages: Arc<RwLock<String>>, input: Arc<RwLock<String>>, host: String, name: String) {
enable_raw_mode().unwrap();
// thread::spawn({
@ -165,5 +167,5 @@ pub fn run_main_loop(messages: Arc<RwLock<String>>, input: Arc<RwLock<String>>,
// }
// });
poll_events(input.clone(), messages.clone(), host, name);
poll_events(config, input.clone(), messages.clone(), host, name);
}