advertisement, update_time, colored_usernames refactor, /clear command

This commit is contained in:
MeexReay 2025-02-09 20:05:49 +03:00
parent 8c3517a8ac
commit ebf740aed5
5 changed files with 95 additions and 72 deletions

7
Cargo.lock generated
View File

@ -16,6 +16,7 @@ name = "bRAC"
version = "1.99.2" version = "1.99.2"
dependencies = [ dependencies = [
"colored", "colored",
"lazy_static",
"rand", "rand",
"regex", "regex",
"termion", "termion",
@ -60,6 +61,12 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.169"

View File

@ -8,3 +8,4 @@ rand = "0.9.0"
termion = "4.0.3" termion = "4.0.3"
regex = "1.11.1" regex = "1.11.1"
colored = "3.0.0" colored = "3.0.0"
lazy_static = "1.5.0"

View File

@ -26,27 +26,27 @@ cargo run # run
#### bRAC #### bRAC
regex - `\[(.*?)\] \uB9AC\u3E70<(.*?)> (.*)` \ regex - `\uB9AC\u3E70<(.*?)> (.*)` \
color - green \ color - green \
example - `[date] 리㹰<nick> text` example - `리㹰<nick> text`
#### CRAB #### CRAB
regex - `\[(.*?)\] \u2550\u2550\u2550<(.*?)> (.*)` \ regex - `\u2550\u2550\u2550<(.*?)> (.*)` \
color - light red \ color - light red \
example - `[date] ═══<nick> text` example - `═══<nick> text`
#### Mefedroniy #### Mefedroniy
regex - `\[(.*?)\] (.*?): (.*)` \ regex - `(.*?): (.*)` \
color - light magenta \ color - light magenta \
example - `[date] nick: text` example - `nick: text`
#### clRAC #### clRAC
regex - `\[(.*?)\] <(.*?)> (.*)` \ regex - `<(.*?)> (.*)` \
color - cyan \ color - cyan \
example - `[date] <nick> text` example - `<nick> text`
## see also ## see also

4
config.yml Normal file
View File

@ -0,0 +1,4 @@
host: meex.lol:11234
name: null
magic_key: "\uB9AC\u3E70"
ad: "\r\x1B[1A use bRAC client! https://github.com/MeexReay/bRAC \x1B[1B"

View File

@ -1,29 +1,46 @@
use std::{ use std::{
error::Error, io::{stdin, stdout, BufRead, Read, Write}, net::TcpStream, sync::{Arc, RwLock}, thread collections::HashMap, error::Error, io::{stdin, stdout, BufRead, Read, Write}, net::TcpStream, sync::{Arc, RwLock}, thread, time::Duration
}; };
use colored::Colorize; use colored::{Color, Colorize};
use rand::random; use rand::random;
use regex::Regex; use regex::Regex;
use termion::{event::Key, input::TermRead, raw::IntoRawMode}; use termion::{event::Key, input::TermRead, raw::IntoRawMode};
use lazy_static::lazy_static;
const MAX_MESSAGES: usize = 100; const MAX_MESSAGES: usize = 100;
const DEFAULT_HOST: &str = "meex.lol:11234"; const DEFAULT_HOST: &str = "meex.lol:11234";
const MAGIC_KEY: &str = "\u{B9AC}\u{3E70}"; const MAGIC_KEY: &str = "\u{B9AC}\u{3E70}";
// const ADVERTISEMENT: &str = "\r\x1B[1A use bRAC client! https://github.com/MeexReay/bRAC \x1B[1B";
const ADVERTISEMENT: &str = "";
const UPDATE_TIME: u64 = 50;
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{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed),
(Regex::new(r"(.*?): (.*)").unwrap(), Color::Magenta),
(Regex::new(r"<(.*?)> (.*)").unwrap(), Color::Cyan),
];
}
fn send_message(host: &str, message: &str) -> Result<(), Box<dyn Error>> { fn send_message(host: &str, message: &str) -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect(host)?; let mut stream = TcpStream::connect(host)?;
stream.write_all(&[0x01])?; stream.write_all(&[0x01])?;
let data = format!("\r\x07{}{}", let data = format!("\r\x07{}{}{}",
message, message,
if message.chars().count() < 39 { if message.chars().count() < 39 {
" ".repeat(39-message.chars().count()) " ".repeat(39-message.chars().count())
} else { } else {
String::new() String::new()
} },
ADVERTISEMENT
); );
stream.write_all(data.as_bytes())?; stream.write_all(data.as_bytes())?;
stream.write_all("\0".repeat(1023 - data.len()).as_bytes())?;
Ok(()) Ok(())
} }
@ -83,7 +100,8 @@ fn recv_loop(host: &str, cache: Arc<RwLock<String>>, input: Arc<RwLock<String>>)
} }
*cache.write().unwrap() = data; *cache.write().unwrap() = data;
print_console(&cache.read().unwrap(), &input.read().unwrap(), true)?; print_console(&cache.read().unwrap(), &input.read().unwrap())?;
thread::sleep(Duration::from_millis(UPDATE_TIME));
} }
Ok(()) Ok(())
} }
@ -117,82 +135,71 @@ fn sanitize_text(input: &str) -> String {
cleaned_text.into_owned() cleaned_text.into_owned()
} }
fn on_message(message: String) -> String { /// 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<String> {
let message = message.trim_end_matches(ADVERTISEMENT);
let message = Regex::new(r"\{[^}]*\}\ ").unwrap().replace(&message, "").to_string(); let message = Regex::new(r"\{[^}]*\}\ ").unwrap().replace(&message, "").to_string();
let message = sanitize_text(&message); let message = sanitize_text(&message);
if message.starts_with(ADVERTISEMENT
.trim_start_matches("\r")
.trim_start_matches("\n")) {
return None
}
if let Some(captures) = Regex::new(r"\[(.*?)\] <(.*?)> (.*)").unwrap().captures(&message) { let date = DATE_REGEX.captures(&message)?;
let date = &captures[1]; let (date, message) = (date.get(1)?.as_str().to_string(), date.get(2)?.as_str().to_string());
let nick = &captures[2];
let content = &captures[3]; 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(), format!("[{}]", date).white().dimmed(),
format!("<{}>", nick).cyan().bold(), format!("<{}>", nick).color(color).bold(),
content.white().blink()
)
} else if let Some(captures) = Regex::new(&format!(r"\[(.*?)\] {}<(.*?)> (.*)", MAGIC_KEY)).unwrap().captures(&message) {
let date = &captures[1];
let nick = &captures[2];
let content = &captures[3];
format!(
"{} {} {}",
format!("[{}]", date).white().dimmed(),
format!("<{}>", nick).green().bold(),
content.white().blink()
)
} else if let Some(captures) = Regex::new(r"\[(.*?)\] (.*?): (.*)").unwrap().captures(&message) {
let date = &captures[1];
let nick = &captures[2];
let content = &captures[3];
format!(
"{} {} {}",
format!("[{}]", date).white().dimmed(),
format!("<{}>", nick).magenta().bold(),
content.white().blink()
)
} else if let Some(captures) = Regex::new(r"\[(.*?)\] \u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap().captures(&message) {
let date = &captures[1];
let nick = &captures[2];
let content = &captures[3];
format!(
"{} {} {}",
format!("[{}]", date).white().dimmed(),
format!("<{}>", nick).bright_red().bold(),
content.white().blink()
)
} else if let Some(captures) = Regex::new(r"\[(.*?)\] (.*)").unwrap().captures(&message) {
let date = &captures[1];
let content = &captures[2];
format!(
"{} {}",
format!("[{}]", date).white().dimmed(),
content.white().blink() content.white().blink()
) )
} else { } else {
message.to_string() format!(
} "{} {}",
format!("[{}]", date).white().dimmed(),
message.white().blink()
)
})
} }
fn print_console(messages: &str, input: &str, sound: bool) -> Result<(), Box<dyn Error>> { fn on_command(host: &str, command: &str) -> Result<(), Box<dyn Error>> {
if command == "/clear" {
send_message(host, &"\n".repeat(MAX_MESSAGES))?;
}
Ok(())
}
fn print_console(messages: &str, input: &str) -> Result<(), Box<dyn Error>> {
let mut messages = messages.split("\n") let mut messages = messages.split("\n")
.map(|o| o.to_string()) .map(|o| o.to_string())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
messages.reverse(); messages.reverse();
messages.truncate(MAX_MESSAGES); messages.truncate(MAX_MESSAGES);
messages.reverse(); messages.reverse();
let messages: Vec<String> = messages.into_iter().map(on_message).collect(); let messages: Vec<String> = messages.into_iter().filter_map(format_message).collect();
let mut out = stdout().into_raw_mode()?; let mut out = stdout().into_raw_mode()?;
let text = format!( let text = format!(
"{}{}\n{}> {}", "{}{}\n> {}",
"\n".repeat(MAX_MESSAGES - messages.len()), "\n".repeat(MAX_MESSAGES - messages.len()),
messages.join("\n"), messages.join("\n"),
if sound { "\x07" } else { "" }, // if sound { "\x07" } else { "" },
input input
); );
for line in text.lines() { for line in text.lines() {
@ -229,10 +236,14 @@ fn main() {
Key::Char('\n') => { Key::Char('\n') => {
let message = input.read().unwrap().clone(); let message = input.read().unwrap().clone();
if !message.is_empty() { if !message.is_empty() {
send_message(&host, &format!("{}<{}> {}", MAGIC_KEY, name, message)).expect("Error sending message"); if message.starts_with("/") {
input.write().unwrap().clear(); on_command(&host, &message).expect("Error on command");
} else {
send_message(&host, &format!("{}<{}> {}", MAGIC_KEY, name, message)).expect("Error sending message");
input.write().unwrap().clear();
}
} }
print_console(&messages.read().unwrap(), &input.read().unwrap(), false).expect("Error printing console"); print_console(&messages.read().unwrap(), &input.read().unwrap()).expect("Error printing console");
} }
Key::Backspace => { Key::Backspace => {
input.write().unwrap().pop(); input.write().unwrap().pop();