idk what to say

This commit is contained in:
MeexReay 2025-02-11 13:03:46 +03:00
parent df13dde2c8
commit 0e850f79c7
15 changed files with 404 additions and 271 deletions

View File

@ -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"

3
.gitignore vendored
View File

@ -1 +1,2 @@
/target
/target
/result

View File

@ -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

View File

@ -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
View File

@ -0,0 +1 @@
todo

96
flake.lock generated Normal file
View 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
View 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}
'';
};
});
}

View File

@ -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
];
}

View File

@ -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) => {
}
_ => {}
}

View File

@ -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,
@ -90,4 +119,92 @@ 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))
}
}
}

View File

@ -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() {
@ -169,46 +43,23 @@ fn main() {
configure(config_path);
return;
}
let config = load_config(config_path);
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());
}

View File

@ -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
View 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())
}
}