mirror of
https://github.com/MeexReay/bRAC.git
synced 2025-05-06 05:28:02 +03:00
293 lines
9.9 KiB
Rust
293 lines
9.9 KiB
Rust
use std::{
|
|
error::Error, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}
|
|
};
|
|
|
|
use crate::connect_rac;
|
|
|
|
use super::proto::{connect, read_messages, send_message, send_message_spoof_auth, register_user, send_message_auth};
|
|
|
|
use gui::{add_chat_message, clear_chat_messages};
|
|
use lazy_static::lazy_static;
|
|
use regex::Regex;
|
|
|
|
use ctx::Context;
|
|
|
|
pub use gui::run_main_loop;
|
|
|
|
|
|
lazy_static! {
|
|
static ref ANSI_REGEX: Regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap();
|
|
static ref CONTROL_CHARS_REGEX: Regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap();
|
|
|
|
pub static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap();
|
|
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
|
|
|
|
pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![
|
|
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "green".to_string()), // bRAC
|
|
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "red".to_string()), // CRAB
|
|
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "magenta".to_string()), // Mefidroniy
|
|
(Regex::new(r"<(.*?)> (.*)").unwrap(), "cyan".to_string()), // clRAC
|
|
];
|
|
}
|
|
|
|
|
|
pub mod gui;
|
|
pub mod config;
|
|
pub mod ctx;
|
|
|
|
|
|
const HELP_MESSAGE: &str = "Help message:
|
|
/help - show help message
|
|
/register password - register user
|
|
/login password - login user
|
|
/clear n - send empty message n times
|
|
/spam n text - send message with text n times
|
|
/ping - check server ping";
|
|
|
|
pub fn sanitize_text(input: &str) -> String {
|
|
let without_ansi = ANSI_REGEX.replace_all(input, "");
|
|
let cleaned_text = CONTROL_CHARS_REGEX.replace_all(&without_ansi, "");
|
|
cleaned_text.into_owned()
|
|
}
|
|
|
|
pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
|
for i in message.split("\n")
|
|
.map(|o| o.to_string()) {
|
|
print_message(ctx.clone(), i)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn on_command(ctx: Arc<Context>, 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" {
|
|
let Some(times) = args.get(0) else { return Ok(()) };
|
|
let times = times.parse()?;
|
|
for _ in 0..times {
|
|
send_message(connect_rac!(ctx), "\r")?;
|
|
}
|
|
} else if command == "spam" {
|
|
let Some(times) = args.get(0) else { return Ok(()) };
|
|
let times = times.parse()?;
|
|
let msg = args[1..].join(" ");
|
|
for _ in 0..times {
|
|
send_message(connect_rac!(ctx), &("\r".to_string()+&msg))?;
|
|
}
|
|
} else if command == "help" {
|
|
add_message(ctx.clone(), HELP_MESSAGE)?;
|
|
} else if command == "register" {
|
|
let Some(pass) = args.get(0) else {
|
|
add_message(ctx.clone(), "please provide password as the first argument")?;
|
|
return Ok(())
|
|
};
|
|
|
|
match register_user(connect_rac!(ctx), &ctx.name(), pass, !ctx.config(|o| o.ssl_enabled)) {
|
|
Ok(true) => {
|
|
add_message(ctx.clone(), "you was registered successfully bro")?;
|
|
*ctx.registered.write().unwrap() = Some(pass.to_string());
|
|
},
|
|
Ok(false) => add_message(ctx.clone(), "user with this account already exists bruh")?,
|
|
Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))?
|
|
};
|
|
} else if command == "login" {
|
|
let Some(pass) = args.get(0) else {
|
|
add_message(ctx.clone(), "please provide password as the first argument")?;
|
|
return Ok(())
|
|
};
|
|
|
|
add_message(ctx.clone(), "ye bro you was logged in")?;
|
|
*ctx.registered.write().unwrap() = Some(pass.to_string());
|
|
} else if command == "ping" {
|
|
let mut before = ctx.packet_size();
|
|
let message = format!("Checking ping... {:X}", SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis());
|
|
|
|
send_message(connect_rac!(ctx), &message)?;
|
|
|
|
let start = SystemTime::now();
|
|
|
|
loop {
|
|
let data = read_messages(
|
|
connect_rac!(ctx),
|
|
ctx.config(|o| o.max_messages),
|
|
before,
|
|
!ctx.config(|o| o.ssl_enabled),
|
|
ctx.config(|o| o.chunked_enabled)
|
|
).ok().flatten();
|
|
|
|
if let Some((data, size)) = data {
|
|
if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) {
|
|
if last.contains(&message) {
|
|
break;
|
|
} else {
|
|
before = size;
|
|
}
|
|
} else {
|
|
before = size;
|
|
}
|
|
}
|
|
}
|
|
|
|
add_message(ctx.clone(), &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?;
|
|
} else {
|
|
add_message(ctx.clone(), "Unknown command bruh")?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
|
|
format!("{}{}{}",
|
|
if ctx.config(|o| o.hide_my_ip) {
|
|
"\r\x07"
|
|
} else {
|
|
""
|
|
},
|
|
message,
|
|
if !ctx.config(|o| o.hide_my_ip) {
|
|
let spaces = if ctx.config(|o| o.auth_enabled) {
|
|
39
|
|
} else {
|
|
54
|
|
};
|
|
|
|
if message.chars().count() < spaces {
|
|
" ".repeat(spaces-message.chars().count())
|
|
} else {
|
|
String::new()
|
|
}
|
|
} else {
|
|
String::new()
|
|
}
|
|
)
|
|
}
|
|
|
|
pub fn print_message(ctx: Arc<Context>, message: String) -> Result<(), Box<dyn Error>> {
|
|
ctx.add_message(ctx.config(|o| o.max_messages), vec![message.clone()]);
|
|
add_chat_message(ctx.clone(), message);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
|
let last_size = ctx.packet_size();
|
|
|
|
match read_messages(
|
|
connect_rac!(ctx),
|
|
ctx.config(|o| o.max_messages),
|
|
ctx.packet_size(),
|
|
!ctx.config(|o| o.ssl_enabled),
|
|
ctx.config(|o| o.chunked_enabled)
|
|
) {
|
|
Ok(Some((messages, size))) => {
|
|
if ctx.config(|o| o.chunked_enabled) {
|
|
ctx.add_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
|
|
if last_size == 0 {
|
|
if messages.len() >= 1 {
|
|
clear_chat_messages(ctx.clone(), messages[0].clone());
|
|
if messages.len() >= 2 {
|
|
for msg in &messages[1..] {
|
|
add_chat_message(ctx.clone(), msg.clone());
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for msg in messages {
|
|
add_chat_message(ctx.clone(), msg.clone());
|
|
}
|
|
}
|
|
} else {
|
|
ctx.put_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
|
|
if messages.len() >= 1 {
|
|
clear_chat_messages(ctx.clone(), messages[0].clone());
|
|
if messages.len() >= 2 {
|
|
for msg in &messages[1..] {
|
|
add_chat_message(ctx.clone(), msg.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Err(e) => {
|
|
println!("Read messages error: {}", e.to_string())
|
|
}
|
|
_ => {}
|
|
}
|
|
thread::sleep(Duration::from_millis(ctx.config(|o| o.update_time) as u64));
|
|
Ok(())
|
|
}
|
|
|
|
pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
|
if message.starts_with("/") && ctx.config(|o| o.commands_enabled) {
|
|
on_command(ctx.clone(), &message)?;
|
|
} else {
|
|
let message = prepare_message(
|
|
ctx.clone(),
|
|
&ctx.config(|o| o.message_format.clone())
|
|
.replace("{name}", &ctx.name())
|
|
.replace("{text}", &message)
|
|
);
|
|
|
|
if let Some(password) = ctx.registered.read().unwrap().clone() {
|
|
send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message, !ctx.config(|o| o.ssl_enabled))?;
|
|
} else if ctx.config(|o| o.auth_enabled) {
|
|
send_message_spoof_auth(connect_rac!(ctx), &message, !ctx.config(|o| o.ssl_enabled))?;
|
|
} else {
|
|
send_message(connect_rac!(ctx), &message)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn sanitize_message(message: String) -> Option<String> {
|
|
let message = sanitize_text(&message);
|
|
|
|
let message = message.trim().to_string();
|
|
|
|
Some(message)
|
|
}
|
|
|
|
/// message -> (date, ip, text, (name, color))
|
|
pub fn parse_message(message: String) -> Option<(String, Option<String>, String, Option<(String, String)>)> {
|
|
if message.is_empty() {
|
|
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(),
|
|
);
|
|
|
|
let message = message
|
|
.trim_start_matches("(UNREGISTERED)")
|
|
.trim_start_matches("(UNAUTHORIZED)")
|
|
.trim_start_matches("(UNAUTHENTICATED)")
|
|
.trim()
|
|
.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 (message, nick) = match find_username_color(&message) {
|
|
Some((name, content, color)) => (content, Some((name, color))),
|
|
None => (message, None),
|
|
};
|
|
|
|
Some((date, ip, message, nick))
|
|
}
|
|
|
|
// message -> (nick, content, color)
|
|
pub fn find_username_color(message: &str) -> Option<(String, String, String)> {
|
|
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
|
|
} |