mirror of
https://github.com/MeexReay/bRAC.git
synced 2025-05-06 13:38:04 +03:00
refactoring
This commit is contained in:
parent
debf578019
commit
5290d2d2e2
16
build-all.sh
16
build-all.sh
@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
TARGETS=(
|
|
||||||
x86_64-unknown-linux-gnu
|
|
||||||
i686-unknown-linux-gnu
|
|
||||||
x86_64-pc-windows-gnu
|
|
||||||
i686-pc-windows-gnu
|
|
||||||
x86_64-apple-darwin
|
|
||||||
aarch64-apple-darwin
|
|
||||||
)
|
|
||||||
|
|
||||||
for TARGET in "${TARGETS[@]}"; do
|
|
||||||
cargo build --release --target "$TARGET"
|
|
||||||
done
|
|
13
shell.nix
13
shell.nix
@ -1,13 +0,0 @@
|
|||||||
{ pkgs ? import <nixpkgs> {} }:
|
|
||||||
pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
rustup
|
|
||||||
gcc_multi
|
|
||||||
pkg-config
|
|
||||||
zlib
|
|
||||||
openssl
|
|
||||||
pkgsCross.gnu32.buildPackages.gcc
|
|
||||||
pkgsCross.mingw32.buildPackages.gcc
|
|
||||||
pkgsCross.mingwW64.buildPackages.gcc
|
|
||||||
];
|
|
||||||
}
|
|
252
src/main.rs
252
src/main.rs
@ -1,22 +1,15 @@
|
|||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{stdin, stdout, BufRead, Read, Write},
|
io::{stdin, stdout, BufRead, Write},
|
||||||
net::TcpStream,
|
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
thread,
|
|
||||||
time::Duration
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use colored::{Color, Colorize};
|
use colored::Color;
|
||||||
use crossterm::{
|
use rac::{run_recv_loop, send_message};
|
||||||
cursor::MoveLeft,
|
|
||||||
event::{self, Event, KeyCode},
|
|
||||||
terminal::{disable_raw_mode, enable_raw_mode},
|
|
||||||
ExecutableCommand
|
|
||||||
};
|
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use term::run_main_loop;
|
||||||
|
|
||||||
|
|
||||||
const DEFAULT_HOST: &str = "meex.lol:11234";
|
const DEFAULT_HOST: &str = "meex.lol:11234";
|
||||||
@ -28,6 +21,10 @@ const ADVERTISEMENT_ENABLED: bool = false;
|
|||||||
const UPDATE_TIME: u64 = 50;
|
const UPDATE_TIME: u64 = 50;
|
||||||
|
|
||||||
|
|
||||||
|
mod term;
|
||||||
|
mod rac;
|
||||||
|
|
||||||
|
|
||||||
lazy_static! {
|
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![
|
static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![
|
||||||
@ -39,83 +36,7 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn send_message(host: &str, message: &str) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut stream = TcpStream::connect(host)?;
|
|
||||||
stream.write_all(&[0x01])?;
|
|
||||||
let data = format!("\r\x07{}{}{}",
|
|
||||||
message,
|
|
||||||
if message.chars().count() < 39 {
|
|
||||||
" ".repeat(39-message.chars().count())
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
},
|
|
||||||
if ADVERTISEMENT_ENABLED {ADVERTISEMENT} else {""}
|
|
||||||
);
|
|
||||||
stream.write_all(data.as_bytes())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skip_null(stream: &mut TcpStream) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
||||||
loop {
|
|
||||||
let mut buf = vec![0; 1];
|
|
||||||
stream.read_exact(&mut buf)?;
|
|
||||||
if buf[0] != 0 {
|
|
||||||
break Ok(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_messages(host: &str) -> Result<String, Box<dyn Error>> {
|
|
||||||
let mut stream = TcpStream::connect(host)?;
|
|
||||||
|
|
||||||
stream.write_all(&[0x00])?;
|
|
||||||
|
|
||||||
let packet_size = {
|
|
||||||
let mut data = skip_null(&mut stream)?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut buf = vec![0; 1];
|
|
||||||
stream.read_exact(&mut buf)?;
|
|
||||||
let ch = buf[0];
|
|
||||||
if ch == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data.push(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
String::from_utf8(data)?
|
|
||||||
.trim_matches(char::from(0))
|
|
||||||
.parse()?
|
|
||||||
};
|
|
||||||
|
|
||||||
stream.write_all(&[0x01])?;
|
|
||||||
|
|
||||||
let packet_data = {
|
|
||||||
let mut data = skip_null(&mut stream)?;
|
|
||||||
while data.len() < packet_size {
|
|
||||||
let mut buf = vec![0; packet_size - data.len()];
|
|
||||||
let read_bytes = stream.read(&mut buf)?;
|
|
||||||
buf.truncate(read_bytes);
|
|
||||||
data.append(&mut buf);
|
|
||||||
}
|
|
||||||
String::from_utf8_lossy(&data).to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(packet_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recv_loop(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));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_input(prompt: &str, default: &str) -> String {
|
fn get_input(prompt: &str, default: &str) -> String {
|
||||||
let input = || -> Option<String> {
|
let input = || -> Option<String> {
|
||||||
@ -138,58 +59,6 @@ fn get_input(prompt: &str, default: &str) -> String {
|
|||||||
}.to_string()
|
}.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sanitize_text(input: &str) -> String {
|
|
||||||
let ansi_regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap();
|
|
||||||
let control_chars_regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap();
|
|
||||||
let without_ansi = ansi_regex.replace_all(input, "");
|
|
||||||
let cleaned_text = control_chars_regex.replace_all(&without_ansi, "");
|
|
||||||
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) {
|
|
||||||
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 = 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, message) = (date.get(1)?.as_str().to_string(), date.get(2)?.as_str().to_string());
|
|
||||||
|
|
||||||
Some(if let Some(captures) = find_username_color(&message) {
|
|
||||||
let nick = captures.0;
|
|
||||||
let content = captures.1;
|
|
||||||
let color = captures.2;
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"{} {} {}",
|
|
||||||
format!("[{}]", date).white().dimmed(),
|
|
||||||
format!("<{}>", nick).color(color).bold(),
|
|
||||||
content.white().blink()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"{} {}",
|
|
||||||
format!("[{}]", date).white().dimmed(),
|
|
||||||
message.white().blink()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_command(host: &str, command: &str) -> Result<(), Box<dyn Error>> {
|
fn on_command(host: &str, command: &str) -> Result<(), Box<dyn Error>> {
|
||||||
let command = command.trim_start_matches("/");
|
let command = command.trim_start_matches("/");
|
||||||
let (command, args) = command.split_once(" ").unwrap_or((&command, ""));
|
let (command, args) = command.split_once(" ").unwrap_or((&command, ""));
|
||||||
@ -206,28 +75,6 @@ fn on_command(host: &str, command: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_console(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.reverse();
|
|
||||||
let messages: Vec<String> = messages.into_iter().filter_map(format_message).collect();
|
|
||||||
let text = format!(
|
|
||||||
"{}{}\n> {}",
|
|
||||||
"\n".repeat(MAX_MESSAGES - messages.len()),
|
|
||||||
messages.join("\n"),
|
|
||||||
// if sound { "\x07" } else { "" },
|
|
||||||
input
|
|
||||||
);
|
|
||||||
for line in text.lines() {
|
|
||||||
write!(stdout().lock(), "\r\n{}", line)?;
|
|
||||||
stdout().lock().flush()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let host = get_input(&format!("Host (default: {}) > ", DEFAULT_HOST), DEFAULT_HOST);
|
let host = get_input(&format!("Host (default: {}) > ", DEFAULT_HOST), DEFAULT_HOST);
|
||||||
let anon_name = format!("Anon#{:X}", random::<u16>());
|
let anon_name = format!("Anon#{:X}", random::<u16>());
|
||||||
@ -236,85 +83,6 @@ fn main() {
|
|||||||
let messages = Arc::new(RwLock::new(String::new()));
|
let messages = Arc::new(RwLock::new(String::new()));
|
||||||
let input = Arc::new(RwLock::new(String::new()));
|
let input = Arc::new(RwLock::new(String::new()));
|
||||||
|
|
||||||
enable_raw_mode().unwrap();
|
run_recv_loop(host.clone(), messages.clone(), input.clone());
|
||||||
|
run_main_loop(messages.clone(), input.clone(), host.clone(), name.clone());
|
||||||
thread::spawn({
|
|
||||||
let host = host.clone();
|
|
||||||
let messages = messages.clone();
|
|
||||||
let input = input.clone();
|
|
||||||
|
|
||||||
move || {
|
|
||||||
let _ = recv_loop(&host, messages, input);
|
|
||||||
println!("Connection closed");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
thread::spawn({
|
|
||||||
let messages = messages.clone();
|
|
||||||
let input = input.clone();
|
|
||||||
|
|
||||||
move || {
|
|
||||||
print_console(
|
|
||||||
&messages.read().unwrap(),
|
|
||||||
&input.read().unwrap()
|
|
||||||
).expect("Error printing console");
|
|
||||||
thread::sleep(Duration::from_millis(UPDATE_TIME));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue }
|
|
||||||
|
|
||||||
let event = match event::read() {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(_) => { continue },
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::Key(event) => {
|
|
||||||
match event.code {
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let message = input.read().unwrap().clone();
|
|
||||||
|
|
||||||
let input_len = input.read().unwrap().len();
|
|
||||||
stdout().lock().execute(MoveLeft(input_len as u16)).unwrap();
|
|
||||||
write!(stdout(), "{}{}", " ".repeat(input_len), MoveLeft(input_len as u16).to_string()).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
input.write().unwrap().clear();
|
|
||||||
|
|
||||||
if !message.is_empty() {
|
|
||||||
if message.starts_with("/") {
|
|
||||||
on_command(&host, &message).expect("Error on command");
|
|
||||||
} else {
|
|
||||||
send_message(&host, &format!("{}<{}> {}", MAGIC_KEY, name, message)).expect("Error sending message");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if input.write().unwrap().pop().is_some() {
|
|
||||||
stdout().lock().execute(MoveLeft(1)).unwrap();
|
|
||||||
write!(stdout(), " {}", MoveLeft(1).to_string()).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
input.write().unwrap().push(c);
|
|
||||||
write!(stdout(), "{}", c).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
}
|
|
||||||
KeyCode::Esc => {
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::Paste(data) => {
|
|
||||||
input.write().unwrap().push_str(&data);
|
|
||||||
write!(stdout(), "{}", &data).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
90
src/rac.rs
Normal file
90
src/rac.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
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};
|
||||||
|
|
||||||
|
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!("\r\x07{}{}{}",
|
||||||
|
message,
|
||||||
|
if message.chars().count() < 39 {
|
||||||
|
" ".repeat(39-message.chars().count())
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
if ADVERTISEMENT_ENABLED {ADVERTISEMENT} else {""}
|
||||||
|
);
|
||||||
|
stream.write_all(data.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_null(stream: &mut TcpStream) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
loop {
|
||||||
|
let mut buf = vec![0; 1];
|
||||||
|
stream.read_exact(&mut buf)?;
|
||||||
|
if buf[0] != 0 {
|
||||||
|
break Ok(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_messages(host: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let mut stream = TcpStream::connect(host)?;
|
||||||
|
|
||||||
|
stream.write_all(&[0x00])?;
|
||||||
|
|
||||||
|
let packet_size = {
|
||||||
|
let mut data = skip_null(&mut stream)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut buf = vec![0; 1];
|
||||||
|
stream.read_exact(&mut buf)?;
|
||||||
|
let ch = buf[0];
|
||||||
|
if ch == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data.push(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from_utf8(data)?
|
||||||
|
.trim_matches(char::from(0))
|
||||||
|
.parse()?
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.write_all(&[0x01])?;
|
||||||
|
|
||||||
|
let packet_data = {
|
||||||
|
let mut data = skip_null(&mut stream)?;
|
||||||
|
while data.len() < packet_size {
|
||||||
|
let mut buf = vec![0; packet_size - data.len()];
|
||||||
|
let read_bytes = stream.read(&mut buf)?;
|
||||||
|
buf.truncate(read_bytes);
|
||||||
|
data.append(&mut buf);
|
||||||
|
}
|
||||||
|
String::from_utf8_lossy(&data).to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(packet_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv_loop(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));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_recv_loop(host: String, messages: Arc<RwLock<String>>, input: Arc<RwLock<String>>) {
|
||||||
|
thread::spawn({
|
||||||
|
move || {
|
||||||
|
let _ = recv_loop(&host, messages, input);
|
||||||
|
println!("Connection closed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
158
src/term.rs
Normal file
158
src/term.rs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
use std::{error::Error, io::{stdout, Write}, sync::{Arc, RwLock}, thread, 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, UPDATE_TIME};
|
||||||
|
|
||||||
|
pub fn print_console(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.reverse();
|
||||||
|
let messages: Vec<String> = messages.into_iter().filter_map(format_message).collect();
|
||||||
|
let text = format!(
|
||||||
|
"{}{}\n> {}",
|
||||||
|
"\n".repeat(MAX_MESSAGES - messages.len()),
|
||||||
|
messages.join("\n"),
|
||||||
|
// if sound { "\x07" } else { "" },
|
||||||
|
input
|
||||||
|
);
|
||||||
|
for line in text.lines() {
|
||||||
|
write!(stdout().lock(), "\r\n{}", line)?;
|
||||||
|
stdout().lock().flush()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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, message) = (date.get(1)?.as_str().to_string(), date.get(2)?.as_str().to_string());
|
||||||
|
|
||||||
|
Some(if let Some(captures) = find_username_color(&message) {
|
||||||
|
let nick = captures.0;
|
||||||
|
let content = captures.1;
|
||||||
|
let color = captures.2;
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} {} {}",
|
||||||
|
format!("[{}]", date).white().dimmed(),
|
||||||
|
format!("<{}>", nick).color(color).bold(),
|
||||||
|
content.white().blink()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{} {}",
|
||||||
|
format!("[{}]", date).white().dimmed(),
|
||||||
|
message.white().blink()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_text(input: &str) -> String {
|
||||||
|
let ansi_regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap();
|
||||||
|
let control_chars_regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap();
|
||||||
|
let without_ansi = ansi_regex.replace_all(input, "");
|
||||||
|
let cleaned_text = control_chars_regex.replace_all(&without_ansi, "");
|
||||||
|
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) {
|
||||||
|
return Some((captures[1].to_string(), captures[2].to_string(), color.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_events(input: Arc<RwLock<String>>, host: String, name: String) {
|
||||||
|
loop {
|
||||||
|
if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue }
|
||||||
|
|
||||||
|
let event = match event::read() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => { continue },
|
||||||
|
};
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Key(event) => {
|
||||||
|
match event.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let message = input.read().unwrap().clone();
|
||||||
|
|
||||||
|
let input_len = input.read().unwrap().chars().count();
|
||||||
|
stdout().lock().execute(MoveLeft(input_len as u16)).unwrap();
|
||||||
|
write!(stdout(), "{}{}", " ".repeat(input_len), MoveLeft(input_len as u16).to_string()).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
input.write().unwrap().clear();
|
||||||
|
|
||||||
|
if !message.is_empty() {
|
||||||
|
if message.starts_with("/") {
|
||||||
|
on_command(&host, &message).expect("Error on command");
|
||||||
|
} else {
|
||||||
|
send_message(&host, &format!("{}<{}> {}", MAGIC_KEY, name, message)).expect("Error sending message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
if input.write().unwrap().pop().is_some() {
|
||||||
|
stdout().lock().execute(MoveLeft(1)).unwrap();
|
||||||
|
write!(stdout(), " {}", MoveLeft(1).to_string()).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
input.write().unwrap().push(c);
|
||||||
|
write!(stdout(), "{}", c).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
}
|
||||||
|
KeyCode::Esc => {
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::Paste(data) => {
|
||||||
|
input.write().unwrap().push_str(&data);
|
||||||
|
write!(stdout(), "{}", &data).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_main_loop(messages: Arc<RwLock<String>>, input: Arc<RwLock<String>>, host: String, name: String) {
|
||||||
|
enable_raw_mode().unwrap();
|
||||||
|
|
||||||
|
thread::spawn({
|
||||||
|
let messages = messages.clone();
|
||||||
|
let input = input.clone();
|
||||||
|
|
||||||
|
move || {
|
||||||
|
print_console(
|
||||||
|
&messages.read().unwrap(),
|
||||||
|
&input.read().unwrap()
|
||||||
|
).expect("Error printing console");
|
||||||
|
thread::sleep(Duration::from_millis(UPDATE_TIME));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
poll_events(input.clone(), host, name);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user