diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0196fd7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true + } +} \ No newline at end of file diff --git a/src/chat/config.rs b/src/chat/config.rs index 8ca6f5b..c6757d0 100644 --- a/src/chat/config.rs +++ b/src/chat/config.rs @@ -1,38 +1,69 @@ +use clap::Parser; +use serde_default::DefaultFromSerde; +use serde_yml; use std::str::FromStr; use std::{fs, path::PathBuf}; -use serde_yml; -use serde_default::DefaultFromSerde; -use clap::Parser; const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}"; -fn default_true() -> bool { true } -pub fn default_max_messages() -> usize { 200 } -pub fn default_update_time() -> usize { 100 } -pub fn default_oof_update_time() -> usize { 10000 } -pub fn default_konata_size() -> usize { 100 } -pub fn default_host() -> String { "meex.lol:11234".to_string() } -pub fn default_message_format() -> String { MESSAGE_FORMAT.to_string() } +fn default_true() -> bool { + true +} +pub fn default_max_messages() -> usize { + 200 +} +pub fn default_update_time() -> usize { + 100 +} +pub fn default_oof_update_time() -> usize { + 10000 +} +pub fn default_konata_size() -> usize { + 100 +} +pub fn default_host() -> String { + "meex.lol:11234".to_string() +} +pub fn default_message_format() -> String { + MESSAGE_FORMAT.to_string() +} #[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)] pub struct Config { - #[serde(default = "default_host")] pub host: String, - #[serde(default)] pub name: Option, - #[serde(default = "default_message_format")] pub message_format: String, - #[serde(default = "default_update_time")] pub update_time: usize, - #[serde(default = "default_oof_update_time")] pub oof_update_time: usize, - #[serde(default = "default_max_messages")] pub max_messages: usize, - #[serde(default = "default_konata_size")] pub konata_size: usize, - #[serde(default)] pub remove_gui_shit: bool, - #[serde(default = "default_true")] pub hide_my_ip: bool, - #[serde(default)] pub show_other_ip: bool, - #[serde(default)] pub auth_enabled: bool, - #[serde(default = "default_true")] pub chunked_enabled: bool, - #[serde(default = "default_true")] pub formatting_enabled: bool, - #[serde(default = "default_true")] pub commands_enabled: bool, - #[serde(default)] pub proxy: Option, - #[serde(default = "default_true")] pub notifications_enabled: bool, - #[serde(default)] pub debug_logs: bool, + #[serde(default = "default_host")] + pub host: String, + #[serde(default)] + pub name: Option, + #[serde(default = "default_message_format")] + pub message_format: String, + #[serde(default = "default_update_time")] + pub update_time: usize, + #[serde(default = "default_oof_update_time")] + pub oof_update_time: usize, + #[serde(default = "default_max_messages")] + pub max_messages: usize, + #[serde(default = "default_konata_size")] + pub konata_size: usize, + #[serde(default)] + pub remove_gui_shit: bool, + #[serde(default = "default_true")] + pub hide_my_ip: bool, + #[serde(default)] + pub show_other_ip: bool, + #[serde(default)] + pub auth_enabled: bool, + #[serde(default = "default_true")] + pub chunked_enabled: bool, + #[serde(default = "default_true")] + pub formatting_enabled: bool, + #[serde(default = "default_true")] + pub commands_enabled: bool, + #[serde(default)] + pub proxy: Option, + #[serde(default = "default_true")] + pub notifications_enabled: bool, + #[serde(default)] + pub debug_logs: bool, } pub fn get_config_path() -> PathBuf { @@ -98,54 +129,105 @@ pub fn save_config(path: PathBuf, config: &Config) { #[command(version, about, long_about = None)] pub struct Args { /// Print config path - #[arg(short='p', long)] + #[arg(short = 'p', long)] pub config_path: bool, /// Print unformatted messages from chat and exit - #[arg(short='r', long)] + #[arg(short = 'r', long)] pub read_messages: bool, /// Send unformatted message to chat and exit - #[arg(short='s', long, value_name="MESSAGE")] + #[arg(short = 's', long, value_name = "MESSAGE")] pub send_message: Option, - - #[arg(short='H', long)] pub host: Option, - #[arg(short='n', long)] pub name: Option, - #[arg(long)] pub message_format: Option, - #[arg(long)] pub update_time: Option, - #[arg(long)] pub oof_update_time: Option, - #[arg(long)] pub max_messages: Option, - #[arg(long)] pub konata_size: Option, - #[arg(long)] pub hide_my_ip: Option, - #[arg(long)] pub show_other_ip: Option, - #[arg(long)] pub auth_enabled:Option , - #[arg(long)] pub remove_gui_shit: Option, - #[arg(long)] pub chunked_enabled: Option, - #[arg(long)] pub formatting_enabled: Option, - #[arg(long)] pub commands_enabled: Option, - #[arg(long)] pub notifications_enabled: Option, - #[arg(long)] pub proxy: Option, - #[arg(long)] pub debug_logs: bool, + + #[arg(short = 'H', long)] + pub host: Option, + #[arg(short = 'n', long)] + pub name: Option, + #[arg(long)] + pub message_format: Option, + #[arg(long)] + pub update_time: Option, + #[arg(long)] + pub oof_update_time: Option, + #[arg(long)] + pub max_messages: Option, + #[arg(long)] + pub konata_size: Option, + #[arg(long)] + pub hide_my_ip: Option, + #[arg(long)] + pub show_other_ip: Option, + #[arg(long)] + pub auth_enabled: Option, + #[arg(long)] + pub remove_gui_shit: Option, + #[arg(long)] + pub chunked_enabled: Option, + #[arg(long)] + pub formatting_enabled: Option, + #[arg(long)] + pub commands_enabled: Option, + #[arg(long)] + pub notifications_enabled: Option, + #[arg(long)] + pub proxy: Option, + #[arg(long)] + pub debug_logs: bool, } impl Args { pub fn patch_config(&self, config: &mut Config) { - if let Some(v) = self.host.clone() { config.host = v } - if let Some(v) = self.name.clone() { config.name = Some(v) } - if let Some(v) = self.proxy.clone() { config.proxy = Some(v) } - if let Some(v) = self.message_format.clone() { config.message_format = v } - if let Some(v) = self.update_time { config.update_time = v } - if let Some(v) = self.oof_update_time { config.oof_update_time = v } - if let Some(v) = self.max_messages { config.max_messages = v } - if let Some(v) = self.konata_size { config.konata_size = v } - if let Some(v) = self.hide_my_ip { config.hide_my_ip = v } - if let Some(v) = self.show_other_ip { config.show_other_ip = v } - if let Some(v) = self.remove_gui_shit { config.remove_gui_shit = v } - if let Some(v) = self.auth_enabled { config.auth_enabled = v } - if let Some(v) = self.chunked_enabled { config.chunked_enabled = v } - if let Some(v) = self.formatting_enabled { config.formatting_enabled = v } - if let Some(v) = self.commands_enabled { config.commands_enabled = v } - if let Some(v) = self.notifications_enabled { config.notifications_enabled = v } - if self.debug_logs { config.debug_logs = true } + if let Some(v) = self.host.clone() { + config.host = v + } + if let Some(v) = self.name.clone() { + config.name = Some(v) + } + if let Some(v) = self.proxy.clone() { + config.proxy = Some(v) + } + if let Some(v) = self.message_format.clone() { + config.message_format = v + } + if let Some(v) = self.update_time { + config.update_time = v + } + if let Some(v) = self.oof_update_time { + config.oof_update_time = v + } + if let Some(v) = self.max_messages { + config.max_messages = v + } + if let Some(v) = self.konata_size { + config.konata_size = v + } + if let Some(v) = self.hide_my_ip { + config.hide_my_ip = v + } + if let Some(v) = self.show_other_ip { + config.show_other_ip = v + } + if let Some(v) = self.remove_gui_shit { + config.remove_gui_shit = v + } + if let Some(v) = self.auth_enabled { + config.auth_enabled = v + } + if let Some(v) = self.chunked_enabled { + config.chunked_enabled = v + } + if let Some(v) = self.formatting_enabled { + config.formatting_enabled = v + } + if let Some(v) = self.commands_enabled { + config.commands_enabled = v + } + if let Some(v) = self.notifications_enabled { + config.notifications_enabled = v + } + if self.debug_logs { + config.debug_logs = true + } } } diff --git a/src/chat/ctx.rs b/src/chat/ctx.rs index edd993f..cd88002 100644 --- a/src/chat/ctx.rs +++ b/src/chat/ctx.rs @@ -1,4 +1,8 @@ -use std::sync::{atomic::{AtomicBool, AtomicUsize, Ordering}, mpsc::Sender, Arc, RwLock}; +use std::sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + mpsc::Sender, + Arc, RwLock, +}; use rand::random; @@ -11,7 +15,7 @@ pub struct Context { pub messages: RwLock>, pub packet_size: AtomicUsize, pub name: RwLock, - pub is_focused: AtomicBool + pub is_focused: AtomicBool, } impl Context { @@ -22,8 +26,13 @@ impl Context { sender: RwLock::new(None), messages: RwLock::new(Vec::new()), packet_size: AtomicUsize::default(), - name: RwLock::new(config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::()))), - is_focused: AtomicBool::new(true) + name: RwLock::new( + config + .name + .clone() + .unwrap_or_else(|| format!("Anon#{:X}", random::())), + ), + is_focused: AtomicBool::new(true), } } @@ -33,13 +42,16 @@ impl Context { pub fn set_config(&self, config: &Config) { *self.config.write().unwrap() = config.clone(); - *self.name.write().unwrap() = config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::())); + *self.name.write().unwrap() = config + .name + .clone() + .unwrap_or_else(|| format!("Anon#{:X}", random::())); *self.registered.write().unwrap() = None; *self.messages.write().unwrap() = Vec::new(); self.packet_size.store(0, Ordering::SeqCst); } - pub fn config(&self, map: fn (&Config) -> T) -> T { + pub fn config(&self, map: fn(&Config) -> T) -> T { map(&self.config.read().unwrap()) } @@ -51,7 +63,12 @@ impl Context { self.messages.read().unwrap().clone() } - pub fn put_messages_packet(&self, max_length: usize, messages: Vec, packet_size: usize) { + pub fn put_messages_packet( + &self, + max_length: usize, + messages: Vec, + packet_size: usize, + ) { self.packet_size.store(packet_size, Ordering::SeqCst); let mut messages = messages; if messages.len() > max_length { @@ -60,7 +77,12 @@ impl Context { *self.messages.write().unwrap() = messages; } - pub fn add_messages_packet(&self, max_length: usize, messages: Vec, packet_size: usize) { + pub fn add_messages_packet( + &self, + max_length: usize, + messages: Vec, + packet_size: usize, + ) { self.packet_size.store(packet_size, Ordering::SeqCst); self.add_message(max_length, messages); } @@ -75,10 +97,10 @@ impl Context { #[macro_export] macro_rules! connect_rac { - ($ctx:ident) => { + ($ctx:ident) => { &mut connect( - &$ctx.config(|o| o.host.clone()), - $ctx.config(|o| o.proxy.clone()) - )? + &$ctx.config(|o| o.host.clone()), + $ctx.config(|o| o.proxy.clone()), + )? }; -} \ No newline at end of file +} diff --git a/src/chat/gui.rs b/src/chat/gui.rs index 8c46ef6..a971539 100644 --- a/src/chat/gui.rs +++ b/src/chat/gui.rs @@ -1,34 +1,37 @@ -use std::sync::{atomic::Ordering, mpsc::channel, Arc, RwLock}; use std::cell::RefCell; -use std::time::{Duration, SystemTime}; -use std::thread; use std::error::Error; +use std::sync::{atomic::Ordering, mpsc::channel, Arc, RwLock}; +use std::thread; +use std::time::{Duration, SystemTime}; use chrono::Local; use gtk4::{self as gtk}; -use gtk::gdk_pixbuf::{Pixbuf, PixbufAnimation, PixbufLoader}; -use gtk::prelude::*; use gtk::gdk::{Cursor, Display, Texture}; +use gtk::gdk_pixbuf::{Pixbuf, PixbufAnimation, PixbufLoader}; use gtk::gio::{self, ActionEntry, ApplicationFlags, MemoryInputStream, Menu}; use gtk::glib::clone; use gtk::glib::{ - self, clone::Downgrade, - timeout_add_local, - source::timeout_add_local_once, + self, clone::Downgrade, source::timeout_add_local_once, timeout_add_local, timeout_add_once, ControlFlow, - timeout_add_once }; use gtk::pango::WrapMode; +use gtk::prelude::*; use gtk::{ - AboutDialog, Align, Application, ApplicationWindow, Box as GtkBox, - Button, Calendar, CheckButton, CssProvider, Entry, Fixed, GestureClick, - Justification, Label, ListBox, Orientation, Overlay, Picture, ScrolledWindow, Settings, Window + AboutDialog, Align, Application, ApplicationWindow, Box as GtkBox, Button, Calendar, + CheckButton, CssProvider, Entry, Fixed, GestureClick, Justification, Label, ListBox, + Orientation, Overlay, Picture, ScrolledWindow, Settings, Window, }; -use super::{config::{default_max_messages, default_update_time, default_konata_size, default_oof_update_time, get_config_path, save_config, Config}, -ctx::Context, on_send_message, parse_message, print_message, recv_tick, sanitize_message, SERVER_LIST}; +use super::{ + config::{ + default_konata_size, default_max_messages, default_oof_update_time, default_update_time, + get_config_path, save_config, Config, + }, + ctx::Context, + on_send_message, parse_message, print_message, recv_tick, sanitize_message, SERVER_LIST, +}; struct UiModel { is_dark_theme: bool, @@ -39,7 +42,7 @@ struct UiModel { #[cfg(feature = "libnotify")] notifications: Arc>>, #[cfg(not(feature = "libnotify"))] - notifications: Arc>> + notifications: Arc>>, } thread_local!( @@ -47,11 +50,23 @@ thread_local!( ); pub fn clear_chat_messages(ctx: Arc, messages: Vec) { - let _ = ctx.sender.read().unwrap().clone().unwrap().send((messages, true)); + let _ = ctx + .sender + .read() + .unwrap() + .clone() + .unwrap() + .send((messages, true)); } pub fn add_chat_messages(ctx: Arc, messages: Vec) { - let _ = ctx.sender.read().unwrap().clone().unwrap().send((messages, false)); + let _ = ctx + .sender + .read() + .unwrap() + .clone() + .unwrap() + .send((messages, false)); } fn load_pixbuf(data: &[u8]) -> Result> { @@ -62,97 +77,83 @@ fn load_pixbuf(data: &[u8]) -> Result> { } macro_rules! gui_entry_setting { - ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => { - { - let hbox = GtkBox::new(Orientation::Horizontal, 5); + ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {{ + let hbox = GtkBox::new(Orientation::Horizontal, 5); - hbox.append(&Label::builder() - .label($e) - .build()); + hbox.append(&Label::builder().label($e).build()); - let entry = Entry::builder() - .text(&$ctx.config(|o| o.$i.clone())) - .build(); + let entry = Entry::builder() + .text(&$ctx.config(|o| o.$i.clone())) + .build(); - hbox.append(&entry); + hbox.append(&entry); - $vbox.append(&hbox); + $vbox.append(&hbox); - entry - } - }; + entry + }}; } macro_rules! gui_usize_entry_setting { - ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => { - { - let hbox = GtkBox::new(Orientation::Horizontal, 5); + ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {{ + let hbox = GtkBox::new(Orientation::Horizontal, 5); - hbox.append(&Label::builder() - .label($e) - .build()); + hbox.append(&Label::builder().label($e).build()); - let entry = Entry::builder() - .text(&$ctx.config(|o| o.$i.to_string())) - .build(); + let entry = Entry::builder() + .text(&$ctx.config(|o| o.$i.to_string())) + .build(); - hbox.append(&entry); + hbox.append(&entry); - $vbox.append(&hbox); + $vbox.append(&hbox); - entry - } - }; + entry + }}; } macro_rules! gui_option_entry_setting { - ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => { - { - let hbox = GtkBox::new(Orientation::Horizontal, 5); + ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {{ + let hbox = GtkBox::new(Orientation::Horizontal, 5); - hbox.append(&Label::builder() - .label($e) - .build()); + hbox.append(&Label::builder().label($e).build()); - let entry = Entry::builder() - .text(&$ctx.config(|o| o.$i.clone()).unwrap_or_default()) - .build(); + let entry = Entry::builder() + .text(&$ctx.config(|o| o.$i.clone()).unwrap_or_default()) + .build(); - hbox.append(&entry); + hbox.append(&entry); - $vbox.append(&hbox); + $vbox.append(&hbox); - entry - } - }; + entry + }}; } macro_rules! gui_checkbox_setting { - ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => { - { - let hbox = GtkBox::new(Orientation::Horizontal, 5); + ($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {{ + let hbox = GtkBox::new(Orientation::Horizontal, 5); - hbox.append(&Label::builder() - .label($e) - .build()); + hbox.append(&Label::builder().label($e).build()); - let entry = CheckButton::builder() - .active($ctx.config(|o| o.$i)) - .build(); + let entry = CheckButton::builder().active($ctx.config(|o| o.$i)).build(); - hbox.append(&entry); + hbox.append(&entry); - $vbox.append(&hbox); + $vbox.append(&hbox); - entry - } - }; + entry + }}; } fn update_window_title(ctx: Arc) { GLOBAL.with(|global| { if let Some(ui) = &*global.borrow() { - ui.window.set_title(Some(&format!("bRAC - Connected to {} as {}", ctx.config(|o| o.host.clone()), &ctx.name()))) + ui.window.set_title(Some(&format!( + "bRAC - Connected to {} as {}", + ctx.config(|o| o.host.clone()), + &ctx.name() + ))) } }) } @@ -169,21 +170,41 @@ fn open_settings(ctx: Arc, app: &Application) { let host_entry = gui_entry_setting!("Host", host, ctx, settings_vbox); let name_entry = gui_option_entry_setting!("Name", name, ctx, settings_vbox); - let message_format_entry = gui_entry_setting!("Message Format", message_format, ctx, settings_vbox); + let message_format_entry = + gui_entry_setting!("Message Format", message_format, ctx, settings_vbox); let proxy_entry = gui_option_entry_setting!("Socks5 proxy", proxy, ctx, settings_vbox); - let update_time_entry = gui_usize_entry_setting!("Update Time", update_time, ctx, settings_vbox); - let oof_update_time_entry = gui_usize_entry_setting!("Out-of-focus Update Time", oof_update_time, ctx, settings_vbox); - let max_messages_entry = gui_usize_entry_setting!("Max Messages", max_messages, ctx, settings_vbox); + let update_time_entry = + gui_usize_entry_setting!("Update Time", update_time, ctx, settings_vbox); + let oof_update_time_entry = gui_usize_entry_setting!( + "Out-of-focus Update Time", + oof_update_time, + ctx, + settings_vbox + ); + let max_messages_entry = + gui_usize_entry_setting!("Max Messages", max_messages, ctx, settings_vbox); let hide_my_ip_entry = gui_checkbox_setting!("Hide My IP", hide_my_ip, ctx, settings_vbox); - let show_other_ip_entry = gui_checkbox_setting!("Show Other IP", show_other_ip, ctx, settings_vbox); - let auth_enabled_entry = gui_checkbox_setting!("Fake Auth Enabled", auth_enabled, ctx, settings_vbox); - let chunked_enabled_entry = gui_checkbox_setting!("Chunked Enabled", chunked_enabled, ctx, settings_vbox); - let formatting_enabled_entry = gui_checkbox_setting!("Formatting Enabled", formatting_enabled, ctx, settings_vbox); - let commands_enabled_entry = gui_checkbox_setting!("Commands Enabled", commands_enabled, ctx, settings_vbox); - let notifications_enabled_entry = gui_checkbox_setting!("Notifications Enabled", notifications_enabled, ctx, settings_vbox); + let show_other_ip_entry = + gui_checkbox_setting!("Show Other IP", show_other_ip, ctx, settings_vbox); + let auth_enabled_entry = + gui_checkbox_setting!("Fake Auth Enabled", auth_enabled, ctx, settings_vbox); + let chunked_enabled_entry = + gui_checkbox_setting!("Chunked Enabled", chunked_enabled, ctx, settings_vbox); + let formatting_enabled_entry = + gui_checkbox_setting!("Formatting Enabled", formatting_enabled, ctx, settings_vbox); + let commands_enabled_entry = + gui_checkbox_setting!("Commands Enabled", commands_enabled, ctx, settings_vbox); + let notifications_enabled_entry = gui_checkbox_setting!( + "Notifications Enabled", + notifications_enabled, + ctx, + settings_vbox + ); let debug_logs_entry = gui_checkbox_setting!("Debug Logs", debug_logs, ctx, settings_vbox); - let konata_size_entry = gui_usize_entry_setting!("Konata Size", konata_size, ctx, settings_vbox); - let remove_gui_shit_entry = gui_checkbox_setting!("Remove Gui Shit", remove_gui_shit, ctx, settings_vbox); + let konata_size_entry = + gui_usize_entry_setting!("Konata Size", konata_size, ctx, settings_vbox); + let remove_gui_shit_entry = + gui_checkbox_setting!("Remove Gui Shit", remove_gui_shit, ctx, settings_vbox); let scrollable = ScrolledWindow::builder() .child(&settings_vbox) @@ -193,37 +214,53 @@ fn open_settings(ctx: Arc, app: &Application) { vbox.append(&scrollable); - let save_button = Button::builder() - .label("Save") - .build(); + let save_button = Button::builder().label("Save").build(); vbox.append(&save_button); save_button.connect_clicked(clone!( - #[weak] ctx, - #[weak] host_entry, - #[weak] name_entry, - #[weak] message_format_entry, - #[weak] update_time_entry, - #[weak] max_messages_entry, - #[weak] hide_my_ip_entry, - #[weak] show_other_ip_entry, - #[weak] auth_enabled_entry, - #[weak] chunked_enabled_entry, - #[weak] formatting_enabled_entry, - #[weak] commands_enabled_entry, - #[weak] notifications_enabled_entry, - #[weak] proxy_entry, - #[weak] debug_logs_entry, - #[weak] oof_update_time_entry, - #[weak] konata_size_entry, - #[weak] remove_gui_shit_entry, + #[weak] + ctx, + #[weak] + host_entry, + #[weak] + name_entry, + #[weak] + message_format_entry, + #[weak] + update_time_entry, + #[weak] + max_messages_entry, + #[weak] + hide_my_ip_entry, + #[weak] + show_other_ip_entry, + #[weak] + auth_enabled_entry, + #[weak] + chunked_enabled_entry, + #[weak] + formatting_enabled_entry, + #[weak] + commands_enabled_entry, + #[weak] + notifications_enabled_entry, + #[weak] + proxy_entry, + #[weak] + debug_logs_entry, + #[weak] + oof_update_time_entry, + #[weak] + konata_size_entry, + #[weak] + remove_gui_shit_entry, move |_| { let config = Config { host: host_entry.text().to_string(), name: { let name = name_entry.text().to_string(); - + if name.is_empty() { None } else { @@ -233,7 +270,7 @@ fn open_settings(ctx: Arc, app: &Application) { message_format: message_format_entry.text().to_string(), update_time: { let update_time = update_time_entry.text(); - + if let Ok(update_time) = update_time.parse::() { update_time } else { @@ -244,7 +281,7 @@ fn open_settings(ctx: Arc, app: &Application) { }, oof_update_time: { let oof_update_time = oof_update_time_entry.text(); - + if let Ok(oof_update_time) = oof_update_time.parse::() { oof_update_time } else { @@ -255,7 +292,7 @@ fn open_settings(ctx: Arc, app: &Application) { }, konata_size: { let konata_size = konata_size_entry.text(); - + if let Ok(konata_size) = konata_size.parse::() { konata_size.max(0).min(200) } else { @@ -266,7 +303,7 @@ fn open_settings(ctx: Arc, app: &Application) { }, max_messages: { let max_messages = max_messages_entry.text(); - + if let Ok(max_messages) = max_messages.parse::() { max_messages } else { @@ -286,13 +323,13 @@ fn open_settings(ctx: Arc, app: &Application) { debug_logs: debug_logs_entry.is_active(), proxy: { let proxy = proxy_entry.text().to_string(); - + if proxy.is_empty() { None } else { Some(proxy) } - } + }, }; ctx.set_config(&config); save_config(get_config_path(), &config); @@ -300,27 +337,39 @@ fn open_settings(ctx: Arc, app: &Application) { } )); - let reset_button = Button::builder() - .label("Reset all") - .build(); + let reset_button = Button::builder().label("Reset all").build(); vbox.append(&reset_button); reset_button.connect_clicked(clone!( - #[weak] ctx, - #[weak] host_entry, - #[weak] name_entry, - #[weak] message_format_entry, - #[weak] update_time_entry, - #[weak] max_messages_entry, - #[weak] hide_my_ip_entry, - #[weak] show_other_ip_entry, - #[weak] auth_enabled_entry, - #[weak] chunked_enabled_entry, - #[weak] formatting_enabled_entry, - #[weak] commands_enabled_entry, - #[weak] notifications_enabled_entry, - #[weak] proxy_entry, + #[weak] + ctx, + #[weak] + host_entry, + #[weak] + name_entry, + #[weak] + message_format_entry, + #[weak] + update_time_entry, + #[weak] + max_messages_entry, + #[weak] + hide_my_ip_entry, + #[weak] + show_other_ip_entry, + #[weak] + auth_enabled_entry, + #[weak] + chunked_enabled_entry, + #[weak] + formatting_enabled_entry, + #[weak] + commands_enabled_entry, + #[weak] + notifications_enabled_entry, + #[weak] + proxy_entry, move |_| { let config = Config::default(); ctx.set_config(&config); @@ -365,7 +414,7 @@ fn open_settings(ctx: Arc, app: &Application) { }); window.add_controller(controller); - + window.present(); } @@ -387,7 +436,8 @@ fn build_menu(ctx: Arc, app: &Application) { app.add_action_entries([ ActionEntry::builder("settings") .activate(clone!( - #[weak] ctx, + #[weak] + ctx, move |a: &Application, _, _| { open_settings(ctx, a); } @@ -400,12 +450,14 @@ fn build_menu(ctx: Arc, app: &Application) { .build(), ActionEntry::builder("about") .activate(clone!( - #[weak] app, + #[weak] + app, move |_, _, _| { - AboutDialog::builder() + AboutDialog::builder() .application(&app) .authors(["MeexReay"]) - .license(" DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + .license( + " DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar @@ -417,24 +469,29 @@ fn build_menu(ctx: Arc, app: &Application) { DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 0. You just DO WHAT THE FUCK YOU WANT TO.") + 0. You just DO WHAT THE FUCK YOU WANT TO.", + ) .comments("better RAC client") .website("https://github.com/MeexReay/bRAC") .website_label("source code") - .logo(&Texture::for_pixbuf(&load_pixbuf(include_bytes!("images/icon.png")).unwrap())) + .logo(&Texture::for_pixbuf( + &load_pixbuf(include_bytes!("images/icon.png")).unwrap(), + )) .build() .present(); } )) - .build() + .build(), ]); } fn build_ui(ctx: Arc, app: &Application) -> UiModel { let is_dark_theme = if let Some(settings) = Settings::default() { - settings.is_gtk_application_prefer_dark_theme() || settings.gtk_theme_name() - .map(|o| o.to_lowercase().contains("dark")) - .unwrap_or_default() + settings.is_gtk_application_prefer_dark_theme() + || settings + .gtk_theme_name() + .map(|o| o.to_lowercase().contains("dark")) + .unwrap_or_default() } else { false }; @@ -452,11 +509,13 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { let remove_gui_shit = ctx.config(|c| c.remove_gui_shit); if !remove_gui_shit { - widget_box.append(&Calendar::builder() - .css_classes(["calendar"]) - .show_heading(false) - .can_target(false) - .build()); + widget_box.append( + &Calendar::builder() + .css_classes(["calendar"]) + .show_heading(false) + .can_target(false) + .build(), + ); } let server_list_vbox = GtkBox::new(Orientation::Vertical, 5); @@ -466,15 +525,13 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { for url in SERVER_LIST.iter() { let url = url.to_string(); - let label = Label::builder() - .label(&url) - .halign(Align::Start) - .build(); + let label = Label::builder().label(&url).halign(Align::Start).build(); let click = GestureClick::new(); click.connect_pressed(clone!( - #[weak] ctx, + #[weak] + ctx, move |_, _, _, _| { let mut config = ctx.config.read().unwrap().clone(); config.host = url.clone(); @@ -501,10 +558,15 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { let konata_size = ctx.config(|c| c.konata_size) as i32; - let konata = Picture::for_pixbuf(&load_pixbuf(include_bytes!("images/konata.png")).unwrap()); + let konata = + Picture::for_pixbuf(&load_pixbuf(include_bytes!("images/konata.png")).unwrap()); konata.set_size_request(174 * konata_size / 100, 127 * konata_size / 100); - - fixed.put(&konata, (499 - 174 * konata_size / 100) as f64, (131 - 127 * konata_size / 100) as f64); + + fixed.put( + &konata, + (499 - 174 * konata_size / 100) as f64, + (131 - 127 * konata_size / 100) as f64, + ); let logo_gif = include_bytes!("images/logo.gif"); @@ -512,11 +574,11 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { logo.set_size_request(152 * konata_size / 100, 64 * konata_size / 100); let logo_anim = PixbufAnimation::from_stream( - &MemoryInputStream::from_bytes( - &glib::Bytes::from(logo_gif) - ), - None::<&gio::Cancellable> - ).unwrap().iter(Some(SystemTime::now())); + &MemoryInputStream::from_bytes(&glib::Bytes::from(logo_gif)), + None::<&gio::Cancellable>, + ) + .unwrap() + .iter(Some(SystemTime::now())); timeout_add_local(Duration::from_millis(30), { let logo = logo.clone(); @@ -531,9 +593,13 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { ControlFlow::Continue } }); - + // 262, 4 - fixed.put(&logo, (436 - 174 * konata_size / 100) as f64, (131 - 127 * konata_size / 100) as f64); + fixed.put( + &logo, + (436 - 174 * konata_size / 100) as f64, + (131 - 127 * konata_size / 100) as f64, + ); let time = Label::builder() .label(&Local::now().format("%H:%M").to_string()) @@ -555,7 +621,6 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { fixed.set_halign(Align::End); widget_box_overlay.add_overlay(&fixed); - } widget_box_overlay.set_child(Some(&widget_box)); @@ -599,16 +664,24 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { .build(); send_btn.connect_clicked(clone!( - #[weak] text_entry, - #[weak] ctx, + #[weak] + text_entry, + #[weak] + ctx, move |_| { - if text_entry.text().is_empty() { return; } - timeout_add_local_once(Duration::ZERO, clone!( - #[weak] text_entry, - move || { - text_entry.set_text(""); - } - )); + if text_entry.text().is_empty() { + return; + } + timeout_add_local_once( + Duration::ZERO, + clone!( + #[weak] + text_entry, + move || { + text_entry.set_text(""); + } + ), + ); if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) { if ctx.config(|o| o.debug_logs) { @@ -620,16 +693,24 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { )); text_entry.connect_activate(clone!( - #[weak] text_entry, - #[weak] ctx, + #[weak] + text_entry, + #[weak] + ctx, move |_| { - if text_entry.text().is_empty() { return; } - timeout_add_local_once(Duration::ZERO, clone!( - #[weak] text_entry, - move || { - text_entry.set_text(""); - } - )); + if text_entry.text().is_empty() { + return; + } + timeout_add_local_once( + Duration::ZERO, + clone!( + #[weak] + text_entry, + move || { + text_entry.set_text(""); + } + ), + ); if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) { if ctx.config(|o| o.debug_logs) { @@ -648,17 +729,22 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { timeout_add_local_once(Duration::ZERO, { let scrolled_window_weak = scrolled_window_weak.clone(); - + move || { if let Some(o) = scrolled_window_weak.upgrade() { - o.vadjustment().set_value(o.vadjustment().upper() - o.vadjustment().page_size()); + o.vadjustment() + .set_value(o.vadjustment().upper() - o.vadjustment().page_size()); } } }); let window = ApplicationWindow::builder() .application(app) - .title(format!("bRAC - Connected to {} as {}", ctx.config(|o| o.host.clone()), &ctx.name())) + .title(format!( + "bRAC - Connected to {} as {}", + ctx.config(|o| o.host.clone()), + &ctx.name() + )) .default_width(500) .default_height(500) .resizable(true) @@ -674,7 +760,8 @@ fn build_ui(ctx: Arc, app: &Application) -> UiModel { let scrolled_window_weak = scrolled_window_weak.clone(); timeout_add_local_once(Duration::ZERO, move || { if let Some(o) = scrolled_window_weak.upgrade() { - o.vadjustment().set_value(o.vadjustment().upper() - o.vadjustment().page_size()); + o.vadjustment() + .set_value(o.vadjustment().upper() - o.vadjustment().page_size()); } }); } @@ -701,7 +788,7 @@ fn setup(_: &Application, ctx: Arc, ui: UiModel) { *ctx.sender.write().unwrap() = Some(Arc::new(sender)); run_recv_loop(ctx.clone()); - + ui.window.connect_notify(Some("is-active"), { let ctx = ctx.clone(); @@ -765,7 +852,7 @@ fn setup(_: &Application, ctx: Arc, ui: UiModel) { fn load_css(is_dark_theme: bool) { let provider = CssProvider::new(); provider.load_from_data(&format!( - "{}\n{}", + "{}\n{}", if is_dark_theme { include_str!("styles/dark.css") } else { @@ -788,7 +875,9 @@ fn send_notification(_: Arc, ui: &UiModel, title: &str, message: &str) let notification = Notification::new(title, message, None); notification.set_app_name("bRAC"); let pixbuf_loader = gdk_pixbuf::PixbufLoader::new(); - pixbuf_loader.loader_write(include_bytes!("images/icon.png")).unwrap(); + pixbuf_loader + .loader_write(include_bytes!("images/icon.png")) + .unwrap(); pixbuf_loader.close().unwrap(); notification.set_image_from_pixbuf(&pixbuf_loader.get_pixbuf().unwrap()); notification.show().expect("libnotify send error"); @@ -798,7 +887,10 @@ fn send_notification(_: Arc, ui: &UiModel, title: &str, message: &str) #[cfg(not(feature = "libnotify"))] fn send_notification(_: Arc, ui: &UiModel, title: &str, message: &str) { - use std::{hash::{DefaultHasher, Hasher}, time::UNIX_EPOCH}; + use std::{ + hash::{DefaultHasher, Hasher}, + time::UNIX_EPOCH, + }; use gtk4::gio::Notification; @@ -806,7 +898,14 @@ fn send_notification(_: Arc, ui: &UiModel, title: &str, message: &str) hash.write(title.as_bytes()); hash.write(message.as_bytes()); - let id = format!("bRAC-{}-{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), hash.finish()); + let id = format!( + "bRAC-{}-{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + hash.finish() + ); let notif = Notification::new(title); notif.set_body(Some(&message)); @@ -816,7 +915,9 @@ fn send_notification(_: Arc, ui: &UiModel, title: &str, message: &str) } fn on_add_message(ctx: Arc, ui: &UiModel, message: String, notify: bool) { - let Some(message) = sanitize_message(message) else { return; }; + let Some(message) = sanitize_message(message) else { + return; + }; if message.is_empty() { return; @@ -825,17 +926,9 @@ fn on_add_message(ctx: Arc, ui: &UiModel, message: String, notify: bool // TODO: softcode these colors let (ip_color, date_color, text_color) = if ui.is_dark_theme { - ( - "#494949", - "#929292", - "#FFFFFF" - ) + ("#494949", "#929292", "#FFFFFF") } else { - ( - "#585858", - "#292929", - "#000000" - ) + ("#585858", "#292929", "#000000") }; let mut label = String::new(); @@ -843,18 +936,33 @@ fn on_add_message(ctx: Arc, ui: &UiModel, message: String, notify: bool if let Some((date, ip, content, nick)) = parse_message(message.clone()) { if let Some(ip) = ip { if ctx.config(|o| o.show_other_ip) { - label.push_str(&format!("{} ", glib::markup_escape_text(&ip))); + label.push_str(&format!( + "{} ", + glib::markup_escape_text(&ip) + )); } } - label.push_str(&format!("[{}] ", glib::markup_escape_text(&date))); + label.push_str(&format!( + "[{}] ", + glib::markup_escape_text(&date) + )); if let Some((name, color)) = nick { - label.push_str(&format!("<{}> ", color.to_uppercase(), glib::markup_escape_text(&name))); + label.push_str(&format!( + "<{}> ", + color.to_uppercase(), + glib::markup_escape_text(&name) + )); if notify && !ui.window.is_active() { if ctx.config(|o| o.chunked_enabled) { - send_notification(ctx.clone(), ui, &format!("{}'s Message", &name), &glib::markup_escape_text(&content)); + send_notification( + ctx.clone(), + ui, + &format!("{}'s Message", &name), + &glib::markup_escape_text(&content), + ); } } } else { @@ -865,9 +973,15 @@ fn on_add_message(ctx: Arc, ui: &UiModel, message: String, notify: bool } } - label.push_str(&format!("{}", glib::markup_escape_text(&content))); + label.push_str(&format!( + "{}", + glib::markup_escape_text(&content) + )); } else { - label.push_str(&format!("{}", glib::markup_escape_text(&message))); + label.push_str(&format!( + "{}", + glib::markup_escape_text(&message) + )); if notify && !ui.window.is_active() { if ctx.config(|o| o.chunked_enabled) { @@ -875,18 +989,20 @@ fn on_add_message(ctx: Arc, ui: &UiModel, message: String, notify: bool } } } - + let hbox = GtkBox::new(Orientation::Horizontal, 2); - hbox.append(&Label::builder() - .label(&label) - .halign(Align::Start) - .valign(Align::Start) - .selectable(true) - .wrap(true) - .wrap_mode(WrapMode::WordChar) - .use_markup(true) - .build()); + hbox.append( + &Label::builder() + .label(&label) + .halign(Align::Start) + .valign(Align::Start) + .selectable(true) + .wrap(true) + .wrap_mode(WrapMode::WordChar) + .use_markup(true) + .build(), + ); hbox.set_hexpand(true); @@ -896,7 +1012,8 @@ fn on_add_message(ctx: Arc, ui: &UiModel, message: String, notify: bool GLOBAL.with(|global| { if let Some(ui) = &*global.borrow() { let o = &ui.chat_scrolled; - o.vadjustment().set_value(o.vadjustment().upper() - o.vadjustment().page_size()); + o.vadjustment() + .set_value(o.vadjustment().upper() - o.vadjustment().page_size()); } }); }); @@ -905,25 +1022,26 @@ fn on_add_message(ctx: Arc, ui: &UiModel, message: String, notify: bool fn make_recv_tick(ctx: Arc) { if let Err(e) = recv_tick(ctx.clone()) { if ctx.config(|o| o.debug_logs) { - let _ = print_message(ctx.clone(), format!("Print messages error: {}", e.to_string()).to_string()); + let _ = print_message( + ctx.clone(), + format!("Print messages error: {}", e.to_string()).to_string(), + ); } thread::sleep(Duration::from_secs(1)); } } fn run_recv_loop(ctx: Arc) { - thread::spawn(move || { - loop { - make_recv_tick(ctx.clone()); + thread::spawn(move || loop { + make_recv_tick(ctx.clone()); - thread::sleep(Duration::from_millis( - if ctx.is_focused.load(Ordering::SeqCst) { - ctx.config(|o| o.update_time) as u64 - } else { - ctx.config(|o| o.oof_update_time) as u64 - } - )); - } + thread::sleep(Duration::from_millis( + if ctx.is_focused.load(Ordering::SeqCst) { + ctx.config(|o| o.update_time) as u64 + } else { + ctx.config(|o| o.oof_update_time) as u64 + }, + )); }); } diff --git a/src/chat/mod.rs b/src/chat/mod.rs index f5e8ef4..8a11092 100644 --- a/src/chat/mod.rs +++ b/src/chat/mod.rs @@ -1,10 +1,14 @@ use std::{ - error::Error, sync::Arc, time::{SystemTime, UNIX_EPOCH} + error::Error, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, }; use crate::connect_rac; -use super::proto::{connect, read_messages, send_message, send_message_spoof_auth, register_user, send_message_auth}; +use super::proto::{ + connect, read_messages, register_user, send_message, send_message_auth, send_message_spoof_auth, +}; use gui::{add_chat_messages, clear_chat_messages}; use lazy_static::lazy_static; @@ -14,7 +18,6 @@ use ctx::Context; pub use gui::run_main_loop; - const HELP_MESSAGE: &str = "Help message: /help - show help message /register password - register user @@ -38,16 +41,15 @@ lazy_static! { ]; pub static ref SERVER_LIST: Vec = vec![ - "rac://meex.lol".to_string(), - "rac://meex.lol:11234".to_string(), + "rac://meex.lol".to_string(), + "rac://meex.lol:11234".to_string(), "rac://91.192.22.20".to_string() ]; } - -pub mod gui; pub mod config; pub mod ctx; +pub mod gui; pub fn sanitize_text(input: &str) -> String { let without_ansi = ANSI_REGEX.replace_all(input, ""); @@ -56,8 +58,7 @@ pub fn sanitize_text(input: &str) -> String { } pub fn add_message(ctx: Arc, message: &str) -> Result<(), Box> { - for i in message.split("\n") - .map(|o| o.to_string()) { + for i in message.split("\n").map(|o| o.to_string()) { print_message(ctx.clone(), i)?; } Ok(()) @@ -69,45 +70,52 @@ pub fn on_command(ctx: Arc, command: &str) -> Result<(), Box let args = args.split(" ").collect::>(); if command == "clear" { - let Some(times) = args.get(0) else { return Ok(()) }; + let Some(times) = args.get(0) else { + return Ok(()); + }; let times = times.parse()?; for _ in 0..times { send_message(connect_rac!(ctx), "\r")?; } } else if command == "spam" { - let Some(times) = args.get(0) else { return Ok(()) }; + let Some(times) = args.get(0) else { + return Ok(()); + }; let times = times.parse()?; let msg = args[1..].join(" "); for _ in 0..times { - send_message(connect_rac!(ctx), &("\r".to_string()+&msg))?; + send_message(connect_rac!(ctx), &("\r".to_string() + &msg))?; } } else if command == "help" { add_message(ctx.clone(), HELP_MESSAGE)?; } else if command == "register" { - let Some(pass) = args.get(0) else { + let Some(pass) = args.get(0) else { add_message(ctx.clone(), "please provide password as the first argument")?; - return Ok(()) + return Ok(()); }; match register_user(connect_rac!(ctx), &ctx.name(), pass) { Ok(true) => { add_message(ctx.clone(), "you was registered successfully bro")?; *ctx.registered.write().unwrap() = Some(pass.to_string()); - }, + } Ok(false) => add_message(ctx.clone(), "user with this account already exists bruh")?, - Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))? + Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))?, }; } else if command == "login" { - let Some(pass) = args.get(0) else { + let Some(pass) = args.get(0) else { add_message(ctx.clone(), "please provide password as the first argument")?; - return Ok(()) + return Ok(()); }; add_message(ctx.clone(), "ye bro you was logged in")?; *ctx.registered.write().unwrap() = Some(pass.to_string()); } else if command == "ping" { let mut before = ctx.packet_size(); - let message = format!("Checking ping... {:X}", SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis()); + let message = format!( + "Checking ping... {:X}", + SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() + ); send_message(connect_rac!(ctx), &message)?; @@ -115,11 +123,13 @@ pub fn on_command(ctx: Arc, command: &str) -> Result<(), Box loop { let data = read_messages( - connect_rac!(ctx), - ctx.config(|o| o.max_messages), - before, - ctx.config(|o| o.chunked_enabled) - ).ok().flatten(); + connect_rac!(ctx), + ctx.config(|o| o.max_messages), + before, + ctx.config(|o| o.chunked_enabled), + ) + .ok() + .flatten(); if let Some((data, size)) = data { if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) { @@ -134,7 +144,10 @@ pub fn on_command(ctx: Arc, command: &str) -> Result<(), Box } } - add_message(ctx.clone(), &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?; + add_message( + ctx.clone(), + &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()), + )?; } else { add_message(ctx.clone(), "Unknown command bruh")?; } @@ -143,17 +156,18 @@ pub fn on_command(ctx: Arc, command: &str) -> Result<(), Box } pub fn prepare_message(ctx: Arc, message: &str) -> String { - format!("{}{}{}", + format!( + "{}{}{}", if ctx.config(|o| o.hide_my_ip) { "\r\x07" } else { "" }, message, - if !ctx.config(|o| o.hide_my_ip) { - if message.chars().count() < 54 { - " ".repeat(54-message.chars().count()) - } else { + if !ctx.config(|o| o.hide_my_ip) { + if message.chars().count() < 54 { + " ".repeat(54 - message.chars().count()) + } else { String::new() } } else { @@ -172,18 +186,16 @@ pub fn recv_tick(ctx: Arc) -> Result<(), Box> { let last_size = ctx.packet_size(); match read_messages( - connect_rac!(ctx), - ctx.config(|o| o.max_messages), - ctx.packet_size(), - ctx.config(|o| o.chunked_enabled) + connect_rac!(ctx), + ctx.config(|o| o.max_messages), + ctx.packet_size(), + ctx.config(|o| o.chunked_enabled), ) { Ok(Some((messages, size))) => { if ctx.config(|o| o.chunked_enabled) { ctx.add_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size); if last_size == 0 { - if messages.len() >= 1 { - clear_chat_messages(ctx.clone(), messages); - } + clear_chat_messages(ctx.clone(), messages); } else { add_chat_messages(ctx.clone(), messages); } @@ -191,10 +203,13 @@ pub fn recv_tick(ctx: Arc) -> Result<(), Box> { ctx.put_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size); clear_chat_messages(ctx.clone(), messages); } - }, + } Err(e) => { if ctx.config(|o| o.debug_logs) { - add_chat_messages(ctx.clone(), vec![format!("Read messages error: {}", e.to_string())]); + add_chat_messages( + ctx.clone(), + vec![format!("Read messages error: {}", e.to_string())], + ); } } _ => {} @@ -208,10 +223,10 @@ pub fn on_send_message(ctx: Arc, message: &str) -> Result<(), Box, message: &str) -> Result<(), Box Option { let message = sanitize_text(&message); @@ -235,15 +250,17 @@ pub fn sanitize_message(message: String) -> Option { } /// message -> (date, ip, text, (name, color)) -pub fn parse_message(message: String) -> Option<(String, Option, String, Option<(String, String)>)> { +pub fn parse_message( + message: String, +) -> Option<(String, Option, String, Option<(String, String)>)> { if message.is_empty() { - return None + return None; } - + let date = DATE_REGEX.captures(&message)?; let (date, message) = ( - date.get(1)?.as_str().to_string(), - date.get(2)?.as_str().to_string(), + date.get(1)?.as_str().to_string(), + date.get(2)?.as_str().to_string(), ); let message = message @@ -254,11 +271,14 @@ pub fn parse_message(message: String) -> Option<(String, Option, String, .to_string(); let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) { - (Some(message.get(1)?.as_str().to_string()), message.get(2)?.as_str().to_string()) + ( + Some(message.get(1)?.as_str().to_string()), + message.get(2)?.as_str().to_string(), + ) } else { (None, message) }; - + let (message, nick) = match find_username_color(&message) { Some((name, content, color)) => (content, Some((name, color))), None => (message, None), @@ -271,7 +291,11 @@ pub fn parse_message(message: String) -> Option<(String, Option, String, pub fn find_username_color(message: &str) -> Option<(String, String, String)> { for (re, color) in COLORED_USERNAMES.iter() { if let Some(captures) = re.captures(message) { - return Some((captures[1].to_string(), captures[2].to_string(), color.clone())) + return Some(( + captures[1].to_string(), + captures[2].to_string(), + color.clone(), + )); } } None diff --git a/src/lib.rs b/src/lib.rs index b5738fd..b905304 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ #![allow(non_snake_case)] pub mod chat; -pub mod proto; \ No newline at end of file +pub mod proto; diff --git a/src/main.rs b/src/main.rs index 128bc56..c76b2db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,21 @@ use std::sync::Arc; +use bRAC::chat::{ + config::{get_config_path, load_config, Args}, + ctx::Context, + run_main_loop, +}; use bRAC::proto::{connect, read_messages, send_message}; -use bRAC::chat::{config::{get_config_path, load_config, Args}, ctx::Context, run_main_loop}; -use clap::Parser; +use clap::Parser; fn main() { #[cfg(feature = "winapi")] - unsafe { winapi::um::wincon::FreeConsole() }; + unsafe { + winapi::um::wincon::FreeConsole() + }; let args = Args::parse(); - + let config_path = get_config_path(); if args.config_path { @@ -20,26 +26,25 @@ fn main() { let mut config = load_config(config_path); if args.read_messages { - let mut stream = connect(&config.host, config.proxy.clone()).expect("Error reading message"); + let mut stream = + connect(&config.host, config.proxy.clone()).expect("Error reading message"); - print!("{}", read_messages( - &mut stream, - config.max_messages, - 0, - false - ) - .ok().flatten() - .expect("Error reading messages").0.join("\n") + print!( + "{}", + read_messages(&mut stream, config.max_messages, 0, false) + .ok() + .flatten() + .expect("Error reading messages") + .0 + .join("\n") ); } if let Some(message) = &args.send_message { - let mut stream = connect(&config.host, config.proxy.clone()).expect("Error sending message"); + let mut stream = + connect(&config.host, config.proxy.clone()).expect("Error sending message"); - send_message( - &mut stream, - message - ).expect("Error sending message"); + send_message(&mut stream, message).expect("Error sending message"); } if args.send_message.is_some() || args.read_messages { @@ -47,7 +52,7 @@ fn main() { } args.patch_config(&mut config); - + let ctx = Arc::new(Context::new(&config)); run_main_loop(ctx.clone()); diff --git a/src/proto/mod.rs b/src/proto/mod.rs index a37d0cc..64193df 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -1,4 +1,10 @@ -use std::{error::Error, fmt::Debug, io::{Read, Write}, net::{TcpStream, ToSocketAddrs}, time::Duration}; +use std::{ + error::Error, + fmt::Debug, + io::{Read, Write}, + net::{TcpStream, ToSocketAddrs}, + time::Duration, +}; use native_tls::{TlsConnector, TlsStream}; use socks::Socks5Stream; @@ -13,28 +19,44 @@ pub trait Stream: Read + Write + Unpin + Send + Sync + Debug { } impl Stream for TcpStream { - fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(&self, Some(timeout)); } - fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(&self, Some(timeout)); } + fn set_read_timeout(&self, timeout: Duration) { + let _ = TcpStream::set_read_timeout(&self, Some(timeout)); + } + fn set_write_timeout(&self, timeout: Duration) { + let _ = TcpStream::set_write_timeout(&self, Some(timeout)); + } } impl Stream for Socks5Stream { - fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout)); } - fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout)); } + fn set_read_timeout(&self, timeout: Duration) { + let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout)); + } + fn set_write_timeout(&self, timeout: Duration) { + let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout)); + } } impl Stream for TlsStream { - fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); } - fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); } + fn set_read_timeout(&self, timeout: Duration) { + self.get_ref().set_read_timeout(timeout); + } + fn set_write_timeout(&self, timeout: Duration) { + self.get_ref().set_write_timeout(timeout); + } } impl Stream for TlsStream> { - fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); } - fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); } + fn set_read_timeout(&self, timeout: Duration) { + self.get_ref().set_read_timeout(timeout); + } + fn set_write_timeout(&self, timeout: Duration) { + self.get_ref().set_write_timeout(timeout); + } } pub enum RacStream { WRAC(WebSocket>), - RAC(Box) + RAC(Box), } /// `socks5://user:pass@127.0.0.1:12345/path -> ("127.0.0.1:12345", ("user", "pass"))` \ @@ -64,46 +86,42 @@ pub fn parse_rac_url(url: &str) -> Option<(String, bool, bool)> { let (scheme, url) = url.split_once("://").unwrap_or(("rac", url)); let (host, _) = url.split_once("/").unwrap_or((url, "")); match scheme.to_lowercase().as_str() { - "rac" => { - Some(( - if host.contains(":") { - host.to_string() - } else { - format!("{host}:42666") - }, - false, false - )) - }, - "racs" => { - Some(( - if host.contains(":") { - host.to_string() - } else { - format!("{host}:42667") - }, - true, false - )) - }, - "wrac" => { - Some(( - if host.contains(":") { - host.to_string() - } else { - format!("{host}:52666") - }, - false, true - )) - }, - "wracs" => { - Some(( - if host.contains(":") { - host.to_string() - } else { - format!("{host}:52667") - }, - true, true - )) - }, + "rac" => Some(( + if host.contains(":") { + host.to_string() + } else { + format!("{host}:42666") + }, + false, + false, + )), + "racs" => Some(( + if host.contains(":") { + host.to_string() + } else { + format!("{host}:42667") + }, + true, + false, + )), + "wrac" => Some(( + if host.contains(":") { + host.to_string() + } else { + format!("{host}:52666") + }, + false, + true, + )), + "wracs" => Some(( + if host.contains(":") { + host.to_string() + } else { + format!("{host}:52667") + }, + true, + true, + )), _ => None, } } @@ -115,12 +133,18 @@ pub fn parse_rac_url(url: &str) -> Option<(String, bool, bool)> { /// proxy - socks5 proxy (host, (user, pass)) /// wrac - to use wrac protocol pub fn connect(host: &str, proxy: Option) -> Result> { - let (host, ssl, wrac) = parse_rac_url(host).ok_or::>("url parse error".into())?; + let (host, ssl, wrac) = + parse_rac_url(host).ok_or::>("url parse error".into())?; let stream: Box = if let Some(proxy) = proxy { if let Some((proxy, auth)) = parse_socks5_url(&proxy) { if let Some((user, pass)) = auth { - Box::new(Socks5Stream::connect_with_password(&proxy, host.as_str(), &user, &pass)?) + Box::new(Socks5Stream::connect_with_password( + &proxy, + host.as_str(), + &user, + &pass, + )?) } else { Box::new(Socks5Stream::connect(&proxy, host.as_str())?) } @@ -128,21 +152,27 @@ pub fn connect(host: &str, proxy: Option) -> Result>("addr parse error".into())?; + let addr = host + .to_socket_addrs()? + .next() + .ok_or::>("addr parse error".into())?; Box::new(TcpStream::connect(&addr)?) }; let stream = if ssl { - let ip: String = host.split_once(":") + let ip: String = host + .split_once(":") .map(|o| o.0.to_string()) .unwrap_or(host.clone()); - Box::new(TlsConnector::builder() - .danger_accept_invalid_certs(true) - .danger_accept_invalid_hostnames(true) - .build()? - .connect(&ip, stream)?) + Box::new( + TlsConnector::builder() + .danger_accept_invalid_certs(true) + .danger_accept_invalid_hostnames(true) + .build()? + .connect(&ip, stream)?, + ) } else { stream }; @@ -152,8 +182,8 @@ pub fn connect(host: &str, proxy: Option) -> Result) -> Result " + message) /// } -pub fn send_message_spoof_auth(stream: &mut RacStream, message: &str) -> Result<(), Box> { - let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) }; +pub fn send_message_spoof_auth( + stream: &mut RacStream, + message: &str, +) -> Result<(), Box> { + let Some((name, message)) = message.split_once("> ") else { + return send_message(stream, message); + }; if let Ok(f) = send_message_auth(stream, &name, &name, &message) { if f != 0 { @@ -185,18 +220,14 @@ pub fn send_message_spoof_auth(stream: &mut RacStream, message: &str) -> Result< Ok(()) } - /// Send message /// /// stream - any stream that can be written to /// message - message text -pub fn send_message( - stream: &mut RacStream, - message: &str -) -> Result<(), Box> { +pub fn send_message(stream: &mut RacStream, message: &str) -> Result<(), Box> { match stream { RacStream::WRAC(websocket) => wrac::send_message(websocket, message), - RacStream::RAC(stream) => rac::send_message(stream, message) + RacStream::RAC(stream) => rac::send_message(stream, message), } } @@ -209,13 +240,13 @@ pub fn send_message( /// /// returns whether the user was registered pub fn register_user( - stream: &mut RacStream, - name: &str, - password: &str + stream: &mut RacStream, + name: &str, + password: &str, ) -> Result> { match stream { RacStream::WRAC(websocket) => wrac::register_user(websocket, name, password), - RacStream::RAC(stream) => rac::register_user(stream, name, password) + RacStream::RAC(stream) => rac::register_user(stream, name, password), } } @@ -231,14 +262,14 @@ pub fn register_user( /// returns 1 if the user does not exist /// returns 2 if the password is incorrect pub fn send_message_auth( - stream: &mut RacStream, - name: &str, - password: &str, - message: &str + stream: &mut RacStream, + name: &str, + password: &str, + message: &str, ) -> Result> { match stream { RacStream::WRAC(websocket) => wrac::send_message_auth(websocket, name, password, message), - RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message) + RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message), } } @@ -251,13 +282,15 @@ pub fn send_message_auth( /// /// returns (messages, packet size) pub fn read_messages( - stream: &mut RacStream, - max_messages: usize, + stream: &mut RacStream, + max_messages: usize, last_size: usize, - chunked: bool + chunked: bool, ) -> Result, usize)>, Box> { match stream { - RacStream::WRAC(websocket) => wrac::read_messages(websocket, max_messages, last_size, chunked), - RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, chunked) + RacStream::WRAC(websocket) => { + wrac::read_messages(websocket, max_messages, last_size, chunked) + } + RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, chunked), } -} \ No newline at end of file +} diff --git a/src/proto/rac.rs b/src/proto/rac.rs index e4db5bd..c21728c 100644 --- a/src/proto/rac.rs +++ b/src/proto/rac.rs @@ -1,4 +1,7 @@ -use std::{error::Error, io::{Read, Write}}; +use std::{ + error::Error, + io::{Read, Write}, +}; /// Send message /// @@ -18,9 +21,9 @@ pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box Result> { stream.write_all(format!("\x03{name}\n{password}").as_bytes())?; if let Ok(out) = skip_null(stream) { @@ -42,9 +45,9 @@ pub fn register_user( /// returns 1 if the user does not exist /// returns 2 if the password is incorrect pub fn send_message_auth( - stream: &mut (impl Write + Read), - name: &str, - password: &str, + stream: &mut (impl Write + Read), + name: &str, + password: &str, message: &str, ) -> Result> { stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?; @@ -61,7 +64,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result, Box> { let mut buf = vec![0; 1]; stream.read_exact(&mut buf)?; if buf[0] != 0 { - break Ok(buf) + break Ok(buf); } } } @@ -69,7 +72,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result, Box> { /// remove trailing null bytes in vector pub fn remove_trailing_null(vec: &mut Vec) -> Result<(), Box> { while vec.ends_with(&[0]) { - vec.remove(vec.len()-1); + vec.remove(vec.len() - 1); } Ok(()) } @@ -83,10 +86,10 @@ pub fn remove_trailing_null(vec: &mut Vec) -> Result<(), Box> { /// /// returns (messages, packet size) pub fn read_messages( - stream: &mut (impl Read + Write), - max_messages: usize, - last_size: usize, - chunked: bool + stream: &mut (impl Read + Write), + max_messages: usize, + last_size: usize, + chunked: bool, ) -> Result, usize)>, Box> { stream.write_all(&[0x00])?; @@ -123,10 +126,16 @@ pub fn read_messages( let packet_data = String::from_utf8_lossy(&packet_data).to_string(); let lines: Vec<&str> = packet_data.split("\n").collect(); - let lines: Vec = lines.clone().into_iter() - .skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 }) + let lines: Vec = lines + .clone() + .into_iter() + .skip(if lines.len() >= max_messages { + lines.len() - max_messages + } else { + 0 + }) .map(|o| o.to_string()) .collect(); Ok(Some((lines, packet_size))) -} \ No newline at end of file +} diff --git a/src/proto/wrac.rs b/src/proto/wrac.rs index b8a269c..a62b5b0 100644 --- a/src/proto/wrac.rs +++ b/src/proto/wrac.rs @@ -1,6 +1,8 @@ -use std::{error::Error, io::{Read, Write}}; -use tungstenite::{WebSocket, Message}; - +use std::{ + error::Error, + io::{Read, Write}, +}; +use tungstenite::{Message, WebSocket}; /// Send message /// @@ -8,9 +10,11 @@ use tungstenite::{WebSocket, Message}; /// message - message text pub fn send_message( stream: &mut WebSocket, - message: &str + message: &str, ) -> Result<(), Box> { - stream.write(Message::Binary(format!("\x01{message}").as_bytes().to_vec().into()))?; + stream.write(Message::Binary( + format!("\x01{message}").as_bytes().to_vec().into(), + ))?; stream.flush()?; Ok(()) } @@ -23,11 +27,13 @@ pub fn send_message( /// /// returns whether the user was registered pub fn register_user( - stream: &mut WebSocket, - name: &str, - password: &str + stream: &mut WebSocket, + name: &str, + password: &str, ) -> Result> { - stream.write(Message::Binary(format!("\x03{name}\n{password}").as_bytes().to_vec().into()))?; + stream.write(Message::Binary( + format!("\x03{name}\n{password}").as_bytes().to_vec().into(), + ))?; stream.flush()?; if let Ok(msg) = stream.read() { Ok(!msg.is_binary() || msg.into_data().get(0).unwrap_or(&0) == &0) @@ -47,12 +53,17 @@ pub fn register_user( /// returns 1 if the user does not exist /// returns 2 if the password is incorrect pub fn send_message_auth( - stream: &mut WebSocket, - name: &str, - password: &str, - message: &str + stream: &mut WebSocket, + name: &str, + password: &str, + message: &str, ) -> Result> { - stream.write(Message::Binary(format!("\x02{name}\n{password}\n{message}").as_bytes().to_vec().into()))?; + stream.write(Message::Binary( + format!("\x02{name}\n{password}\n{message}") + .as_bytes() + .to_vec() + .into(), + ))?; stream.flush()?; if let Ok(msg) = stream.read() { if msg.is_binary() { @@ -73,10 +84,10 @@ pub fn send_message_auth( /// /// returns (messages, packet size) pub fn read_messages( - stream: &mut WebSocket, - max_messages: usize, - last_size: usize, - chunked: bool + stream: &mut WebSocket, + max_messages: usize, + last_size: usize, + chunked: bool, ) -> Result, usize)>, Box> { stream.write(Message::Binary(vec![0x00].into()))?; stream.flush()?; @@ -101,7 +112,9 @@ pub fn read_messages( stream.write(Message::Binary(vec![0x00, 0x01].into()))?; packet_size } else { - stream.write(Message::Binary(format!("\x00\x02{}", last_size).as_bytes().to_vec().into()))?; + stream.write(Message::Binary( + format!("\x00\x02{}", last_size).as_bytes().to_vec().into(), + ))?; packet_size - last_size }; stream.flush()?; @@ -119,10 +132,16 @@ pub fn read_messages( let packet_data = String::from_utf8_lossy(&packet_data).to_string(); let lines: Vec<&str> = packet_data.split("\n").collect(); - let lines: Vec = lines.clone().into_iter() - .skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 }) + let lines: Vec = lines + .clone() + .into_iter() + .skip(if lines.len() >= max_messages { + lines.len() - max_messages + } else { + 0 + }) .map(|o| o.to_string()) .collect(); Ok(Some((lines, packet_size))) -} \ No newline at end of file +}