mirror of
https://github.com/MeexReay/bRAC.git
synced 2025-05-06 13:38:04 +03:00
idk what to say
This commit is contained in:
parent
df13dde2c8
commit
0e850f79c7
@ -1,16 +0,0 @@
|
||||
|
||||
[target.i686-unknown-linux-gnu]
|
||||
linker = "i686-unknown-linux-gnu-gcc"
|
||||
ar = "i686-unknown-linux-gnu-ar"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "x86_64-unknown-linux-gnu-gcc"
|
||||
ar = "x86_64-unknown-linux-gnu-ar"
|
||||
|
||||
# [target.i686-pc-windows-gnu]
|
||||
# linker = "i686-w64-mingw32-gcc"
|
||||
# ar = "i686-w64-mingw32-ar"
|
||||
|
||||
# [target.x86_64-pc-windows-gnu]
|
||||
# linker = "x86_64-w64-mingw32-gcc"
|
||||
# ar = "x86_64-w64-mingw32-ar"
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
/target
|
||||
/result
|
@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
REM cargo build --release --target x86_64-unknown-linux-gnu
|
||||
REM echo x86_64-unknown-linux-gnu built
|
||||
REM cargo build --release --target i686-unknown-linux-gnu
|
||||
REM echo i686-unknown-linux-gnu built
|
||||
REM cargo build --release --target x86_64-pc-windows-gnu
|
||||
REM echo x86_64-pc-windows-gnu built
|
||||
REM cargo build --release --target i686-pc-windows-gnu
|
||||
REM echo i686-pc-windows-gnu built
|
||||
cargo build --release --target x86_64-pc-windows-msvc
|
||||
echo x86_64-pc-windows-msvc built
|
||||
cargo build --release --target i686-pc-windows-msvc
|
||||
echo i686-pc-windows-msvc built
|
13
build_all.sh
13
build_all.sh
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
echo x86_64-unknown-linux-gnu built
|
||||
cargo build --release --target i686-unknown-linux-gnu
|
||||
echo i686-unknown-linux-gnu built
|
||||
cargo build --release --target x86_64-pc-windows-gnu
|
||||
echo x86_64-pc-windows-gnu built
|
||||
# cargo build --release --target i686-pc-windows-gnu
|
||||
# echo i686-pc-windows-gnu built
|
||||
# cargo build --release --target x86_64-pc-windows-msvc
|
||||
# echo x86_64-pc-windows-msvc built
|
||||
# cargo build --release --target i686-pc-windows-msvc
|
||||
# echo i686-pc-windows-msvc built
|
1
docs/rac_protocol.md
Normal file
1
docs/rac_protocol.md
Normal file
@ -0,0 +1 @@
|
||||
todo
|
96
flake.lock
generated
Normal file
96
flake.lock
generated
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739055578,
|
||||
"narHash": "sha256-2MhC2Bgd06uI1A0vkdNUyDYsMD0SLNGKtD8600mZ69A=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a45fa362d887f4d4a7157d95c28ca9ce2899b70e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1736320768,
|
||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1739240901,
|
||||
"narHash": "sha256-YDtl/9w71m5WcZvbEroYoWrjECDhzJZLZ8E68S3BYok=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "03473e2af8a4b490f4d2cdb2e4d3b75f82c8197c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
66
flake.nix
Normal file
66
flake.nix
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
description = "Cargo project with cross-compilation support";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... }:
|
||||
flake-utils.lib.eachSystem [
|
||||
"x86_64-linux"
|
||||
"x86_64-darwin"
|
||||
"x86_64-windows"
|
||||
"i686-linux"
|
||||
"i686-windows"
|
||||
"aarch64-darwin"
|
||||
] (system:
|
||||
let
|
||||
overlays = [ (import rust-overlay) ];
|
||||
pkgs = import nixpkgs {
|
||||
inherit system overlays;
|
||||
crossSystem = (import <nixpkgs/lib>).systems.examples.gnu64 // {
|
||||
rust.rustcTarget = {
|
||||
"x86_64-linux" = "x86_64-unknown-linux-gnu";
|
||||
"x86_64-darwin" = "x86_64-apple-darwin";
|
||||
"x86_64-windows" = "x86_64-pc-windows-gnu";
|
||||
"i686-linux" = "i686-unknown-linux-gnu";
|
||||
"i686-windows" = "i686-pc-windows-gnu";
|
||||
"aarch64-darwin" = "aarch64-apple-darwin";
|
||||
}.${system}; # here invalid target format
|
||||
};
|
||||
};
|
||||
|
||||
exeSuffix = if pkgs.stdenv.hostPlatform.isWindows then ".exe" else "";
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
rust-bin.stable.latest.default
|
||||
pkg-config
|
||||
(if stdenv.isDarwin then darwin.libiconv else null)
|
||||
];
|
||||
};
|
||||
|
||||
packages.default = pkgs.rustPlatform.buildRustPackage {
|
||||
pname = "my-cargo-project";
|
||||
version = "0.1.0";
|
||||
src = pkgs.lib.cleanSource ./.;
|
||||
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
};
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "My Rust project";
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [ ];
|
||||
platforms = platforms.all;
|
||||
};
|
||||
|
||||
postInstall = ''
|
||||
mv $out/bin/bRAC $out/bin/bRAC-${system}${exeSuffix}
|
||||
'';
|
||||
};
|
||||
});
|
||||
}
|
39
shell.nix
39
shell.nix
@ -1,39 +0,0 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
let
|
||||
# Переопределение для mingw32
|
||||
mingw32WithDwarf = pkgs.pkgsCross.mingw32.buildPackages.gcc.overrideAttrs (oldAttrs: {
|
||||
configureFlags = [
|
||||
"--disable-sjlj-exceptions"
|
||||
"--enable-dwarf2"
|
||||
];
|
||||
});
|
||||
in
|
||||
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
rustup
|
||||
gcc_multi
|
||||
pkg-config
|
||||
zlib
|
||||
openssl
|
||||
|
||||
# Добавляем кросс-компиляторы
|
||||
pkgsCross.gnu32.buildPackages.gcc
|
||||
pkgsCross.gnu32.buildPackages.binutils
|
||||
pkgsCross.gnu64.buildPackages.gcc
|
||||
pkgsCross.gnu64.buildPackages.binutils
|
||||
|
||||
# Переопределённый MinGW для 32-бит Windows
|
||||
mingw32WithDwarf
|
||||
|
||||
# Необходимые библиотеки для Windows
|
||||
pkgsCross.mingw32.windows.pthreads
|
||||
pkgsCross.mingw32.windows.mcfgthreads
|
||||
|
||||
# 64-битный MinGW и необходимые библиотеки
|
||||
pkgsCross.mingwW64.buildPackages.gcc
|
||||
pkgsCross.mingwW64.windows.pthreads
|
||||
pkgsCross.mingwW64.windows.mcfgthreads
|
||||
];
|
||||
}
|
@ -1,10 +1,54 @@
|
||||
use std::{error::Error, io::{stdout, Write}, sync::Arc, time::Duration};
|
||||
use std::{error::Error, io::{stdout, Write}, sync::{atomic::Ordering, Arc}, time::{Duration, SystemTime}};
|
||||
|
||||
use colored::{Color, Colorize};
|
||||
use crossterm::{cursor::MoveLeft, event::{self, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode}, ExecutableCommand};
|
||||
use regex::Regex;
|
||||
use rand::random;
|
||||
|
||||
use super::{on_command, rac::send_message, Context, ADVERTISEMENT, COLORED_USERNAMES, DATE_REGEX};
|
||||
use super::{proto::read_messages, util::sanitize_text, ADVERTISEMENT, COLORED_USERNAMES, DATE_REGEX, config::Context, proto::send_message};
|
||||
|
||||
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" {
|
||||
send_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))?;
|
||||
} else if command == "help" {
|
||||
write!(stdout(), "Help message:\r
|
||||
/help - show help message\r
|
||||
/clear - clear console\r
|
||||
/spam *args - spam console with text\r
|
||||
/ping - check server ping\r
|
||||
\r
|
||||
Press enter to close")?;
|
||||
stdout().flush()?;
|
||||
} else if command == "ping" {
|
||||
let mut before = ctx.messages.1.load(Ordering::SeqCst);
|
||||
let start = SystemTime::now();
|
||||
let message = format!("Checking ping... {:X}", random::<u16>());
|
||||
send_message(ctx.clone(), &message)?;
|
||||
loop {
|
||||
let data = read_messages(ctx.clone(), before).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
send_message(ctx.clone(), &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!(
|
||||
@ -67,14 +111,6 @@ fn format_message(ctx: Arc<Context>, message: String) -> Option<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()
|
||||
}
|
||||
|
||||
fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
||||
for (re, color) in COLORED_USERNAMES.iter() {
|
||||
if let Some(captures) = re.captures(message) {
|
||||
@ -135,6 +171,22 @@ fn poll_events(ctx: Arc<Context>) {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
}
|
||||
KeyCode::Down => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
disable_raw_mode().unwrap();
|
||||
break;
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
|
||||
disable_raw_mode().unwrap();
|
||||
@ -151,6 +203,9 @@ fn poll_events(ctx: Arc<Context>) {
|
||||
ctx.input.write().unwrap().push_str(&data);
|
||||
write!(stdout(), "{}", &data).unwrap();
|
||||
stdout().lock().flush().unwrap();
|
||||
},
|
||||
Event::Mouse(data) => {
|
||||
|
||||
}
|
||||
_ => {}
|
||||
}
|
135
src/config.rs
135
src/config.rs
@ -1,9 +1,12 @@
|
||||
use std::sync::{atomic::AtomicUsize, Arc, RwLock};
|
||||
#[allow(unused_imports)]
|
||||
use std::{env, fs, path::{Path, PathBuf}, thread, time::Duration};
|
||||
use homedir::my_home;
|
||||
use rand::random;
|
||||
use serde_yml;
|
||||
use clap::Parser;
|
||||
|
||||
use super::get_input;
|
||||
use super::util::get_input;
|
||||
|
||||
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
|
||||
|
||||
@ -25,23 +28,49 @@ pub struct Config {
|
||||
pub disable_ip_hiding: bool,
|
||||
}
|
||||
|
||||
fn default_max_messages() -> usize { 100 }
|
||||
fn default_max_messages() -> usize { 1000 }
|
||||
fn default_update_time() -> usize { 50 }
|
||||
fn default_host() -> String { "meex.lol:11234".to_string() }
|
||||
fn default_message_format() -> String { MESSAGE_FORMAT.to_string() }
|
||||
|
||||
fn ask_usize(name: impl ToString, default: usize) -> usize {
|
||||
get_input(format!("{} (default: {}) > ", name.to_string(), default))
|
||||
.and_then(|o| o.parse().ok()).unwrap_or(default)
|
||||
}
|
||||
|
||||
fn ask_string(name: impl ToString, default: impl ToString + Clone) -> String {
|
||||
ask_string_option(name, default.clone()).unwrap_or(default.to_string())
|
||||
}
|
||||
|
||||
fn ask_string_option(name: impl ToString, default: impl ToString) -> Option<String> {
|
||||
let default = default.to_string();
|
||||
get_input(format!("{} (default: {}) > ", name.to_string(), default))
|
||||
}
|
||||
|
||||
fn ask_bool(name: impl ToString, default: bool) -> bool {
|
||||
get_input(format!("{} (Y/N, default: {}) > ", name.to_string(), default))
|
||||
.map(|o| o.to_lowercase() != "n")
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
pub fn configure(path: PathBuf) -> Config {
|
||||
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 enable_ip_viewing = get_input("Enable users IP viewing? (Y/N, default: N) > ").map(|o| o.to_lowercase() != "n").unwrap_or(false);
|
||||
let disable_ip_hiding = get_input("Enable your IP viewing? (Y/N, default: N) > ").map(|o| o.to_lowercase() != "n").unwrap_or(false);
|
||||
println!("To configure the client, please answer a few questions. It won't take long.");
|
||||
println!("You can reconfigure client in any moment via `bRAC --configure`");
|
||||
println!("Config stores in path `{}`", path.to_string_lossy());
|
||||
println!();
|
||||
|
||||
let host = ask_string("Host", default_host());
|
||||
let name = ask_string_option("Name", "ask every time");
|
||||
let update_time = ask_usize("Update interval", default_update_time());
|
||||
let max_messages = ask_usize("Max messages", default_max_messages());
|
||||
let message_format = ask_string("Message format", default_message_format());
|
||||
let enable_ip_viewing = ask_bool("Enable users IP viewing?", false);
|
||||
let disable_ip_hiding = ask_bool("Enable your IP viewing?", false);
|
||||
|
||||
let config = Config {
|
||||
host,
|
||||
name,
|
||||
message_format: MESSAGE_FORMAT.to_string(),
|
||||
message_format,
|
||||
update_time,
|
||||
max_messages,
|
||||
enable_ip_viewing,
|
||||
@ -91,3 +120,91 @@ pub fn get_config_path() -> PathBuf {
|
||||
|
||||
config_path
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// Print config path
|
||||
#[arg(short='p', long)]
|
||||
pub config_path: bool,
|
||||
|
||||
/// Use specified host
|
||||
#[arg(short='H', long)]
|
||||
pub host: Option<String>,
|
||||
|
||||
/// Use specified name
|
||||
#[arg(short='n', long)]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// Use specified message format
|
||||
#[arg(short='F', long)]
|
||||
pub message_format: Option<String>,
|
||||
|
||||
/// Print unformatted messages from chat and exit
|
||||
#[arg(short='r', long)]
|
||||
pub read_messages: bool,
|
||||
|
||||
/// Send unformatted message to chat and exit
|
||||
#[arg(short='s', long, value_name="MESSAGE")]
|
||||
pub send_message: Option<String>,
|
||||
|
||||
/// Disable message formatting and sanitizing
|
||||
#[arg(short='f', long)]
|
||||
pub disable_formatting: bool,
|
||||
|
||||
/// Disable slash commands
|
||||
#[arg(short='c', long)]
|
||||
pub disable_commands: bool,
|
||||
|
||||
/// Disable ip hiding
|
||||
#[arg(short='i', long)]
|
||||
pub disable_ip_hiding: bool,
|
||||
|
||||
/// Enable users IP viewing
|
||||
#[arg(short='v', long)]
|
||||
pub enable_users_ip_viewing: bool,
|
||||
|
||||
/// Configure client
|
||||
#[arg(short='C', long)]
|
||||
pub configure: bool,
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
pub messages: Arc<(RwLock<Vec<String>>, AtomicUsize)>,
|
||||
pub input: Arc<RwLock<String>>,
|
||||
pub host: String,
|
||||
pub name: String,
|
||||
pub disable_formatting: bool,
|
||||
pub disable_commands: bool,
|
||||
pub disable_hiding_ip: bool,
|
||||
pub message_format: String,
|
||||
pub update_time: usize,
|
||||
pub max_messages: usize,
|
||||
pub enable_ip_viewing: bool,
|
||||
pub scroll: Arc<AtomicUsize>
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(config: &Config, args: &Args) -> Context {
|
||||
Context {
|
||||
messages: Arc::new((RwLock::new(Vec::new()), AtomicUsize::new(0))),
|
||||
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()),
|
||||
name: match args.name.clone().or(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)
|
||||
},
|
||||
},
|
||||
disable_formatting: args.disable_formatting,
|
||||
disable_commands: args.disable_commands,
|
||||
disable_hiding_ip: args.disable_ip_hiding,
|
||||
update_time: config.update_time,
|
||||
max_messages: config.max_messages,
|
||||
enable_ip_viewing: args.enable_users_ip_viewing || config.enable_ip_viewing,
|
||||
scroll: Arc::new(AtomicUsize::new(0))
|
||||
}
|
||||
}
|
||||
}
|
183
src/main.rs
183
src/main.rs
@ -1,15 +1,12 @@
|
||||
use std::{
|
||||
error::Error, io::{stdin, stdout, BufRead, Write}, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, time::SystemTime
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use colored::Color;
|
||||
use config::{configure, get_config_path, load_config};
|
||||
use rac::{read_messages, run_recv_loop, send_message};
|
||||
use rand::random;
|
||||
use config::{configure, get_config_path, load_config, Args, Context};
|
||||
use proto::{read_messages, run_recv_loop, send_message};
|
||||
use regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
use term::run_main_loop;
|
||||
use clap::Parser;
|
||||
use chat::run_main_loop;
|
||||
|
||||
|
||||
const ADVERTISEMENT: &str = "\r\x1B[1A use bRAC client! https://github.com/MeexReay/bRAC \x1B[1B";
|
||||
@ -18,141 +15,18 @@ const ADVERTISEMENT_ENABLED: bool = false;
|
||||
lazy_static! {
|
||||
static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] \{(.*?)\} (.*)").unwrap();
|
||||
static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![
|
||||
(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),
|
||||
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green), // bRAC
|
||||
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed), // CRAB
|
||||
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), Color::Magenta), // Mefidroniy
|
||||
(Regex::new(r"<(.*?)> (.*)").unwrap(), Color::Cyan), // clRAC
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
mod config;
|
||||
mod term;
|
||||
mod rac;
|
||||
|
||||
|
||||
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()?;
|
||||
|
||||
if input.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(input.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
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" {
|
||||
send_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))?;
|
||||
} else if command == "help" {
|
||||
write!(stdout(), "Help message:\r
|
||||
/help - show help message\r
|
||||
/clear - clear console\r
|
||||
/spam *args - spam console with text\r
|
||||
/ping - check server ping\r
|
||||
\r
|
||||
Press enter to close")?;
|
||||
stdout().flush()?;
|
||||
} else if command == "ping" {
|
||||
let mut before = ctx.messages.1.load(Ordering::SeqCst);
|
||||
let start = SystemTime::now();
|
||||
let message = format!("Checking ping... {:X}", random::<u16>());
|
||||
send_message(ctx.clone(), &message)?;
|
||||
loop {
|
||||
let data = read_messages(ctx.clone(), before).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
send_message(ctx.clone(), &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Print config path
|
||||
#[arg(short='p', long)]
|
||||
config_path: bool,
|
||||
|
||||
/// Use specified host
|
||||
#[arg(short='H', long)]
|
||||
host: Option<String>,
|
||||
|
||||
/// Use specified name
|
||||
#[arg(short='n', long)]
|
||||
name: Option<String>,
|
||||
|
||||
/// Use specified message format
|
||||
#[arg(short='F', long)]
|
||||
message_format: Option<String>,
|
||||
|
||||
/// Print unformatted messages from chat and exit
|
||||
#[arg(short='r', long)]
|
||||
read_messages: bool,
|
||||
|
||||
/// Send unformatted message to chat and exit
|
||||
#[arg(short='s', long, value_name="MESSAGE")]
|
||||
send_message: Option<String>,
|
||||
|
||||
/// Disable message formatting and sanitizing
|
||||
#[arg(short='f', long)]
|
||||
disable_formatting: bool,
|
||||
|
||||
/// Disable slash commands
|
||||
#[arg(short='c', long)]
|
||||
disable_commands: bool,
|
||||
|
||||
/// Disable ip hiding
|
||||
#[arg(short='i', long)]
|
||||
disable_ip_hiding: bool,
|
||||
|
||||
/// Enable users IP viewing
|
||||
#[arg(short='v', long)]
|
||||
enable_users_ip_viewing: bool,
|
||||
|
||||
/// Configure client
|
||||
#[arg(short='C', long)]
|
||||
configure: bool,
|
||||
}
|
||||
|
||||
|
||||
struct Context {
|
||||
messages: Arc<(RwLock<Vec<String>>, AtomicUsize)>,
|
||||
input: Arc<RwLock<String>>,
|
||||
host: String,
|
||||
name: String,
|
||||
disable_formatting: bool,
|
||||
disable_commands: bool,
|
||||
disable_hiding_ip: bool,
|
||||
message_format: String,
|
||||
update_time: usize,
|
||||
max_messages: usize,
|
||||
enable_ip_viewing: bool
|
||||
}
|
||||
mod chat;
|
||||
mod proto;
|
||||
mod util;
|
||||
|
||||
|
||||
fn main() {
|
||||
@ -170,45 +44,22 @@ fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
let context = {
|
||||
let config = load_config(config_path);
|
||||
|
||||
Context {
|
||||
messages: Arc::new((RwLock::new(Vec::new()), AtomicUsize::new(0))),
|
||||
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()),
|
||||
name: match args.name.clone().or(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)
|
||||
},
|
||||
},
|
||||
disable_formatting: args.disable_formatting,
|
||||
disable_commands: args.disable_commands,
|
||||
disable_hiding_ip: args.disable_ip_hiding,
|
||||
update_time: config.update_time,
|
||||
max_messages: config.max_messages,
|
||||
enable_ip_viewing: args.enable_users_ip_viewing || config.enable_ip_viewing
|
||||
}
|
||||
};
|
||||
|
||||
let context = Arc::new(context);
|
||||
let ctx = Arc::new(Context::new(&config, &args));
|
||||
|
||||
if args.read_messages {
|
||||
print!("{}", read_messages(context.clone(), 0).ok().flatten().expect("Error reading messages").0.join("\n"));
|
||||
print!("{}", read_messages(ctx.clone(), 0).ok().flatten().expect("Error reading messages").0.join("\n"));
|
||||
}
|
||||
|
||||
if let Some(message) = &args.send_message {
|
||||
send_message(context.clone(), message).expect("Error sending message");
|
||||
send_message(ctx.clone(), message).expect("Error sending message");
|
||||
}
|
||||
|
||||
if args.send_message.is_some() || args.read_messages {
|
||||
return;
|
||||
}
|
||||
|
||||
run_recv_loop(context.clone());
|
||||
run_main_loop(context.clone());
|
||||
run_recv_loop(ctx.clone());
|
||||
run_main_loop(ctx.clone());
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{error::Error, io::{Read, Write}, net::TcpStream, sync::{atomic::Ordering, Arc}, thread, time::Duration};
|
||||
|
||||
use super::{term::print_console, Context, ADVERTISEMENT, ADVERTISEMENT_ENABLED};
|
||||
use super::{chat::print_console, Context, ADVERTISEMENT, ADVERTISEMENT_ENABLED};
|
||||
|
||||
pub fn send_message(context: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(&context.host)?;
|
27
src/util.rs
Normal file
27
src/util.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use std::io::{stdin, stdout, BufRead, Write};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
|
||||
pub 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()
|
||||
}
|
||||
|
||||
pub fn get_input(prompt: impl ToString) -> Option<String> {
|
||||
let mut out = stdout().lock();
|
||||
out.write_all(prompt.to_string().as_bytes()).ok()?;
|
||||
out.flush().ok()?;
|
||||
let input = stdin().lock().lines().next()
|
||||
.map(|o| o.ok())
|
||||
.flatten()?;
|
||||
|
||||
if input.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(input.to_string())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user