mirror of
https://github.com/MeexReay/bRAC.git
synced 2025-07-01 05:53:02 +03:00
refactor: more avatar-specific stuff
This commit is contained in:
parent
00cc5b2e86
commit
5266d1190f
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -244,7 +244,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bRAC"
|
||||
version = "0.1.5+2.0"
|
||||
version = "0.1.6+2.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
@ -720,6 +720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -788,8 +789,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
@ -2030,7 +2034,9 @@ dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bRAC"
|
||||
version = "0.1.5+2.0"
|
||||
version = "0.1.6+2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@ -21,7 +21,7 @@ notify-rust = { version = "4.11.7", optional = true }
|
||||
gdk-pixbuf = { version = "0.3.0", optional = true } # DO NOT UPDATE
|
||||
winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] }
|
||||
tungstenite = "0.27.0"
|
||||
reqwest = "0.12.20"
|
||||
reqwest = { version = "0.12.20", features = ["blocking"] }
|
||||
|
||||
[build-dependencies]
|
||||
winresource = { version = "0.1.20", optional = true }
|
||||
|
@ -63,6 +63,8 @@ pub struct Config {
|
||||
pub new_ui_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub debug_logs: bool,
|
||||
#[serde(default)]
|
||||
pub avatar: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@ -154,6 +156,8 @@ pub struct Args {
|
||||
#[arg(long)]
|
||||
pub proxy: Option<String>,
|
||||
#[arg(long)]
|
||||
pub avatar: Option<String>,
|
||||
#[arg(long)]
|
||||
pub debug_logs: bool,
|
||||
}
|
||||
|
||||
@ -207,6 +211,9 @@ impl Args {
|
||||
if let Some(v) = self.new_ui_enabled {
|
||||
config.new_ui_enabled = v
|
||||
}
|
||||
if let Some(v) = self.avatar.clone() {
|
||||
config.avatar = Some(v)
|
||||
}
|
||||
if self.debug_logs {
|
||||
config.debug_logs = true
|
||||
}
|
||||
|
161
src/chat/gui.rs
161
src/chat/gui.rs
@ -1,6 +1,11 @@
|
||||
// TODO: REFACTOR THIS SHIT!!!!!!!!!!!!!!!
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::hash::{DefaultHasher, Hasher};
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::RwLockWriteGuard;
|
||||
use std::sync::{atomic::Ordering, mpsc::channel, Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@ -25,6 +30,8 @@ use gtk::{
|
||||
Orientation, Overlay, Picture, ScrolledWindow, Settings, Window,
|
||||
};
|
||||
|
||||
use crate::chat::grab_avatar;
|
||||
|
||||
use super::{
|
||||
config::{
|
||||
default_konata_size, default_max_messages, default_oof_update_time, default_update_time,
|
||||
@ -46,6 +53,7 @@ struct UiModel {
|
||||
notifications: Arc<RwLock<Vec<String>>>,
|
||||
default_avatar: Pixbuf,
|
||||
avatars: Arc<RwLock<HashMap<u64, Pixbuf>>>,
|
||||
latest_sign: Arc<AtomicU64>
|
||||
}
|
||||
|
||||
thread_local!(
|
||||
@ -176,6 +184,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
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 avatar_entry = gui_option_entry_setting!("Avatar", avatar, 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!(
|
||||
@ -257,6 +266,8 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
remove_gui_shit_entry,
|
||||
#[weak]
|
||||
new_ui_enabled_entry,
|
||||
#[weak]
|
||||
avatar_entry,
|
||||
move |_| {
|
||||
let config = Config {
|
||||
host: host_entry.text().to_string(),
|
||||
@ -269,6 +280,15 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
Some(name)
|
||||
}
|
||||
},
|
||||
avatar: {
|
||||
let avatar = avatar_entry.text().to_string();
|
||||
|
||||
if avatar.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(avatar)
|
||||
}
|
||||
},
|
||||
message_format: message_format_entry.text().to_string(),
|
||||
update_time: {
|
||||
let update_time = update_time_entry.text();
|
||||
@ -380,12 +400,15 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
|
||||
remove_gui_shit_entry,
|
||||
#[weak]
|
||||
new_ui_enabled_entry,
|
||||
#[weak]
|
||||
avatar_entry,
|
||||
move |_| {
|
||||
let config = Config::default();
|
||||
ctx.set_config(&config);
|
||||
save_config(get_config_path(), &config);
|
||||
host_entry.set_text(&config.host);
|
||||
name_entry.set_text(&config.name.unwrap_or_default());
|
||||
avatar_entry.set_text(&config.avatar.unwrap_or_default());
|
||||
proxy_entry.set_text(&config.proxy.unwrap_or_default());
|
||||
message_format_entry.set_text(&config.message_format);
|
||||
update_time_entry.set_text(&config.update_time.to_string());
|
||||
@ -791,6 +814,7 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
|
||||
notifications: Arc::new(RwLock::new(Vec::<String>::new())),
|
||||
default_avatar: load_pixbuf(include_bytes!("images/avatar.png")).unwrap(),
|
||||
avatars: Arc::new(RwLock::new(HashMap::new())),
|
||||
latest_sign: Arc::new(AtomicU64::new(0))
|
||||
}
|
||||
}
|
||||
|
||||
@ -843,6 +867,7 @@ fn setup(_: &Application, ctx: Arc<Context>, ui: UiModel) {
|
||||
move || {
|
||||
while let Ok((messages, clear)) = receiver.recv() {
|
||||
let ctx = ctx.clone();
|
||||
|
||||
timeout_add_once(Duration::ZERO, move || {
|
||||
GLOBAL.with(|global| {
|
||||
if let Some(ui) = &*global.borrow() {
|
||||
@ -852,6 +877,7 @@ fn setup(_: &Application, ctx: Arc<Context>, ui: UiModel) {
|
||||
}
|
||||
}
|
||||
for message in messages.iter() {
|
||||
prepare_avatar(&mut ui.avatars.write().unwrap(), message); // TODO: fuck
|
||||
on_add_message(ctx.clone(), &ui, message.to_string(), !clear);
|
||||
}
|
||||
}
|
||||
@ -1036,8 +1062,34 @@ fn get_message_box(
|
||||
hbox
|
||||
}
|
||||
|
||||
fn load_avatar(ui: &UiModel, url: &str) -> Option<Pixbuf> {
|
||||
Some(ui.default_avatar.clone())
|
||||
fn prepare_avatar(avatars: &mut RwLockWriteGuard<'_, HashMap<u64, Pixbuf>>, message: &str) {
|
||||
if let Some(url) = grab_avatar(message) {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(url.as_bytes());
|
||||
let id = hasher.finish();
|
||||
|
||||
if !avatars.contains_key(&id) {
|
||||
let Ok(data) = reqwest::blocking::get(&url).and_then(|o| o.bytes()) else {
|
||||
return
|
||||
};
|
||||
let Ok(pixbuf) = load_pixbuf(&data.to_vec()) else {
|
||||
return
|
||||
};
|
||||
avatars.insert(id, pixbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_avatar_or_default(ui: &UiModel, url: &str) -> Pixbuf {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(url.as_bytes());
|
||||
let id = hasher.finish();
|
||||
|
||||
if let Some(pixbuf) = ui.avatars.read().unwrap().get(&id) {
|
||||
pixbuf.clone() // FIXME: cloning pixbufs is a dangerous shit
|
||||
} else {
|
||||
ui.default_avatar.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_new_message_box(
|
||||
@ -1045,7 +1097,7 @@ fn get_new_message_box(
|
||||
ui: &UiModel,
|
||||
message: String,
|
||||
notify: bool,
|
||||
formatting_enabled: bool,
|
||||
formatting_enabled: bool
|
||||
) -> Overlay {
|
||||
// TODO: softcode these colors
|
||||
|
||||
@ -1055,10 +1107,13 @@ fn get_new_message_box(
|
||||
("#585858", "#292929", "#000000")
|
||||
};
|
||||
|
||||
let latest_sign = ui.latest_sign.load(Ordering::SeqCst);
|
||||
|
||||
let (date, ip, content, name, color, avatar) =
|
||||
if let (true, Some((date, ip, content, nick, avatar))) =
|
||||
(formatting_enabled, parse_message(message.clone()))
|
||||
{
|
||||
|
||||
(
|
||||
date,
|
||||
ip,
|
||||
@ -1070,7 +1125,7 @@ fn get_new_message_box(
|
||||
.map(|o| o.1.to_string())
|
||||
.unwrap_or("#DDDDDD".to_string()),
|
||||
avatar
|
||||
.and_then(|o| load_avatar(ui, &o))
|
||||
.map(|o| get_avatar_or_default(ui, &o))
|
||||
.unwrap_or(ui.default_avatar.clone()),
|
||||
)
|
||||
} else {
|
||||
@ -1083,40 +1138,65 @@ fn get_new_message_box(
|
||||
ui.default_avatar.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
if notify && !ui.window.is_active() {
|
||||
if ctx.config(|o| o.chunked_enabled) {
|
||||
send_notification(
|
||||
ctx.clone(),
|
||||
ui,
|
||||
&if name == "System" {
|
||||
"System Message".to_string()
|
||||
} else {
|
||||
format!("{}'s Message", name)
|
||||
},
|
||||
&glib::markup_escape_text(&content),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let sign = get_message_sign(&name, &date);
|
||||
|
||||
let squashed = latest_sign == sign;
|
||||
|
||||
ui.latest_sign.store(sign, Ordering::SeqCst);
|
||||
|
||||
let overlay = Overlay::new();
|
||||
|
||||
let fixed = Fixed::new();
|
||||
if !squashed {
|
||||
let fixed = Fixed::new();
|
||||
fixed.set_can_target(false);
|
||||
|
||||
let avatar_picture = Picture::for_pixbuf(&avatar);
|
||||
avatar_picture.set_css_classes(&["message-avatar"]);
|
||||
avatar_picture.set_vexpand(false);
|
||||
avatar_picture.set_hexpand(false);
|
||||
avatar_picture.set_valign(Align::Start);
|
||||
avatar_picture.set_halign(Align::Start);
|
||||
avatar_picture.set_size_request(32, 32);
|
||||
let avatar_picture = Picture::for_pixbuf(&avatar);
|
||||
avatar_picture.set_css_classes(&["message-avatar"]);
|
||||
avatar_picture.set_vexpand(false);
|
||||
avatar_picture.set_hexpand(false);
|
||||
avatar_picture.set_valign(Align::Start);
|
||||
avatar_picture.set_halign(Align::Start);
|
||||
avatar_picture.set_size_request(32, 32);
|
||||
|
||||
fixed.put(&avatar_picture, 0.0, 4.0);
|
||||
fixed.put(&avatar_picture, 0.0, 4.0);
|
||||
|
||||
overlay.add_overlay(&fixed);
|
||||
overlay.add_overlay(&fixed);
|
||||
}
|
||||
|
||||
let vbox = GtkBox::new(Orientation::Vertical, 2);
|
||||
|
||||
vbox.append(&Label::builder()
|
||||
.label(format!(
|
||||
"<span color=\"{color}\">{}</span> <span color=\"{date_color}\">{}</span> <span color=\"{ip_color}\">{}</span>",
|
||||
glib::markup_escape_text(&name),
|
||||
glib::markup_escape_text(&date),
|
||||
glib::markup_escape_text(&ip.unwrap_or_default()),
|
||||
))
|
||||
.halign(Align::Start)
|
||||
.valign(Align::Start)
|
||||
.selectable(true)
|
||||
.wrap(true)
|
||||
.wrap_mode(WrapMode::WordChar)
|
||||
.use_markup(true)
|
||||
.vexpand(true)
|
||||
.build());
|
||||
if !squashed {
|
||||
vbox.append(&Label::builder()
|
||||
.label(format!(
|
||||
"<span color=\"{color}\">{}</span> <span color=\"{date_color}\">{}</span> <span color=\"{ip_color}\">{}</span>",
|
||||
glib::markup_escape_text(&name),
|
||||
glib::markup_escape_text(&date),
|
||||
glib::markup_escape_text(&ip.unwrap_or_default()),
|
||||
))
|
||||
.halign(Align::Start)
|
||||
.valign(Align::Start)
|
||||
.selectable(true)
|
||||
.wrap(true)
|
||||
.wrap_mode(WrapMode::WordChar)
|
||||
.use_markup(true)
|
||||
.build());
|
||||
}
|
||||
|
||||
vbox.append(&Label::builder()
|
||||
.label(format!(
|
||||
@ -1129,20 +1209,31 @@ fn get_new_message_box(
|
||||
.wrap(true)
|
||||
.wrap_mode(WrapMode::WordChar)
|
||||
.use_markup(true)
|
||||
.vexpand(true)
|
||||
.build());
|
||||
|
||||
vbox.set_valign(Align::Fill);
|
||||
vbox.set_vexpand(true);
|
||||
vbox.set_margin_start(37);
|
||||
vbox.set_hexpand(true);
|
||||
|
||||
overlay.set_child(Some(&vbox));
|
||||
|
||||
overlay.set_margin_top(7);
|
||||
if !squashed {
|
||||
overlay.set_margin_top(7);
|
||||
} else {
|
||||
overlay.set_margin_top(2);
|
||||
}
|
||||
|
||||
overlay
|
||||
}
|
||||
|
||||
// creates sign that expires in 0-20 minutes
|
||||
fn get_message_sign(name: &str, date: &str) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(name.as_bytes());
|
||||
hasher.write(date[..date.len()-2].as_bytes());
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// returns message sign
|
||||
fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool) {
|
||||
let notify = notify && ctx.config(|c| c.notifications_enabled);
|
||||
|
||||
@ -1164,7 +1255,7 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
|
||||
ui.chat_box.append(&get_new_message_box(ctx.clone(), ui, message, notify, formatting_enabled));
|
||||
} else {
|
||||
ui.chat_box.append(&get_message_box(ctx.clone(), ui, message, notify, formatting_enabled));
|
||||
}
|
||||
};
|
||||
|
||||
timeout_add_local_once(Duration::from_millis(1000), move || {
|
||||
GLOBAL.with(|global| {
|
||||
|
@ -34,7 +34,7 @@ lazy_static! {
|
||||
|
||||
pub static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap();
|
||||
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
|
||||
pub static ref AVATAR_REGEX: Regex = Regex::new(r"(.*) \x06!!AR!!(.*)").unwrap();
|
||||
pub static ref AVATAR_REGEX: Regex = Regex::new(r"(.*)\x06!!AR!!(.*)").unwrap();
|
||||
|
||||
pub static ref DEFAULT_USER_AGENT: Regex = Regex::new(r"<(.*?)> (.*)").unwrap();
|
||||
|
||||
@ -234,13 +234,17 @@ pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn E
|
||||
if message.starts_with("/") && ctx.config(|o| o.commands_enabled) {
|
||||
on_command(ctx.clone(), &message)?;
|
||||
} else {
|
||||
let message = prepare_message(
|
||||
let mut message = prepare_message(
|
||||
ctx.clone(),
|
||||
&ctx.config(|o| o.message_format.clone())
|
||||
.replace("{name}", &ctx.name())
|
||||
.replace("{text}", &message),
|
||||
);
|
||||
|
||||
if let Some(avatar) = ctx.config(|o| o.avatar.clone()) {
|
||||
message = format!("{message}\x06!!AR!!{avatar}"); // TODO: softcode this shittttttt
|
||||
}
|
||||
|
||||
if let Some(password) = ctx.registered.read().unwrap().clone() {
|
||||
send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message)?;
|
||||
} else {
|
||||
@ -257,6 +261,15 @@ pub fn sanitize_message(message: String) -> Option<String> {
|
||||
Some(message)
|
||||
}
|
||||
|
||||
/// message -> avatar
|
||||
pub fn grab_avatar(message: &str) -> Option<String> {
|
||||
if let Some(message) = AVATAR_REGEX.captures(&message) {
|
||||
Some(message.get(2)?.as_str().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// message -> (date, ip, text, (name, color), avatar)
|
||||
pub fn parse_message(
|
||||
message: String,
|
||||
|
Loading…
x
Reference in New Issue
Block a user