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
|
/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 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}, 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>> {
|
pub fn print_console(context: Arc<Context>, messages: Vec<String>, input: &str) -> Result<(), Box<dyn Error>> {
|
||||||
let text = format!(
|
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)> {
|
fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
||||||
for (re, color) in COLORED_USERNAMES.iter() {
|
for (re, color) in COLORED_USERNAMES.iter() {
|
||||||
if let Some(captures) = re.captures(message) {
|
if let Some(captures) = re.captures(message) {
|
||||||
@ -135,6 +171,22 @@ fn poll_events(ctx: Arc<Context>) {
|
|||||||
disable_raw_mode().unwrap();
|
disable_raw_mode().unwrap();
|
||||||
break;
|
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) => {
|
KeyCode::Char(c) => {
|
||||||
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
|
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
|
||||||
disable_raw_mode().unwrap();
|
disable_raw_mode().unwrap();
|
||||||
@ -151,6 +203,9 @@ fn poll_events(ctx: Arc<Context>) {
|
|||||||
ctx.input.write().unwrap().push_str(&data);
|
ctx.input.write().unwrap().push_str(&data);
|
||||||
write!(stdout(), "{}", &data).unwrap();
|
write!(stdout(), "{}", &data).unwrap();
|
||||||
stdout().lock().flush().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)]
|
#[allow(unused_imports)]
|
||||||
use std::{env, fs, path::{Path, PathBuf}, thread, time::Duration};
|
use std::{env, fs, path::{Path, PathBuf}, thread, time::Duration};
|
||||||
use homedir::my_home;
|
use homedir::my_home;
|
||||||
|
use rand::random;
|
||||||
use serde_yml;
|
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}";
|
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
|
||||||
|
|
||||||
@ -25,23 +28,49 @@ pub struct Config {
|
|||||||
pub disable_ip_hiding: bool,
|
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_update_time() -> usize { 50 }
|
||||||
fn default_host() -> String { "meex.lol:11234".to_string() }
|
fn default_host() -> String { "meex.lol:11234".to_string() }
|
||||||
fn default_message_format() -> String { MESSAGE_FORMAT.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 {
|
pub fn configure(path: PathBuf) -> Config {
|
||||||
let host = get_input("Host (default: meex.lol:11234) > ").unwrap_or("meex.lol:11234".to_string());
|
println!("To configure the client, please answer a few questions. It won't take long.");
|
||||||
let name = get_input("Name (default: ask every time) > ");
|
println!("You can reconfigure client in any moment via `bRAC --configure`");
|
||||||
let update_time = get_input("Update interval (default: 50) > ").map(|o| o.parse().ok()).flatten().unwrap_or(50);
|
println!("Config stores in path `{}`", path.to_string_lossy());
|
||||||
let max_messages = get_input("Max messages (default: 100) > ").map(|o| o.parse().ok()).flatten().unwrap_or(100);
|
println!();
|
||||||
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);
|
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 {
|
let config = Config {
|
||||||
host,
|
host,
|
||||||
name,
|
name,
|
||||||
message_format: MESSAGE_FORMAT.to_string(),
|
message_format,
|
||||||
update_time,
|
update_time,
|
||||||
max_messages,
|
max_messages,
|
||||||
enable_ip_viewing,
|
enable_ip_viewing,
|
||||||
@ -91,3 +120,91 @@ pub fn get_config_path() -> PathBuf {
|
|||||||
|
|
||||||
config_path
|
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::{
|
use std::sync::Arc;
|
||||||
error::Error, io::{stdin, stdout, BufRead, Write}, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, time::SystemTime
|
|
||||||
};
|
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use colored::Color;
|
use colored::Color;
|
||||||
use config::{configure, get_config_path, load_config};
|
use config::{configure, get_config_path, load_config, Args, Context};
|
||||||
use rac::{read_messages, run_recv_loop, send_message};
|
use proto::{read_messages, run_recv_loop, send_message};
|
||||||
use rand::random;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use term::run_main_loop;
|
use chat::run_main_loop;
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
|
|
||||||
const ADVERTISEMENT: &str = "\r\x1B[1A use bRAC client! https://github.com/MeexReay/bRAC \x1B[1B";
|
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! {
|
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![
|
||||||
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green),
|
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green), // bRAC
|
||||||
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed),
|
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed), // CRAB
|
||||||
(Regex::new(r"(.*?): (.*)").unwrap(), Color::Magenta),
|
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), Color::Magenta), // Mefidroniy
|
||||||
(Regex::new(r"<(.*?)> (.*)").unwrap(), Color::Cyan),
|
(Regex::new(r"<(.*?)> (.*)").unwrap(), Color::Cyan), // clRAC
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod term;
|
mod chat;
|
||||||
mod rac;
|
mod proto;
|
||||||
|
mod util;
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -170,45 +44,22 @@ fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = {
|
|
||||||
let config = load_config(config_path);
|
let config = load_config(config_path);
|
||||||
|
|
||||||
Context {
|
let ctx = Arc::new(Context::new(&config, &args));
|
||||||
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);
|
|
||||||
|
|
||||||
if args.read_messages {
|
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 {
|
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 {
|
if args.send_message.is_some() || args.read_messages {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
run_recv_loop(context.clone());
|
run_recv_loop(ctx.clone());
|
||||||
run_main_loop(context.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 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>> {
|
pub fn send_message(context: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
let mut stream = TcpStream::connect(&context.host)?;
|
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