This commit is contained in:
MeexReay 2025-06-17 00:03:25 +03:00
parent 3e75662969
commit c1e9d00d3a
10 changed files with 820 additions and 502 deletions

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
}
}

View File

@ -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<String>,
#[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<String>,
#[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<String>,
#[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<String>,
#[serde(default = "default_true")]
pub notifications_enabled: bool,
#[serde(default)]
pub debug_logs: bool,
}
pub fn get_config_path() -> PathBuf {
@ -109,43 +140,94 @@ pub struct Args {
#[arg(short = 's', long, value_name = "MESSAGE")]
pub send_message: Option<String>,
#[arg(short='H', long)] pub host: Option<String>,
#[arg(short='n', long)] pub name: Option<String>,
#[arg(long)] pub message_format: Option<String>,
#[arg(long)] pub update_time: Option<usize>,
#[arg(long)] pub oof_update_time: Option<usize>,
#[arg(long)] pub max_messages: Option<usize>,
#[arg(long)] pub konata_size: Option<usize>,
#[arg(long)] pub hide_my_ip: Option<bool>,
#[arg(long)] pub show_other_ip: Option<bool>,
#[arg(long)] pub auth_enabled:Option <bool>,
#[arg(long)] pub remove_gui_shit: Option<bool>,
#[arg(long)] pub chunked_enabled: Option<bool>,
#[arg(long)] pub formatting_enabled: Option<bool>,
#[arg(long)] pub commands_enabled: Option<bool>,
#[arg(long)] pub notifications_enabled: Option<bool>,
#[arg(long)] pub proxy: Option<String>,
#[arg(long)] pub debug_logs: bool,
#[arg(short = 'H', long)]
pub host: Option<String>,
#[arg(short = 'n', long)]
pub name: Option<String>,
#[arg(long)]
pub message_format: Option<String>,
#[arg(long)]
pub update_time: Option<usize>,
#[arg(long)]
pub oof_update_time: Option<usize>,
#[arg(long)]
pub max_messages: Option<usize>,
#[arg(long)]
pub konata_size: Option<usize>,
#[arg(long)]
pub hide_my_ip: Option<bool>,
#[arg(long)]
pub show_other_ip: Option<bool>,
#[arg(long)]
pub auth_enabled: Option<bool>,
#[arg(long)]
pub remove_gui_shit: Option<bool>,
#[arg(long)]
pub chunked_enabled: Option<bool>,
#[arg(long)]
pub formatting_enabled: Option<bool>,
#[arg(long)]
pub commands_enabled: Option<bool>,
#[arg(long)]
pub notifications_enabled: Option<bool>,
#[arg(long)]
pub proxy: Option<String>,
#[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
}
}
}

View File

@ -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<Vec<String>>,
pub packet_size: AtomicUsize,
pub name: RwLock<String>,
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::<u16>()))),
is_focused: AtomicBool::new(true)
name: RwLock::new(
config
.name
.clone()
.unwrap_or_else(|| format!("Anon#{:X}", random::<u16>())),
),
is_focused: AtomicBool::new(true),
}
}
@ -33,7 +42,10 @@ 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::<u16>()));
*self.name.write().unwrap() = config
.name
.clone()
.unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()));
*self.registered.write().unwrap() = None;
*self.messages.write().unwrap() = Vec::new();
self.packet_size.store(0, Ordering::SeqCst);
@ -51,7 +63,12 @@ impl Context {
self.messages.read().unwrap().clone()
}
pub fn put_messages_packet(&self, max_length: usize, messages: Vec<String>, packet_size: usize) {
pub fn put_messages_packet(
&self,
max_length: usize,
messages: Vec<String>,
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<String>, packet_size: usize) {
pub fn add_messages_packet(
&self,
max_length: usize,
messages: Vec<String>,
packet_size: usize,
) {
self.packet_size.store(packet_size, Ordering::SeqCst);
self.add_message(max_length, messages);
}
@ -78,7 +100,7 @@ macro_rules! connect_rac {
($ctx:ident) => {
&mut connect(
&$ctx.config(|o| o.host.clone()),
$ctx.config(|o| o.proxy.clone())
$ctx.config(|o| o.proxy.clone()),
)?
};
}

View File

@ -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<RwLock<Vec<libnotify::Notification>>>,
#[cfg(not(feature = "libnotify"))]
notifications: Arc<RwLock<Vec<String>>>
notifications: Arc<RwLock<Vec<String>>>,
}
thread_local!(
@ -47,11 +50,23 @@ thread_local!(
);
pub fn clear_chat_messages(ctx: Arc<Context>, messages: Vec<String>) {
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<Context>, messages: Vec<String>) {
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<Pixbuf, Box<dyn Error>> {
@ -62,13 +77,10 @@ fn load_pixbuf(data: &[u8]) -> Result<Pixbuf, Box<dyn Error>> {
}
macro_rules! gui_entry_setting {
($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {
{
($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()))
@ -79,18 +91,14 @@ macro_rules! gui_entry_setting {
$vbox.append(&hbox);
entry
}
};
}};
}
macro_rules! gui_usize_entry_setting {
($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {
{
($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()))
@ -101,18 +109,14 @@ macro_rules! gui_usize_entry_setting {
$vbox.append(&hbox);
entry
}
};
}};
}
macro_rules! gui_option_entry_setting {
($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {
{
($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())
@ -123,36 +127,33 @@ macro_rules! gui_option_entry_setting {
$vbox.append(&hbox);
entry
}
};
}};
}
macro_rules! gui_checkbox_setting {
($e:expr, $i:ident, $ctx:ident, $vbox:ident) => {
{
($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);
$vbox.append(&hbox);
entry
}
};
}};
}
fn update_window_title(ctx: Arc<Context>) {
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<Context>, 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,31 +214,47 @@ fn open_settings(ctx: Arc<Context>, 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(),
@ -292,7 +329,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
} else {
Some(proxy)
}
}
},
};
ctx.set_config(&config);
save_config(get_config_path(), &config);
@ -300,27 +337,39 @@ fn open_settings(ctx: Arc<Context>, 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);
@ -387,7 +436,8 @@ fn build_menu(ctx: Arc<Context>, 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<Context>, app: &Application) {
.build(),
ActionEntry::builder("about")
.activate(clone!(
#[weak] app,
#[weak]
app,
move |_, _, _| {
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 <sam@hocevar.net>
@ -417,22 +469,27 @@ fn build_menu(ctx: Arc<Context>, 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<Context>, app: &Application) -> UiModel {
let is_dark_theme = if let Some(settings) = Settings::default() {
settings.is_gtk_application_prefer_dark_theme() || settings.gtk_theme_name()
settings.is_gtk_application_prefer_dark_theme()
|| settings
.gtk_theme_name()
.map(|o| o.to_lowercase().contains("dark"))
.unwrap_or_default()
} else {
@ -452,11 +509,13 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
let remove_gui_shit = ctx.config(|c| c.remove_gui_shit);
if !remove_gui_shit {
widget_box.append(&Calendar::builder()
widget_box.append(
&Calendar::builder()
.css_classes(["calendar"])
.show_heading(false)
.can_target(false)
.build());
.build(),
);
}
let server_list_vbox = GtkBox::new(Orientation::Vertical, 5);
@ -466,15 +525,13 @@ fn build_ui(ctx: Arc<Context>, 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<Context>, 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<Context>, 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();
@ -533,7 +595,11 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
});
// 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<Context>, 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<Context>, 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,
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<Context>, 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,
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) {
@ -651,14 +732,19 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
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<Context>, 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());
}
});
}
@ -788,7 +875,9 @@ fn send_notification(_: Arc<Context>, 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<Context>, ui: &UiModel, title: &str, message: &str)
#[cfg(not(feature = "libnotify"))]
fn send_notification(_: Arc<Context>, 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<Context>, 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<Context>, ui: &UiModel, title: &str, message: &str)
}
fn on_add_message(ctx: Arc<Context>, 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<Context>, 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<Context>, 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!("<span color=\"{ip_color}\">{}</span> ", glib::markup_escape_text(&ip)));
label.push_str(&format!(
"<span color=\"{ip_color}\">{}</span> ",
glib::markup_escape_text(&ip)
));
}
}
label.push_str(&format!("<span color=\"{date_color}\">[{}]</span> ", glib::markup_escape_text(&date)));
label.push_str(&format!(
"<span color=\"{date_color}\">[{}]</span> ",
glib::markup_escape_text(&date)
));
if let Some((name, color)) = nick {
label.push_str(&format!("<span font_weight=\"bold\" color=\"{}\">&lt;{}&gt;</span> ", color.to_uppercase(), glib::markup_escape_text(&name)));
label.push_str(&format!(
"<span font_weight=\"bold\" color=\"{}\">&lt;{}&gt;</span> ",
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<Context>, ui: &UiModel, message: String, notify: bool
}
}
label.push_str(&format!("<span color=\"{text_color}\">{}</span>", glib::markup_escape_text(&content)));
label.push_str(&format!(
"<span color=\"{text_color}\">{}</span>",
glib::markup_escape_text(&content)
));
} else {
label.push_str(&format!("<span color=\"{text_color}\">{}</span>", glib::markup_escape_text(&message)));
label.push_str(&format!(
"<span color=\"{text_color}\">{}</span>",
glib::markup_escape_text(&message)
));
if notify && !ui.window.is_active() {
if ctx.config(|o| o.chunked_enabled) {
@ -878,7 +992,8 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
let hbox = GtkBox::new(Orientation::Horizontal, 2);
hbox.append(&Label::builder()
hbox.append(
&Label::builder()
.label(&label)
.halign(Align::Start)
.valign(Align::Start)
@ -886,7 +1001,8 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
.wrap(true)
.wrap_mode(WrapMode::WordChar)
.use_markup(true)
.build());
.build(),
);
hbox.set_hexpand(true);
@ -896,7 +1012,8 @@ fn on_add_message(ctx: Arc<Context>, 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,15 +1022,17 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
fn make_recv_tick(ctx: Arc<Context>) {
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<Context>) {
thread::spawn(move || {
loop {
thread::spawn(move || loop {
make_recv_tick(ctx.clone());
thread::sleep(Duration::from_millis(
@ -921,9 +1040,8 @@ fn run_recv_loop(ctx: Arc<Context>) {
ctx.config(|o| o.update_time) as u64
} else {
ctx.config(|o| o.oof_update_time) as u64
}
},
));
}
});
}

View File

@ -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
@ -44,10 +47,9 @@ lazy_static! {
];
}
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<Context>, message: &str) -> Result<(), Box<dyn Error>> {
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,13 +70,17 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
let args = args.split(" ").collect::<Vec<&str>>();
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 {
@ -86,28 +91,31 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
} else if command == "register" {
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 {
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)?;
@ -118,8 +126,10 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
connect_rac!(ctx),
ctx.config(|o| o.max_messages),
before,
ctx.config(|o| o.chunked_enabled)
).ok().flatten();
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<Context>, command: &str) -> Result<(), Box<dyn Error>
}
}
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,7 +156,8 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
}
pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
format!("{}{}{}",
format!(
"{}{}{}",
if ctx.config(|o| o.hide_my_ip) {
"\r\x07"
} else {
@ -175,15 +189,13 @@ pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
connect_rac!(ctx),
ctx.config(|o| o.max_messages),
ctx.packet_size(),
ctx.config(|o| o.chunked_enabled)
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);
}
} else {
add_chat_messages(ctx.clone(), messages);
}
@ -191,10 +203,13 @@ pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
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())],
);
}
}
_ => {}
@ -211,7 +226,7 @@ pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn E
ctx.clone(),
&ctx.config(|o| o.message_format.clone())
.replace("{name}", &ctx.name())
.replace("{text}", &message)
.replace("{text}", &message),
);
if let Some(password) = ctx.registered.read().unwrap().clone() {
@ -235,9 +250,11 @@ pub fn sanitize_message(message: String) -> Option<String> {
}
/// message -> (date, ip, text, (name, color))
pub fn parse_message(message: String) -> Option<(String, Option<String>, String, Option<(String, String)>)> {
pub fn parse_message(
message: String,
) -> Option<(String, Option<String>, String, Option<(String, String)>)> {
if message.is_empty() {
return None
return None;
}
let date = DATE_REGEX.captures(&message)?;
@ -254,7 +271,10 @@ pub fn parse_message(message: String) -> Option<(String, Option<String>, 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)
};
@ -271,7 +291,11 @@ pub fn parse_message(message: String) -> Option<(String, Option<String>, 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

View File

@ -1,12 +1,18 @@
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;
fn main() {
#[cfg(feature = "winapi")]
unsafe { winapi::um::wincon::FreeConsole() };
unsafe {
winapi::um::wincon::FreeConsole()
};
let args = Args::parse();
@ -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 {

View File

@ -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<T: Stream> Stream for TlsStream<T> {
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<Box<dyn Stream>> {
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<Box<dyn Stream>>),
RAC(Box<dyn Stream>)
RAC(Box<dyn Stream>),
}
/// `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((
"rac" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:42666")
},
false, false
))
},
"racs" => {
Some((
false,
false,
)),
"racs" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:42667")
},
true, false
))
},
"wrac" => {
Some((
true,
false,
)),
"wrac" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:52666")
},
false, true
))
},
"wracs" => {
Some((
false,
true,
)),
"wracs" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:52667")
},
true, true
))
},
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<String>) -> Result<RacStream, Box<dyn Error>> {
let (host, ssl, wrac) = parse_rac_url(host).ok_or::<Box<dyn Error>>("url parse error".into())?;
let (host, ssl, wrac) =
parse_rac_url(host).ok_or::<Box<dyn Error>>("url parse error".into())?;
let stream: Box<dyn Stream> = 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<String>) -> Result<RacStream, Box<dyn E
return Err("proxy parse error".into());
}
} else {
let addr = host.to_socket_addrs()?.next().ok_or::<Box<dyn Error>>("addr parse error".into())?;
let addr = host
.to_socket_addrs()?
.next()
.ok_or::<Box<dyn Error>>("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()
Box::new(
TlsConnector::builder()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()?
.connect(&ip, stream)?)
.connect(&ip, stream)?,
)
} else {
stream
};
@ -153,7 +183,7 @@ pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn E
if wrac {
let (client, _) = tungstenite::client(
&format!("ws{}://{host}", if ssl { "s" } else { "" }),
stream
stream,
)?;
Ok(RacStream::WRAC(client))
} else {
@ -171,8 +201,13 @@ pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn E
/// register_user(stream, name, name)
/// send_message_spoof_auth(stream, name + "> " + message)
/// }
pub fn send_message_spoof_auth(stream: &mut RacStream, message: &str) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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<dyn Error>> {
pub fn send_message(stream: &mut RacStream, message: &str) -> Result<(), Box<dyn Error>> {
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),
}
}
@ -211,11 +242,11 @@ pub fn send_message(
pub fn register_user(
stream: &mut RacStream,
name: &str,
password: &str
password: &str,
) -> Result<bool, Box<dyn Error>> {
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),
}
}
@ -234,11 +265,11 @@ pub fn send_message_auth(
stream: &mut RacStream,
name: &str,
password: &str,
message: &str
message: &str,
) -> Result<u8, Box<dyn Error>> {
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),
}
}
@ -254,10 +285,12 @@ pub fn read_messages(
stream: &mut RacStream,
max_messages: usize,
last_size: usize,
chunked: bool
chunked: bool,
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
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),
}
}

View File

@ -1,4 +1,7 @@
use std::{error::Error, io::{Read, Write}};
use std::{
error::Error,
io::{Read, Write},
};
/// Send message
///
@ -20,7 +23,7 @@ pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dy
pub fn register_user(
stream: &mut (impl Write + Read),
name: &str,
password: &str
password: &str,
) -> Result<bool, Box<dyn Error>> {
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
if let Ok(out) = skip_null(stream) {
@ -61,7 +64,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
let mut buf = vec![0; 1];
stream.read_exact(&mut buf)?;
if buf[0] != 0 {
break Ok(buf)
break Ok(buf);
}
}
}
@ -86,7 +89,7 @@ pub fn read_messages(
stream: &mut (impl Read + Write),
max_messages: usize,
last_size: usize,
chunked: bool
chunked: bool,
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write_all(&[0x00])?;
@ -123,8 +126,14 @@ 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<String> = lines.clone().into_iter()
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
let lines: Vec<String> = lines
.clone()
.into_iter()
.skip(if lines.len() >= max_messages {
lines.len() - max_messages
} else {
0
})
.map(|o| o.to_string())
.collect();

View File

@ -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<impl Write + Read>,
message: &str
message: &str,
) -> Result<(), Box<dyn Error>> {
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(())
}
@ -25,9 +29,11 @@ pub fn send_message(
pub fn register_user(
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str
password: &str,
) -> Result<bool, Box<dyn Error>> {
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)
@ -50,9 +56,14 @@ pub fn send_message_auth(
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str,
message: &str
message: &str,
) -> Result<u8, Box<dyn Error>> {
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() {
@ -76,7 +87,7 @@ pub fn read_messages(
stream: &mut WebSocket<impl Write + Read>,
max_messages: usize,
last_size: usize,
chunked: bool
chunked: bool,
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
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,8 +132,14 @@ 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<String> = lines.clone().into_iter()
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
let lines: Vec<String> = lines
.clone()
.into_iter()
.skip(if lines.len() >= max_messages {
lines.len() - max_messages
} else {
0
})
.map(|o| o.to_string())
.collect();