mirror of
https://github.com/MeexReay/bRAC.git
synced 2025-05-06 13:38:04 +03:00
history and rac 2.0 small compatibility
This commit is contained in:
parent
0e850f79c7
commit
8d671c7e86
255
src/chat.rs
255
src/chat.rs
@ -1,10 +1,42 @@
|
||||
use std::{error::Error, io::{stdout, Write}, sync::{atomic::Ordering, Arc}, time::{Duration, SystemTime}};
|
||||
use std::{cmp::{max, min}, error::Error, io::{stdout, Write}, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, thread, time::{Duration, SystemTime}};
|
||||
|
||||
use clap::builder::Str;
|
||||
use colored::{Color, Colorize};
|
||||
use crossterm::{cursor::MoveLeft, event::{self, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode}, ExecutableCommand};
|
||||
use crossterm::{cursor::MoveLeft, event::{self, Event, KeyCode, KeyModifiers, MouseEventKind}, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
use rand::random;
|
||||
|
||||
use super::{proto::read_messages, util::sanitize_text, ADVERTISEMENT, COLORED_USERNAMES, DATE_REGEX, config::Context, proto::send_message};
|
||||
use crate::IP_REGEX;
|
||||
|
||||
use super::{proto::read_messages, util::sanitize_text, COLORED_USERNAMES, DATE_REGEX, config::Context, proto::send_message};
|
||||
|
||||
|
||||
pub struct ChatStorage {
|
||||
messages: RwLock<Vec<String>>,
|
||||
packet_size: AtomicUsize
|
||||
}
|
||||
|
||||
impl ChatStorage {
|
||||
pub fn new() -> Self {
|
||||
ChatStorage {
|
||||
messages: RwLock::new(Vec::new()),
|
||||
packet_size: AtomicUsize::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn packet_size(&self) -> usize {
|
||||
self.packet_size.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn messages(&self) -> Vec<String> {
|
||||
self.messages.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn update(&self, messages: Vec<String>, packet_size: usize) {
|
||||
self.packet_size.store(packet_size, Ordering::SeqCst);
|
||||
*self.messages.write().unwrap() = messages;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
|
||||
let command = command.trim_start_matches("/");
|
||||
@ -12,9 +44,15 @@ fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
|
||||
let args = args.split(" ").collect::<Vec<&str>>();
|
||||
|
||||
if command == "clear" {
|
||||
send_message(ctx.clone(), &format!("\r\x1B[1A{}", " ".repeat(64)).repeat(ctx.max_messages))?;
|
||||
send_message(&ctx.host,
|
||||
&prepare_message(ctx.clone(),
|
||||
&format!("\r\x1B[1A{}", " ".repeat(64)).repeat(ctx.max_messages)
|
||||
))?;
|
||||
} else if command == "spam" {
|
||||
send_message(ctx.clone(), &format!("\r\x1B[1A{}{}", args.join(" "), " ".repeat(10)).repeat(ctx.max_messages))?;
|
||||
send_message(&ctx.host,
|
||||
&prepare_message(ctx.clone(),
|
||||
&format!("\r\x1B[1A{}{}", args.join(" "), " ".repeat(10)).repeat(ctx.max_messages)
|
||||
))?;
|
||||
} else if command == "help" {
|
||||
write!(stdout(), "Help message:\r
|
||||
/help - show help message\r
|
||||
@ -25,12 +63,12 @@ fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
|
||||
Press enter to close")?;
|
||||
stdout().flush()?;
|
||||
} else if command == "ping" {
|
||||
let mut before = ctx.messages.1.load(Ordering::SeqCst);
|
||||
let mut before = ctx.messages.packet_size();
|
||||
let start = SystemTime::now();
|
||||
let message = format!("Checking ping... {:X}", random::<u16>());
|
||||
send_message(ctx.clone(), &message)?;
|
||||
send_message(&ctx.host, &message)?;
|
||||
loop {
|
||||
let data = read_messages(ctx.clone(), before).ok().flatten();
|
||||
let data = read_messages(&ctx.host, ctx.max_messages, before).ok().flatten();
|
||||
|
||||
if let Some((data, size)) = data {
|
||||
if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) {
|
||||
@ -44,49 +82,67 @@ Press enter to close")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
send_message(ctx.clone(), &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?;
|
||||
send_message(&ctx.host, &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn print_console(context: Arc<Context>, messages: Vec<String>, input: &str) -> Result<(), Box<dyn Error>> {
|
||||
let text = format!(
|
||||
"{}{}\n> {}",
|
||||
"\n".repeat(context.max_messages - messages.len()),
|
||||
if context.disable_formatting {
|
||||
messages
|
||||
} else {
|
||||
messages.into_iter().filter_map(|o| format_message(context.clone(), o)).collect()
|
||||
}.join("\n"),
|
||||
"{}{}\r\n> {}",
|
||||
"\r\n".repeat(context.max_messages - messages.len()),
|
||||
messages.join("\r\n"),
|
||||
input
|
||||
);
|
||||
for line in text.lines() {
|
||||
write!(stdout().lock(), "\r\n{}", line)?;
|
||||
stdout().lock().flush()?;
|
||||
}
|
||||
|
||||
let mut out = stdout().lock();
|
||||
write!(out, "{}", text)?;
|
||||
out.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn prepare_message(context: Arc<Context>, message: &str) -> String {
|
||||
format!("{}{}{}",
|
||||
if !context.disable_hiding_ip {
|
||||
"\r\x07"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
message,
|
||||
if !context.disable_hiding_ip && message.chars().count() < 39 {
|
||||
" ".repeat(39-message.chars().count())
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn format_message(ctx: Arc<Context>, message: String) -> Option<String> {
|
||||
let message = message.trim_end_matches(ADVERTISEMENT);
|
||||
let message = sanitize_text(&message);
|
||||
if ADVERTISEMENT.len() > 0 &&
|
||||
message.starts_with(ADVERTISEMENT
|
||||
.trim_start_matches("\r")
|
||||
.trim_start_matches("\n")) {
|
||||
return None
|
||||
}
|
||||
|
||||
let date = DATE_REGEX.captures(&message)?;
|
||||
let (date, ip, message) = (
|
||||
let (date, message) = (
|
||||
date.get(1)?.as_str().to_string(),
|
||||
date.get(2)?.as_str().to_string(),
|
||||
date.get(3)?.as_str().to_string()
|
||||
);
|
||||
|
||||
let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) {
|
||||
(Some(message.get(1)?.as_str().to_string()), message.get(2)?.as_str().to_string())
|
||||
} else {
|
||||
(None, message)
|
||||
};
|
||||
|
||||
let prefix = if ctx.enable_ip_viewing {
|
||||
format!("{}{} [{}]", ip, " ".repeat(15-ip.len()), date)
|
||||
if let Some(ip) = ip {
|
||||
format!("{}{} [{}]", ip, " ".repeat(15-ip.len()), date)
|
||||
} else {
|
||||
format!("{} [{}]", " ".repeat(15), date)
|
||||
}
|
||||
} else {
|
||||
format!("[{}]", date)
|
||||
};
|
||||
@ -111,6 +167,7 @@ fn format_message(ctx: Arc<Context>, message: String) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
||||
for (re, color) in COLORED_USERNAMES.iter() {
|
||||
if let Some(captures) = re.captures(message) {
|
||||
@ -120,7 +177,36 @@ fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn poll_events(ctx: Arc<Context>) {
|
||||
|
||||
fn write_backspace(len: usize) {
|
||||
write_backspace_with_text(len, "")
|
||||
}
|
||||
|
||||
fn write_backspace_with_text(len: usize, text: &str) {
|
||||
let spaces = if text.chars().count() < len {
|
||||
len-text.chars().count()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
write!(stdout(),
|
||||
"{}{}{}{}",
|
||||
MoveLeft(1).to_string().repeat(len),
|
||||
text,
|
||||
" ".repeat(spaces),
|
||||
MoveLeft(1).to_string().repeat(spaces)
|
||||
).unwrap();
|
||||
stdout().lock().flush().unwrap();
|
||||
}
|
||||
|
||||
|
||||
fn poll_events(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
||||
let mut history: Vec<String> = vec![String::new()];
|
||||
let mut history_cursor: usize = 0;
|
||||
let mut cursor: usize = 0;
|
||||
|
||||
let input = ctx.input.clone();
|
||||
let messages = ctx.messages.clone();
|
||||
|
||||
loop {
|
||||
if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue }
|
||||
|
||||
@ -133,66 +219,70 @@ fn poll_events(ctx: Arc<Context>) {
|
||||
Event::Key(event) => {
|
||||
match event.code {
|
||||
KeyCode::Enter => {
|
||||
let message = ctx.input.read().unwrap().clone();
|
||||
let message = input.read().unwrap().clone();
|
||||
|
||||
if !message.is_empty() {
|
||||
let input_len = ctx.input.read().unwrap().chars().count();
|
||||
write!(stdout(),
|
||||
"{}{}{}",
|
||||
MoveLeft(1).to_string().repeat(input_len),
|
||||
" ".repeat(input_len),
|
||||
MoveLeft(1).to_string().repeat(input_len)
|
||||
).unwrap();
|
||||
stdout().lock().flush().unwrap();
|
||||
ctx.input.write().unwrap().clear();
|
||||
write_backspace(message.chars().count());
|
||||
input.write().unwrap().clear();
|
||||
|
||||
history.insert(history_cursor, message.clone());
|
||||
history_cursor += 1;
|
||||
|
||||
if message.starts_with("/") && !ctx.disable_commands {
|
||||
on_command(ctx.clone(), &message).expect("Error on command");
|
||||
on_command(ctx.clone(), &message)?;
|
||||
} else {
|
||||
let message = ctx.message_format.replace("{name}", &ctx.name).replace("{text}", &message);
|
||||
send_message(ctx.clone(), &message).expect("Error sending message");
|
||||
let message = ctx.message_format
|
||||
.replace("{name}", &ctx.name)
|
||||
.replace("{text}", &message);
|
||||
send_message(&ctx.host, &message)?;
|
||||
}
|
||||
} else {
|
||||
print_console(
|
||||
ctx.clone(),
|
||||
ctx.messages.0.read().unwrap().clone(),
|
||||
&ctx.input.read().unwrap()
|
||||
).expect("Error printing console");
|
||||
messages.messages(),
|
||||
""
|
||||
)?;
|
||||
}
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
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();
|
||||
if input.write().unwrap().pop().is_some() {
|
||||
history[history_cursor].pop();
|
||||
write_backspace(1);
|
||||
}
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
disable_raw_mode().unwrap();
|
||||
disable_raw_mode()?;
|
||||
break;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
}
|
||||
KeyCode::Down => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
KeyCode::Up | KeyCode::Down => {
|
||||
history_cursor = if event.code == KeyCode::Up {
|
||||
max(history_cursor + 1, 1) - 1
|
||||
} else {
|
||||
min(history_cursor + 1, history.len() - 1)
|
||||
};
|
||||
let was_len = input.read().unwrap().chars().count();
|
||||
*input.write().unwrap() = history[history_cursor].clone();
|
||||
write_backspace_with_text(was_len, &history[history_cursor]);
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
|
||||
}
|
||||
KeyCode::Left => {
|
||||
|
||||
}
|
||||
KeyCode::Right => {
|
||||
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
}
|
||||
ctx.input.write().unwrap().push(c);
|
||||
history[history_cursor].push(c);
|
||||
input.write().unwrap().push(c);
|
||||
write!(stdout(), "{}", c).unwrap();
|
||||
stdout().lock().flush().unwrap();
|
||||
}
|
||||
@ -200,19 +290,50 @@ fn poll_events(ctx: Arc<Context>) {
|
||||
}
|
||||
},
|
||||
Event::Paste(data) => {
|
||||
ctx.input.write().unwrap().push_str(&data);
|
||||
input.write().unwrap().push_str(&data);
|
||||
write!(stdout(), "{}", &data).unwrap();
|
||||
stdout().lock().flush().unwrap();
|
||||
},
|
||||
Event::Mouse(data) => {
|
||||
|
||||
match data.kind {
|
||||
MouseEventKind::ScrollUp => {
|
||||
|
||||
},
|
||||
MouseEventKind::ScrollDown => {
|
||||
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
||||
if let Ok(Some((messages, size))) = read_messages(&ctx.host, ctx.max_messages, ctx.messages.packet_size()) {
|
||||
let messages: Vec<String> = messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect();
|
||||
ctx.messages.update(messages.clone(), size);
|
||||
print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(ctx.update_time as u64));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_main_loop(ctx: Arc<Context>) {
|
||||
enable_raw_mode().unwrap();
|
||||
poll_events(ctx);
|
||||
|
||||
thread::spawn({
|
||||
let ctx = ctx.clone();
|
||||
|
||||
move || {
|
||||
loop {
|
||||
recv_tick(ctx.clone()).expect("Error printing console");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
poll_events(ctx).expect("Error while polling events");
|
||||
}
|
@ -6,6 +6,8 @@ use rand::random;
|
||||
use serde_yml;
|
||||
use clap::Parser;
|
||||
|
||||
use crate::chat::ChatStorage;
|
||||
|
||||
use super::util::get_input;
|
||||
|
||||
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
|
||||
@ -170,7 +172,7 @@ pub struct Args {
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
pub messages: Arc<(RwLock<Vec<String>>, AtomicUsize)>,
|
||||
pub messages: Arc<ChatStorage>,
|
||||
pub input: Arc<RwLock<String>>,
|
||||
pub host: String,
|
||||
pub name: String,
|
||||
@ -187,7 +189,7 @@ pub struct Context {
|
||||
impl Context {
|
||||
pub fn new(config: &Config, args: &Args) -> Context {
|
||||
Context {
|
||||
messages: Arc::new((RwLock::new(Vec::new()), AtomicUsize::new(0))),
|
||||
messages: Arc::new(ChatStorage::new()),
|
||||
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()),
|
||||
|
15
src/main.rs
15
src/main.rs
@ -3,17 +3,15 @@ use std::sync::Arc;
|
||||
use clap::Parser;
|
||||
use colored::Color;
|
||||
use config::{configure, get_config_path, load_config, Args, Context};
|
||||
use proto::{read_messages, run_recv_loop, send_message};
|
||||
use proto::{read_messages, send_message};
|
||||
use regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
use chat::run_main_loop;
|
||||
|
||||
|
||||
const ADVERTISEMENT: &str = "\r\x1B[1A use bRAC client! https://github.com/MeexReay/bRAC \x1B[1B";
|
||||
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 IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
|
||||
static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![
|
||||
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green), // bRAC
|
||||
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed), // CRAB
|
||||
@ -31,7 +29,7 @@ mod util;
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
|
||||
let config_path = get_config_path();
|
||||
|
||||
if args.config_path {
|
||||
@ -49,17 +47,16 @@ fn main() {
|
||||
let ctx = Arc::new(Context::new(&config, &args));
|
||||
|
||||
if args.read_messages {
|
||||
print!("{}", read_messages(ctx.clone(), 0).ok().flatten().expect("Error reading messages").0.join("\n"));
|
||||
print!("{}", read_messages(&ctx.host, ctx.max_messages, 0).ok().flatten().expect("Error reading messages").0.join("\n"));
|
||||
}
|
||||
|
||||
if let Some(message) = &args.send_message {
|
||||
send_message(ctx.clone(), message).expect("Error sending message");
|
||||
send_message(&ctx.host, message).expect("Error sending message");
|
||||
}
|
||||
|
||||
if args.send_message.is_some() || args.read_messages {
|
||||
return;
|
||||
}
|
||||
|
||||
run_recv_loop(ctx.clone());
|
||||
run_main_loop(ctx.clone());
|
||||
}
|
||||
|
51
src/proto.rs
51
src/proto.rs
@ -1,25 +1,13 @@
|
||||
use std::{error::Error, io::{Read, Write}, net::TcpStream, sync::{atomic::Ordering, Arc}, thread, time::Duration};
|
||||
use std::{error::Error, io::{Read, Write}, net::TcpStream, sync::Arc, thread, time::Duration};
|
||||
|
||||
use super::{chat::print_console, Context, ADVERTISEMENT, ADVERTISEMENT_ENABLED};
|
||||
use crate::config::Context;
|
||||
|
||||
pub fn send_message(context: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(&context.host)?;
|
||||
use super::chat::print_console;
|
||||
|
||||
pub fn send_message(host: &str, message: &str) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(host)?;
|
||||
stream.write_all(&[0x01])?;
|
||||
let data = format!("{}{}{}{}",
|
||||
if !context.disable_hiding_ip {
|
||||
"\r\x07"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
message,
|
||||
if !context.disable_hiding_ip && message.chars().count() < 39 {
|
||||
" ".repeat(39-message.chars().count())
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
if ADVERTISEMENT_ENABLED {ADVERTISEMENT} else {""}
|
||||
);
|
||||
stream.write_all(data.as_bytes())?;
|
||||
stream.write_all(message.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -33,8 +21,8 @@ fn skip_null(stream: &mut TcpStream) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_messages(context: Arc<Context>, last_size: usize) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(&context.host)?;
|
||||
pub fn read_messages(host: &str, max_messages: usize, last_size: usize) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(host)?;
|
||||
|
||||
stream.write_all(&[0x00])?;
|
||||
|
||||
@ -75,28 +63,9 @@ pub fn read_messages(context: Arc<Context>, last_size: usize) -> Result<Option<(
|
||||
|
||||
let lines: Vec<&str> = packet_data.split("\n").collect();
|
||||
let lines: Vec<String> = lines.clone().into_iter()
|
||||
.skip(lines.len() - context.max_messages)
|
||||
.skip(lines.len() - max_messages)
|
||||
.map(|o| o.to_string())
|
||||
.collect();
|
||||
|
||||
Ok(Some((lines, packet_size)))
|
||||
}
|
||||
|
||||
pub fn run_recv_loop(context: Arc<Context>) {
|
||||
thread::spawn({
|
||||
let cache = context.messages.clone();
|
||||
let update_time = context.update_time;
|
||||
let input = context.input.clone();
|
||||
|
||||
move || {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user