mirror of
https://github.com/MeexReay/bRAC.git
synced 2025-06-23 18:32:58 +03:00
rustfmt
This commit is contained in:
parent
3e75662969
commit
c1e9d00d3a
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
@ -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 {
|
||||
@ -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<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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,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::<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);
|
||||
}
|
||||
|
||||
pub fn config<T>(&self, map: fn (&Config) -> T) -> T {
|
||||
pub fn config<T>(&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<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);
|
||||
}
|
||||
@ -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()),
|
||||
)?
|
||||
};
|
||||
}
|
||||
}
|
||||
|
578
src/chat/gui.rs
578
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<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,97 +77,83 @@ fn load_pixbuf(data: &[u8]) -> Result<Pixbuf, Box<dyn Error>> {
|
||||
}
|
||||
|
||||
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<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,37 +214,53 @@ 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(),
|
||||
name: {
|
||||
let name = name_entry.text().to_string();
|
||||
|
||||
|
||||
if name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@ -233,7 +270,7 @@ fn open_settings(ctx: Arc<Context>, 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::<usize>() {
|
||||
update_time
|
||||
} else {
|
||||
@ -244,7 +281,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
},
|
||||
oof_update_time: {
|
||||
let oof_update_time = oof_update_time_entry.text();
|
||||
|
||||
|
||||
if let Ok(oof_update_time) = oof_update_time.parse::<usize>() {
|
||||
oof_update_time
|
||||
} else {
|
||||
@ -255,7 +292,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
},
|
||||
konata_size: {
|
||||
let konata_size = konata_size_entry.text();
|
||||
|
||||
|
||||
if let Ok(konata_size) = konata_size.parse::<usize>() {
|
||||
konata_size.max(0).min(200)
|
||||
} else {
|
||||
@ -266,7 +303,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
},
|
||||
max_messages: {
|
||||
let max_messages = max_messages_entry.text();
|
||||
|
||||
|
||||
if let Ok(max_messages) = max_messages.parse::<usize>() {
|
||||
max_messages
|
||||
} else {
|
||||
@ -286,13 +323,13 @@ fn open_settings(ctx: Arc<Context>, 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<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);
|
||||
@ -365,7 +414,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
});
|
||||
|
||||
window.add_controller(controller);
|
||||
|
||||
|
||||
window.present();
|
||||
}
|
||||
|
||||
@ -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()
|
||||
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,24 +469,29 @@ 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()
|
||||
.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<Context>, 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<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();
|
||||
@ -531,9 +593,13 @@ fn build_ui(ctx: Arc<Context>, 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<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,
|
||||
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<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,
|
||||
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<Context>, 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<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());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -701,7 +788,7 @@ fn setup(_: &Application, ctx: Arc<Context>, 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<Context>, 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<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=\"{}\"><{}></span> ", color.to_uppercase(), glib::markup_escape_text(&name)));
|
||||
label.push_str(&format!(
|
||||
"<span font_weight=\"bold\" color=\"{}\"><{}></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) {
|
||||
@ -875,18 +989,20 @@ fn on_add_message(ctx: Arc<Context>, 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<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,25 +1022,26 @@ 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 {
|
||||
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
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
|
128
src/chat/mod.rs
128
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<String> = 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<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,45 +70,52 @@ 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 {
|
||||
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<Context>, command: &str) -> Result<(), Box<dyn Error>
|
||||
|
||||
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<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,17 +156,18 @@ 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 {
|
||||
""
|
||||
},
|
||||
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<Context>) -> Result<(), Box<dyn Error>> {
|
||||
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<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())],
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -208,10 +223,10 @@ pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn E
|
||||
on_command(ctx.clone(), &message)?;
|
||||
} else {
|
||||
let message = prepare_message(
|
||||
ctx.clone(),
|
||||
&ctx.config(|o| o.message_format.clone())
|
||||
.replace("{name}", &ctx.name())
|
||||
.replace("{text}", &message)
|
||||
ctx.clone(),
|
||||
&ctx.config(|o| o.message_format.clone())
|
||||
.replace("{name}", &ctx.name())
|
||||
.replace("{text}", &message),
|
||||
);
|
||||
|
||||
if let Some(password) = ctx.registered.read().unwrap().clone() {
|
||||
@ -224,7 +239,7 @@ pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn E
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sanitize_message(message: String) -> Option<String> {
|
||||
let message = sanitize_text(&message);
|
||||
@ -235,15 +250,17 @@ 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)?;
|
||||
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>, 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>, 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
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod chat;
|
||||
pub mod proto;
|
||||
pub mod proto;
|
||||
|
43
src/main.rs
43
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());
|
||||
|
201
src/proto/mod.rs
201
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<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((
|
||||
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<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()
|
||||
.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<String>) -> Result<RacStream, Box<dyn E
|
||||
|
||||
if wrac {
|
||||
let (client, _) = tungstenite::client(
|
||||
&format!("ws{}://{host}", if ssl { "s" } else { "" }),
|
||||
stream
|
||||
&format!("ws{}://{host}", if ssl { "s" } else { "" }),
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<dy
|
||||
///
|
||||
/// returns whether the user was registered
|
||||
pub fn register_user(
|
||||
stream: &mut (impl Write + Read),
|
||||
name: &str,
|
||||
password: &str
|
||||
stream: &mut (impl Write + Read),
|
||||
name: &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) {
|
||||
@ -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<u8, Box<dyn Error>> {
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,7 +72,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
/// remove trailing null bytes in vector
|
||||
pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
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<u8>) -> Result<(), Box<dyn Error>> {
|
||||
///
|
||||
/// 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<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
||||
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<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();
|
||||
|
||||
Ok(Some((lines, packet_size)))
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
@ -23,11 +27,13 @@ pub fn send_message(
|
||||
///
|
||||
/// returns whether the user was registered
|
||||
pub fn register_user(
|
||||
stream: &mut WebSocket<impl Write + Read>,
|
||||
name: &str,
|
||||
password: &str
|
||||
stream: &mut WebSocket<impl Write + Read>,
|
||||
name: &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)
|
||||
@ -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<impl Write + Read>,
|
||||
name: &str,
|
||||
password: &str,
|
||||
message: &str
|
||||
stream: &mut WebSocket<impl Write + Read>,
|
||||
name: &str,
|
||||
password: &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() {
|
||||
@ -73,10 +84,10 @@ pub fn send_message_auth(
|
||||
///
|
||||
/// returns (messages, packet size)
|
||||
pub fn read_messages(
|
||||
stream: &mut WebSocket<impl Write + Read>,
|
||||
max_messages: usize,
|
||||
last_size: usize,
|
||||
chunked: bool
|
||||
stream: &mut WebSocket<impl Write + Read>,
|
||||
max_messages: usize,
|
||||
last_size: usize,
|
||||
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,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<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();
|
||||
|
||||
Ok(Some((lines, packet_size)))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user