massive rewrite

This commit is contained in:
MeexReay 2025-04-13 22:53:07 +03:00
parent 036648b5e4
commit 58213cf8c9
12 changed files with 772 additions and 478 deletions

110
Cargo.lock generated
View File

@ -91,9 +91,9 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -124,9 +124,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.29" version = "4.5.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -134,9 +134,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.29" version = "4.5.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -146,9 +146,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.28" version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -177,6 +177,15 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "convert_case"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -195,15 +204,17 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.28.1" version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"crossterm_winapi", "crossterm_winapi",
"derive_more",
"document-features",
"mio", "mio",
"parking_lot", "parking_lot",
"rustix", "rustix 1.0.5",
"signal-hook", "signal-hook",
"signal-hook-mio", "signal-hook-mio",
"winapi", "winapi",
@ -218,6 +229,36 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -341,6 +382,18 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@ -377,9 +430,9 @@ dependencies = [
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.13" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
@ -588,7 +641,20 @@ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -638,18 +704,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.217" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -740,7 +806,7 @@ dependencies = [
"fastrand", "fastrand",
"getrandom", "getrandom",
"once_cell", "once_cell",
"rustix", "rustix 0.38.44",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -750,6 +816,12 @@ version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.2"

View File

@ -8,9 +8,16 @@ rand = "0.9.0"
regex = "1.11.1" regex = "1.11.1"
colored = "3.0.0" colored = "3.0.0"
lazy_static = "1.5.0" lazy_static = "1.5.0"
crossterm = "0.28.1" crossterm = { version = "0.29.0", optional = true }
serde = { version = "1.0.217", features = ["serde_derive"] } serde = { version = "1.0.219", features = ["serde_derive"] }
serde_yml = "0.0.12" serde_yml = "0.0.12"
homedir = "0.3.4" homedir = { version = "0.3.4", optional = true }
clap = { version = "4.5.29", features = ["derive"] } clap = { version = "4.5.36", features = ["derive"] }
native-tls = "0.2.13" native-tls = { version = "0.2.14", optional = true }
[features]
default = ["ssl", "pretty", "homedir"]
ssl = ["dep:native-tls"]
pretty = ["dep:crossterm"]
homedir = ["dep:homedir"]

View File

@ -27,6 +27,15 @@ better RAC client
go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download file you need. its simple. go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download file you need. its simple.
### from flake
If you have Nix package manager installed, you can use:
```bash
nix build github:MeexReay/bRAC # build binary (result/bin/bRAC)
nix run github:MeexReay/bRAC # run (builds and runs bRAC)
```
### build from source ### build from source
(you have to install [rust](https://www.rust-lang.org/tools/install) at first) (you have to install [rust](https://www.rust-lang.org/tools/install) at first)
@ -35,7 +44,7 @@ go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download
git clone https://github.com/MeexReay/bRAC.git git clone https://github.com/MeexReay/bRAC.git
cd bRAC cd bRAC
cargo build --release # build release (target/release/bRAC) cargo build --release # build release (target/release/bRAC)
cargo run # run (builds and runs bRAC itself) cargo run --release # run (builds and runs bRAC itself)
``` ```
## default config ## default config

68
flake.lock generated
View File

@ -1,39 +1,54 @@
{ {
"nodes": { "nodes": {
"flake-utils": { "flake-parts": {
"inputs": { "inputs": {
"systems": "systems" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1743550720,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "numtide", "owner": "hercules-ci",
"repo": "flake-utils", "repo": "flake-parts",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "hercules-ci",
"repo": "flake-utils", "repo": "flake-parts",
"type": "github" "type": "github"
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1739055578, "lastModified": 1744463964,
"narHash": "sha256-2MhC2Bgd06uI1A0vkdNUyDYsMD0SLNGKtD8600mZ69A=", "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
"owner": "NixOS", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a45fa362d887f4d4a7157d95c28ca9ce2899b70e", "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "nixos",
"ref": "nixos-24.11", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs-lib": {
"locked": {
"lastModified": 1743296961,
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1736320768, "lastModified": 1736320768,
@ -52,7 +67,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-parts": "flake-parts",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
} }
@ -62,11 +77,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1739240901, "lastModified": 1744513456,
"narHash": "sha256-YDtl/9w71m5WcZvbEroYoWrjECDhzJZLZ8E68S3BYok=", "narHash": "sha256-NLVluTmK8d01Iz+WyarQhwFcXpHEwU7m5hH3YQQFJS0=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "03473e2af8a4b490f4d2cdb2e4d3b75f82c8197c", "rev": "730fd8e82799219754418483fabe1844262fd1e2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -74,21 +89,6 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "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", "root": "root",

View File

@ -2,34 +2,59 @@
description = "bRAC - better RAC client"; description = "bRAC - better RAC client";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... }: outputs = inputs:
flake-utils.lib.eachDefaultSystem (system: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
let systems = [ "x86_64-linux" ];
overlays = [ (import rust-overlay) ]; perSystem = { config, self', pkgs, lib, system, ... }:
pkgs = import nixpkgs { let
inherit system overlays; runtimeDeps = with pkgs; [ pkg-config openssl ];
}; buildDeps = with pkgs; [ pkg-config openssl ];
in { devDeps = with pkgs; [ pkg-config openssl ];
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
rust-bin.stable.latest.default
pkg-config
];
};
packages.default = pkgs.rustPlatform.buildRustPackage { cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
pname = "bRAC"; msrv = cargoToml.package.rust-version;
version = "0.1.1+2.0";
src = pkgs.lib.cleanSource ./.;
cargoLock = { rustPackage = features:
lockFile = ./Cargo.lock; (pkgs.makeRustPlatform {
cargo = pkgs.rust-bin.stable.latest.minimal;
rustc = pkgs.rust-bin.stable.latest.minimal;
}).buildRustPackage {
inherit (cargoToml.package) name version;
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
buildFeatures = features;
buildInputs = runtimeDeps;
nativeBuildInputs = buildDeps;
};
mkDevShell = rustc:
pkgs.mkShell {
shellHook = ''
export RUST_SRC_PATH=${pkgs.rustPlatform.rustLibSrc}
'';
buildInputs = runtimeDeps;
nativeBuildInputs = buildDeps ++ devDeps ++ [ rustc ];
};
in {
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [ (import inputs.rust-overlay) ];
}; };
packages.default = self'.packages.bRAC;
devShells.default = self'.devShells.stable;
packages.bRAC = (rustPackage "default");
packages.bRAC-minimal = (rustPackage "");
devShells.nightly = (mkDevShell (pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)));
devShells.stable = (mkDevShell pkgs.rust-bin.stable.latest.default);
devShells.msrv = (mkDevShell pkgs.rust-bin.stable.${msrv}.default);
}; };
}); };
} }

View File

@ -1,11 +1,29 @@
use std::{cmp::{max, min}, error::Error, io::{stdout, Write}, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, thread, time::{Duration, SystemTime, UNIX_EPOCH}}; use std::{
error::Error, io::{stdout, Write},
sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock},
time::{SystemTime, UNIX_EPOCH}
};
use colored::{Color, Colorize}; use colored::{Color, Colorize};
use crossterm::{cursor::{MoveLeft, MoveRight}, event::{self, Event, KeyCode, KeyModifiers, MouseEventKind}, execute, terminal::{self, disable_raw_mode, enable_raw_mode}};
use crate::{proto::{connect, send_message_auth}, util::{char_index_to_byte_index, string_chunks}, IP_REGEX}; use super::{
proto::{connect, read_messages, send_message, send_message_spoof_auth},
IP_REGEX,
util::sanitize_text,
COLORED_USERNAMES,
DATE_REGEX,
config::Context
};
use super::{proto::read_messages, util::sanitize_text, COLORED_USERNAMES, DATE_REGEX, config::Context, proto::send_message}; #[cfg(not(feature = "pretty"))]
pub mod minimal_tui;
#[cfg(not(feature = "pretty"))]
pub use minimal_tui::run_main_loop;
#[cfg(feature = "pretty")]
pub mod pretty_tui;
#[cfg(feature = "pretty")]
pub use pretty_tui::run_main_loop;
pub struct ChatStorage { pub struct ChatStorage {
@ -34,10 +52,14 @@ impl ChatStorage {
*self.messages.write().unwrap() = messages; *self.messages.write().unwrap() = messages;
} }
pub fn append(&self, messages: Vec<String>, packet_size: usize) { pub fn append_and_store(&self, messages: Vec<String>, packet_size: usize) {
self.packet_size.store(packet_size, Ordering::SeqCst); self.packet_size.store(packet_size, Ordering::SeqCst);
self.messages.write().unwrap().append(&mut messages.clone()); self.messages.write().unwrap().append(&mut messages.clone());
} }
pub fn append(&self, messages: Vec<String>) {
self.messages.write().unwrap().append(&mut messages.clone());
}
} }
@ -50,7 +72,7 @@ const HELP_MESSAGE: &str = "Help message:\r
Press enter to close"; Press enter to close";
fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> { pub fn on_command(ctx: Arc<Context>, 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, ""));
let args = args.split(" ").collect::<Vec<&str>>(); let args = args.split(" ").collect::<Vec<&str>>();
@ -121,89 +143,7 @@ fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
pub fn prepare_message(context: Arc<Context>, message: &str) -> String {
pub fn print_console(ctx: Arc<Context>, messages: Vec<String>, input: &str) -> Result<(), Box<dyn Error>> {
let (width, height) = terminal::size()?;
let (width, height) = (width as usize, height as usize);
let mut messages = messages
.into_iter()
.flat_map(|o| string_chunks(&o, width as usize - 1))
.map(|o| (o.0.white().blink().to_string(), o.1))
.collect::<Vec<(String, usize)>>();
let messages_size = if messages.len() >= height {
messages.len()-height
} else {
for _ in 0..height-messages.len() {
messages.insert(0, (String::new(), 0));
}
0
};
let scroll = min(ctx.scroll.load(Ordering::SeqCst), messages_size);
let scroll_f = ((1f64 - scroll as f64 / (messages_size+1) as f64) * (height-2) as f64).round() as usize+1;
let messages = if height < messages.len() {
if scroll < messages.len() - height {
messages[
messages.len()-height-scroll..
messages.len()-scroll
].to_vec()
} else {
if scroll < messages.len() {
messages[
0..
messages.len()-scroll
].to_vec()
} else {
vec![]
}
}
} else {
messages
};
let formatted_messages = if ctx.disable_formatting {
messages
.into_iter()
.map(|(i, _)| i)
.collect::<Vec<String>>()
} else {
messages
.into_iter()
.enumerate()
.map(|(i, (s, l))| {
format!("{}{}{}",
s,
" ".repeat(width - 1 - l),
if i == scroll_f {
"".bright_yellow()
} else {
"".yellow()
}
)
})
.collect::<Vec<String>>()
};
let text = format!(
"{}\r\n{} {}",
formatted_messages.join("\r\n"),
">".bright_yellow(),
input
);
let mut out = stdout().lock();
write!(out, "{}", text)?;
out.flush()?;
Ok(())
}
fn prepare_message(context: Arc<Context>, message: &str) -> String {
format!("{}{}{}", format!("{}{}{}",
if !context.disable_hiding_ip { if !context.disable_hiding_ip {
"\r\x07" "\r\x07"
@ -229,8 +169,28 @@ fn prepare_message(context: Arc<Context>, message: &str) -> String {
) )
} }
pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
if message.starts_with("/") && !ctx.disable_commands {
on_command(ctx.clone(), &message)?;
} else {
let message = prepare_message(
ctx.clone(),
&ctx.message_format
.replace("{name}", &ctx.name)
.replace("{text}", &message)
);
fn format_message(ctx: Arc<Context>, message: String) -> Option<String> { if ctx.enable_auth {
send_message_spoof_auth(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
} else {
send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
}
}
Ok(())
}
pub fn format_message(ctx: Arc<Context>, message: String) -> Option<String> {
let message = sanitize_text(&message); let message = sanitize_text(&message);
let date = DATE_REGEX.captures(&message)?; let date = DATE_REGEX.captures(&message)?;
@ -282,7 +242,6 @@ fn format_message(ctx: Arc<Context>, message: String) -> Option<String> {
}) })
} }
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) {
@ -291,278 +250,3 @@ fn find_username_color(message: &str) -> Option<(String, String, Color)> {
} }
None None
} }
fn replace_input(cursor: usize, len: usize, text: &str) {
let spaces = if text.chars().count() < len {
len-text.chars().count()
} else {
0
};
write!(stdout(),
"{}{}{}{}",
MoveLeft(1).to_string().repeat(cursor),
text,
" ".repeat(spaces),
MoveLeft(1).to_string().repeat(spaces)
).unwrap();
stdout().lock().flush().unwrap();
}
fn replace_input_left(cursor: usize, len: usize, text: &str, left: usize) {
let spaces = if text.chars().count() < len {
len-text.chars().count()
} else {
0
};
write!(stdout(),
"{}{}{}{}",
MoveLeft(1).to_string().repeat(cursor),
text,
" ".repeat(spaces),
MoveLeft(1).to_string().repeat(len-left)
).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 }
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();
if !message.is_empty() {
replace_input(cursor, message.chars().count(), "");
input.write().unwrap().clear();
cursor = 0;
history.push(String::new());
history_cursor = history.len()-1;
if message.starts_with("/") && !ctx.disable_commands {
on_command(ctx.clone(), &message)?;
} else {
let message = prepare_message(
ctx.clone(),
&ctx.message_format
.replace("{name}", &ctx.name)
.replace("{text}", &message)
);
if ctx.enable_auth {
send_message_auth(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
} else {
send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
}
}
} else {
print_console(
ctx.clone(),
messages.messages(),
""
)?;
}
}
KeyCode::Backspace => {
if cursor == 0 || !(0..=history[history_cursor].len()).contains(&(cursor)) {
continue
}
let len = input.read().unwrap().chars().count();
let i = char_index_to_byte_index(&history[history_cursor], cursor-1);
history[history_cursor].remove(i);
*input.write().unwrap() = history[history_cursor].clone();
replace_input_left(cursor, len, &history[history_cursor], cursor-1);
cursor -= 1;
}
KeyCode::Delete => {
if cursor == 0 || !(0..history[history_cursor].len()).contains(&(cursor)) {
continue
}
let len = input.read().unwrap().chars().count();
let i = char_index_to_byte_index(&history[history_cursor], cursor);
history[history_cursor].remove(i);
*input.write().unwrap() = history[history_cursor].clone();
replace_input_left(cursor, len, &history[history_cursor], cursor);
}
KeyCode::Esc => {
on_close();
break;
}
KeyCode::Up | KeyCode::Down => {
history_cursor = if event.code == KeyCode::Up {
max(history_cursor, 1) - 1
} else {
min(history_cursor + 1, history.len() - 1)
};
let len = input.read().unwrap().chars().count();
*input.write().unwrap() = history[history_cursor].clone();
replace_input(cursor, len, &history[history_cursor]);
cursor = history[history_cursor].chars().count();
}
KeyCode::PageUp => {
let height = terminal::size().unwrap().1 as usize;
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+height, ctx.messages.messages().len()), Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
}
KeyCode::PageDown => {
let height = terminal::size().unwrap().1 as usize;
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), height)-height, Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
}
KeyCode::Left => {
if cursor > 0 {
cursor -= 1;
write!(stdout(), "{}", MoveLeft(1).to_string(), ).unwrap();
stdout().lock().flush().unwrap();
}
}
KeyCode::Right => {
if cursor < history[history_cursor].len() {
cursor += 1;
write!(stdout(), "{}", MoveRight(1).to_string(), ).unwrap();
stdout().lock().flush().unwrap();
}
}
KeyCode::Char(c) => {
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
on_close();
break;
}
let i = char_index_to_byte_index(&history[history_cursor], cursor);
history[history_cursor].insert(i, c);
input.write().unwrap().insert(i, c);
write!(stdout(), "{}{}",
history[history_cursor][i..].to_string(),
MoveLeft(1).to_string().repeat(history[history_cursor].chars().count()-cursor-1)
).unwrap();
stdout().lock().flush().unwrap();
cursor += 1;
}
_ => {}
}
},
Event::Paste(data) => {
let i = char_index_to_byte_index(&history[history_cursor], cursor);
history[history_cursor].insert_str(i, &data);
input.write().unwrap().insert_str(i, &data);
write!(stdout(), "{}{}",
history[history_cursor][cursor..].to_string(),
MoveLeft(1).to_string().repeat(history[history_cursor].len()-cursor-1)
).unwrap();
stdout().lock().flush().unwrap();
cursor += data.len();
},
Event::Resize(_, _) => {
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
},
Event::Mouse(data) => {
match data.kind {
MouseEventKind::ScrollUp => {
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+3, ctx.messages.messages().len()), Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
},
MouseEventKind::ScrollDown => {
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), 3)-3, Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
},
_ => {}
}
}
_ => {}
}
}
Ok(())
}
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
match read_messages(
&mut connect(&ctx.host, ctx.enable_ssl)?,
ctx.max_messages,
ctx.messages.packet_size(),
!ctx.enable_ssl,
ctx.enable_chunked
) {
Ok(Some((messages, size))) => {
let messages: Vec<String> = if ctx.disable_formatting {
messages
} else {
messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect()
};
if ctx.enable_chunked {
ctx.messages.append(messages.clone(), size);
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
} else {
ctx.messages.update(messages.clone(), size);
print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?;
}
}
Err(e) => {
println!("{:?}", e);
}
_ => {}
}
thread::sleep(Duration::from_millis(ctx.update_time as u64));
Ok(())
}
pub fn on_close() {
disable_raw_mode().unwrap();
execute!(stdout(), event::DisableMouseCapture).unwrap();
}
pub fn run_main_loop(ctx: Arc<Context>) {
enable_raw_mode().unwrap();
execute!(stdout(), event::EnableMouseCapture).unwrap();
thread::spawn({
let ctx = ctx.clone();
move || {
loop {
recv_tick(ctx.clone()).expect("Error printing console");
}
}
});
poll_events(ctx).expect("Error while polling events");
}

67
src/chat/minimal_tui.rs Normal file
View File

@ -0,0 +1,67 @@
use std::sync::Arc;
use colored::Colorize;
use super::{
super::{
config::Context,
proto::{connect, read_messages},
util::get_input
}, format_message, on_send_message
};
pub fn run_main_loop(ctx: Arc<Context>) {
loop {
match connect(&ctx.host, ctx.enable_ssl) {
Ok(mut stream) => {
match read_messages(
&mut stream,
ctx.max_messages,
ctx.messages.packet_size(),
!ctx.enable_ssl,
ctx.enable_chunked
) {
Ok(Some((messages, size))) => {
let messages: Vec<String> = if ctx.disable_formatting {
messages
} else {
messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect()
};
if ctx.enable_chunked {
ctx.messages.append_and_store(messages.clone(), size);
} else {
ctx.messages.update(messages.clone(), size);
}
}
Err(e) => {
let msg = format!("Read messages error: {}", e.to_string()).bright_red().to_string();
ctx.messages.append(vec![msg]);
}
_ => {}
}
},
Err(e) => {
let msg = format!("Connect error: {}", e.to_string()).bright_red().to_string();
ctx.messages.append(vec![msg]);
}
}
println!(
"{}\n{} ",
ctx.messages.messages()
.into_iter()
.map(|o| o.white().blink().to_string())
.collect::<Vec<String>>()
.join("\n"),
">".bright_yellow()
);
if let Some(message) = get_input("") {
if let Err(e) = on_send_message(ctx.clone(), &message) {
let msg = format!("Send message error: {}", e.to_string()).bright_red().to_string();
ctx.messages.append(vec![msg]);
}
}
}
}

377
src/chat/pretty_tui.rs Normal file
View File

@ -0,0 +1,377 @@
use crossterm::{
cursor::{MoveLeft, MoveRight},
event::{self, Event, KeyCode, KeyModifiers, MouseEventKind},
execute,
terminal::{self, disable_raw_mode, enable_raw_mode}
};
use colored::Colorize;
use std::{
cmp::{max, min},
error::Error, io::{stdout, Write},
sync::{atomic::Ordering, Arc},
thread,
time::Duration
};
use super::{
super::{
config::Context, proto::{connect, read_messages}, util::{char_index_to_byte_index, string_chunks}
}, format_message, on_send_message
};
pub fn print_console(ctx: Arc<Context>, messages: Vec<String>, input: &str) -> Result<(), Box<dyn Error>> {
let (width, height) = terminal::size()?;
let (width, height) = (width as usize, height as usize);
let mut messages = messages
.into_iter()
.flat_map(|o| string_chunks(&o, width as usize - 1))
.map(|o| (o.0.white().blink().to_string(), o.1))
.collect::<Vec<(String, usize)>>();
let messages_size = if messages.len() >= height {
messages.len()-height
} else {
for _ in 0..height-messages.len() {
messages.insert(0, (String::new(), 0));
}
0
};
let scroll = min(ctx.scroll.load(Ordering::SeqCst), messages_size);
let scroll_f = ((1f64 - scroll as f64 / (messages_size+1) as f64) * (height-2) as f64).round() as usize+1;
let messages = if height < messages.len() {
if scroll < messages.len() - height {
messages[
messages.len()-height-scroll..
messages.len()-scroll
].to_vec()
} else {
if scroll < messages.len() {
messages[
0..
messages.len()-scroll
].to_vec()
} else {
vec![]
}
}
} else {
messages
};
let formatted_messages = if ctx.disable_formatting {
messages
.into_iter()
.map(|(i, _)| i)
.collect::<Vec<String>>()
} else {
messages
.into_iter()
.enumerate()
.map(|(i, (s, l))| {
format!("{}{}{}",
s,
" ".repeat(width - 1 - l),
if i == scroll_f {
"".bright_yellow()
} else {
"".yellow()
}
)
})
.collect::<Vec<String>>()
};
let text = format!(
"{}\r\n{} {}",
formatted_messages.join("\r\n"),
">".bright_yellow(),
input
);
let mut out = stdout().lock();
write!(out, "{}", text)?;
out.flush()?;
Ok(())
}
fn replace_input(cursor: usize, len: usize, text: &str) {
let spaces = if text.chars().count() < len {
len-text.chars().count()
} else {
0
};
write!(stdout(),
"{}{}{}{}",
MoveLeft(1).to_string().repeat(cursor),
text,
" ".repeat(spaces),
MoveLeft(1).to_string().repeat(spaces)
).unwrap();
stdout().lock().flush().unwrap();
}
fn replace_input_left(cursor: usize, len: usize, text: &str, left: usize) {
let spaces = if text.chars().count() < len {
len-text.chars().count()
} else {
0
};
write!(stdout(),
"{}{}{}{}",
MoveLeft(1).to_string().repeat(cursor),
text,
" ".repeat(spaces),
MoveLeft(1).to_string().repeat(len-left)
).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 }
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();
if !message.is_empty() {
replace_input(cursor, message.chars().count(), "");
input.write().unwrap().clear();
cursor = 0;
history.push(String::new());
history_cursor = history.len()-1;
if let Err(e) = on_send_message(ctx.clone(), &message) {
let msg = format!("Send message error: {}", e.to_string()).bright_red().to_string();
ctx.messages.append(vec![msg]);
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
}
} else {
print_console(
ctx.clone(),
messages.messages(),
""
)?;
}
}
KeyCode::Backspace => {
if cursor == 0 || !(0..=history[history_cursor].len()).contains(&(cursor)) {
continue
}
let len = input.read().unwrap().chars().count();
let i = char_index_to_byte_index(&history[history_cursor], cursor-1);
history[history_cursor].remove(i);
*input.write().unwrap() = history[history_cursor].clone();
replace_input_left(cursor, len, &history[history_cursor], cursor-1);
cursor -= 1;
}
KeyCode::Delete => {
if cursor == 0 || !(0..history[history_cursor].len()).contains(&(cursor)) {
continue
}
let len = input.read().unwrap().chars().count();
let i = char_index_to_byte_index(&history[history_cursor], cursor);
history[history_cursor].remove(i);
*input.write().unwrap() = history[history_cursor].clone();
replace_input_left(cursor, len, &history[history_cursor], cursor);
}
KeyCode::Esc => {
on_close();
break;
}
KeyCode::Up | KeyCode::Down => {
history_cursor = if event.code == KeyCode::Up {
max(history_cursor, 1) - 1
} else {
min(history_cursor + 1, history.len() - 1)
};
let len = input.read().unwrap().chars().count();
*input.write().unwrap() = history[history_cursor].clone();
replace_input(cursor, len, &history[history_cursor]);
cursor = history[history_cursor].chars().count();
}
KeyCode::PageUp => {
let height = terminal::size().unwrap().1 as usize;
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+height, ctx.messages.messages().len()), Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
}
KeyCode::PageDown => {
let height = terminal::size().unwrap().1 as usize;
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), height)-height, Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
}
KeyCode::Left => {
if cursor > 0 {
cursor -= 1;
write!(stdout(), "{}", MoveLeft(1).to_string(), ).unwrap();
stdout().lock().flush().unwrap();
}
}
KeyCode::Right => {
if cursor < history[history_cursor].len() {
cursor += 1;
write!(stdout(), "{}", MoveRight(1).to_string(), ).unwrap();
stdout().lock().flush().unwrap();
}
}
KeyCode::Char(c) => {
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
on_close();
break;
}
let i = char_index_to_byte_index(&history[history_cursor], cursor);
history[history_cursor].insert(i, c);
input.write().unwrap().insert(i, c);
write!(stdout(), "{}{}",
history[history_cursor][i..].to_string(),
MoveLeft(1).to_string().repeat(history[history_cursor].chars().count()-cursor-1)
).unwrap();
stdout().lock().flush().unwrap();
cursor += 1;
}
_ => {}
}
},
Event::Paste(data) => {
let i = char_index_to_byte_index(&history[history_cursor], cursor);
history[history_cursor].insert_str(i, &data);
input.write().unwrap().insert_str(i, &data);
write!(stdout(), "{}{}",
history[history_cursor][cursor..].to_string(),
MoveLeft(1).to_string().repeat(history[history_cursor].len()-cursor-1)
).unwrap();
stdout().lock().flush().unwrap();
cursor += data.len();
},
Event::Resize(_, _) => {
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
},
Event::Mouse(data) => {
match data.kind {
MouseEventKind::ScrollUp => {
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+3, ctx.messages.messages().len()), Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
},
MouseEventKind::ScrollDown => {
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), 3)-3, Ordering::SeqCst);
print_console(
ctx.clone(),
messages.messages(),
&input.read().unwrap()
)?;
},
_ => {}
}
}
_ => {}
}
}
Ok(())
}
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
match read_messages(
&mut connect(&ctx.host, ctx.enable_ssl)?,
ctx.max_messages,
ctx.messages.packet_size(),
!ctx.enable_ssl,
ctx.enable_chunked
) {
Ok(Some((messages, size))) => {
let messages: Vec<String> = if ctx.disable_formatting {
messages
} else {
messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect()
};
if ctx.enable_chunked {
ctx.messages.append_and_store(messages.clone(), size);
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
} else {
ctx.messages.update(messages.clone(), size);
print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?;
}
}
Err(e) => {
let msg = format!("Read messages error: {}", e.to_string()).bright_red().to_string();
ctx.messages.append(vec![msg]);
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
}
_ => {}
}
thread::sleep(Duration::from_millis(ctx.update_time as u64));
Ok(())
}
pub fn on_close() {
disable_raw_mode().unwrap();
execute!(stdout(), event::DisableMouseCapture).unwrap();
}
pub fn run_main_loop(ctx: Arc<Context>) {
enable_raw_mode().unwrap();
execute!(stdout(), event::EnableMouseCapture).unwrap();
thread::spawn({
let ctx = ctx.clone();
move || {
loop {
if let Err(e) = recv_tick(ctx.clone()) {
let msg = format!("Print messages error: {}", e.to_string()).bright_red().to_string();
ctx.messages.append(vec![msg]);
let _ = print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap());
}
}
}
});
if let Err(e) = poll_events(ctx.clone()) {
let msg = format!("Poll events error: {}", e.to_string()).bright_red().to_string();
ctx.messages.append(vec![msg]);
let _ = print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap());
}
}

View File

@ -1,8 +1,7 @@
use std::sync::{atomic::AtomicUsize, Arc, RwLock}; use std::{str::FromStr, 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 colored::Colorize; use colored::Colorize;
use homedir::my_home;
use rand::random; use rand::random;
use serde_yml; use serde_yml;
use clap::Parser; use clap::Parser;
@ -114,18 +113,26 @@ pub fn load_config(path: PathBuf) -> Config {
} }
pub fn get_config_path() -> PathBuf { pub fn get_config_path() -> PathBuf {
let home_dir = PathBuf::from_str(".").ok();
#[cfg(feature = "homedir")]
let home_dir = {
use homedir::my_home;
my_home().ok().flatten()
};
#[allow(unused_variables)] #[allow(unused_variables)]
let config_path = Path::new("config.yml").to_path_buf(); let config_path = Path::new("config.yml").to_path_buf();
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
let config_path = { let config_path = {
let home_dir = my_home().ok().flatten().expect("Config find path error"); let home_dir = home_dir.expect("Config find path error");
home_dir.join(".config").join("bRAC").join("config.yml") home_dir.join(".config").join("bRAC").join("config.yml")
}; };
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let config_path = { let config_path = {
let home_dir = my_home().ok().flatten().expect("Config find path error"); let home_dir = home_dir.expect("Config find path error");
home_dir.join(".config").join("bRAC").join("config.yml") home_dir.join(".config").join("bRAC").join("config.yml")
}; };

5
src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
#[warn(non_snake_case)]
mod proto;
pub use proto::*;

View File

@ -1,10 +1,12 @@
use std::{error::Error, fmt::Debug, io::{Read, Write}, net::TcpStream}; use std::{error::Error, fmt::Debug, io::{Read, Write}, net::TcpStream};
use native_tls::TlsConnector;
pub trait RacStream: Read + Write + Unpin + Send + Sync + Debug {} pub trait RacStream: Read + Write + Unpin + Send + Sync + Debug {}
impl<T: Read + Write + Unpin + Send + Sync + Debug> RacStream for T {} impl<T: Read + Write + Unpin + Send + Sync + Debug> RacStream for T {}
/// Create RAC connection (also you can just TcpStream::connect)
///
/// host - host string, example: "example.com:12345", "example.com" (default port is 42666)
/// ssl - wrap with ssl client, write false if you dont know what it is
pub fn connect(host: &str, ssl: bool) -> Result<Box<dyn RacStream>, Box<dyn Error>> { pub fn connect(host: &str, ssl: bool) -> Result<Box<dyn RacStream>, Box<dyn Error>> {
let host = if host.contains(":") { let host = if host.contains(":") {
host.to_string() host.to_string()
@ -12,27 +14,59 @@ pub fn connect(host: &str, ssl: bool) -> Result<Box<dyn RacStream>, Box<dyn Erro
format!("{host}:42666") format!("{host}:42666")
}; };
Ok(if ssl { #[cfg(feature = "ssl")]
let ip: String = host.split_once(":") {
.map(|o| o.0.to_string()) use native_tls::TlsConnector;
.unwrap_or(host.clone());
Box::new(TlsConnector::builder() if ssl {
.danger_accept_invalid_certs(true) let ip: String = host.split_once(":")
.danger_accept_invalid_hostnames(true) .map(|o| o.0.to_string())
.build()? .unwrap_or(host.clone());
.connect(&ip, connect(&host, false)?)?)
} else { return Ok(Box::new(TlsConnector::builder()
Box::new(TcpStream::connect(host)?) .danger_accept_invalid_certs(true)
}) .danger_accept_invalid_hostnames(true)
.build()?
.connect(&ip, connect(&host, false)?)?))
}
}
Ok(Box::new(TcpStream::connect(host)?))
} }
pub fn send_message(stream: &mut (impl Read + Write), message: &str) -> Result<(), Box<dyn Error>> { /// Send message
///
/// stream - any stream that can be written to
/// message - message text
pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dyn Error>> {
stream.write_all(format!("\x01{message}").as_bytes())?; stream.write_all(format!("\x01{message}").as_bytes())?;
Ok(()) Ok(())
} }
pub fn send_message_auth(stream: &mut (impl Read + Write), message: &str) -> Result<(), Box<dyn Error>> { /// Register user
///
/// stream - any stream that can be written to
/// name - user name
/// password - user password
pub fn register_user(stream: &mut impl Write, name: &str, password: &str) -> Result<(), Box<dyn Error>> {
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
Ok(())
}
/// Send message with auth
///
/// stream - any stream that can be written to
/// message - message text
/// name - user name
/// password - user password
pub fn send_message_auth(stream: &mut impl Write, name: &str, password: &str, message: &str) -> Result<(), Box<dyn Error>> {
Ok(stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?)
}
/// Send message with fake auth
///
/// im rly bored to explain all of this so if you want to know just check sources
pub fn send_message_spoof_auth(stream: &mut (impl Write + Read), message: &str) -> Result<(), Box<dyn Error>> {
let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) }; let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) };
stream.write_all(format!("\x02{name}\n{name}\n{message}").as_bytes())?; stream.write_all(format!("\x02{name}\n{name}\n{message}").as_bytes())?;
@ -42,18 +76,14 @@ pub fn send_message_auth(stream: &mut (impl Read + Write), message: &str) -> Res
let name = format!("\x1f{name}"); let name = format!("\x1f{name}");
register_user(stream, &name, &name)?; register_user(stream, &name, &name)?;
let message = format!("{name}> {message}"); let message = format!("{name}> {message}");
send_message_auth(stream, &message) send_message_spoof_auth(stream, &message)
} else { } else {
Ok(()) Ok(())
} }
} }
pub fn register_user(stream: &mut (impl Read + Write), name: &str, password: &str) -> Result<(), Box<dyn Error>> { /// Skip null bytes and return first non-null byte
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?; pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
Ok(())
}
fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
loop { loop {
let mut buf = vec![0; 1]; let mut buf = vec![0; 1];
stream.read_exact(&mut buf)?; stream.read_exact(&mut buf)?;
@ -63,6 +93,14 @@ fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
} }
} }
/// Read messages
///
/// max_messages - max messages in list
/// last_size - last returned packet size
/// start_null - start with skipping null bytes
/// chunked - is chunked reading enabled
///
/// returns (messages, packet size)
pub fn read_messages( pub fn read_messages(
stream: &mut (impl Read + Write), stream: &mut (impl Read + Write),
max_messages: usize, max_messages: usize,

View File

@ -65,9 +65,12 @@ pub fn sanitize_text(input: &str) -> String {
} }
pub fn get_input(prompt: impl ToString) -> Option<String> { pub fn get_input(prompt: impl ToString) -> Option<String> {
let mut out = stdout().lock(); let prompt = prompt.to_string();
out.write_all(prompt.to_string().as_bytes()).ok()?; if !prompt.is_empty() {
out.flush().ok()?; let mut out = stdout().lock();
out.write_all(prompt.as_bytes()).ok()?;
out.flush().ok()?;
}
let input = stdin().lock().lines().next() let input = stdin().lock().lines().next()
.map(|o| o.ok()) .map(|o| o.ok())
.flatten()?; .flatten()?;