diff --git a/src/main.rs b/src/main.rs index b89cf14..48a338c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod client; mod listener; mod whitelist; mod info_messages; +mod motd; #[tokio::main] async fn main() -> Result<(), Box> { diff --git a/src/motd.rs b/src/motd.rs new file mode 100644 index 0000000..e66fa78 --- /dev/null +++ b/src/motd.rs @@ -0,0 +1,121 @@ +// Yeahbut June 2024 + +use std::fs::{self, File}; +use std::io::Read; +use std::time::{Duration, Instant}; +use std::sync::{Arc, Mutex}; + +use serde_json::Value; +use base64::{Engine as _, engine::general_purpose}; +use rand::Rng; +use lazy_static::lazy_static; + +// Refresh every 60 minutes +const EXPIRATION_DURATION: Duration = Duration::from_secs(3600); + +struct CachedMotds { + motd_data: Value, + timestamp: Instant, +} + +fn load_motds() -> Value { + let file_path = "./motd.json"; + + let data = match fs::read_to_string(file_path) { + Ok(data) => data, + Err(_) => return Value::Null, + }; + + let motd_data: Value = match serde_json::from_str(&data) { + Ok(value) => value, + Err(_) => return Value::Null, + }; + + motd_data +} + +fn get_motds() -> Value { + lazy_static! { + static ref MOTDS_CACHE: Arc>> = + Arc::new(Mutex::new(None)); + } + + let mut cache = MOTDS_CACHE.lock().unwrap(); + + if let Some(cached_motds) = cache.as_ref() { + if cached_motds.timestamp.elapsed() >= EXPIRATION_DURATION { + println!("Refreshing MOTD cache"); + *cache = Some(CachedMotds { + motd_data: load_motds(), + timestamp: Instant::now(), + }); + } + } else { + *cache = Some(CachedMotds { + motd_data: load_motds(), + timestamp: Instant::now(), + }); + } + + let motds = cache.as_ref().unwrap().motd_data.clone(); + + std::mem::drop(cache); + + motds +} + +pub fn motd() -> String { + let default = "A Minecraft Server Proxy".to_string(); + + let motd_data = get_motds(); + + if motd_data == Value::Null { + return default; + } + + let length1 = motd_data["line1"].as_array().map_or(0, |v| v.len()); + let length2 = motd_data["line2"].as_array().map_or(0, |v| v.len()); + + if length1 == 0 || length2 == 0 { + return default; + } + + let mut rng = rand::thread_rng(); + let rand1 = rng.gen_range(0..length1) as usize; + let rand2 = rng.gen_range(0..length2) as usize; + + let line1: &str = match motd_data["line1"][rand1].as_str() { + Some(s) => s, + None => return default, + }; + + // TODO: Birthdays, Holidays, and Announcements + + let line2: &str = match motd_data["line2"][rand2].as_str() { + Some(s) => s, + None => return default, + }; + + let line: String = format!("{}\n{}", line1, line2); + line +} + +pub fn favicon() -> Option { + let file_path = "./icon.png"; + + let mut file = match File::open(file_path) { + Ok(file) => file, + Err(_) => return None, + }; + + let mut buffer = Vec::new(); + if let Err(_) = file.read_to_end(&mut buffer) { + return None + }; + + let base64_string = general_purpose::STANDARD_NO_PAD.encode(buffer); + let full_string: String = + format!("data:image/png;base64,{}", base64_string); + + Some(full_string) +} diff --git a/src/status_handle.rs b/src/status_handle.rs index dfb2570..175ea22 100644 --- a/src/status_handle.rs +++ b/src/status_handle.rs @@ -1,15 +1,6 @@ // Yeahbut December 2023 -use std::fs::{self, File}; -use std::io::Read; -use std::time::{Duration, Instant}; -use std::sync::{Arc, Mutex}; - use tokio::io::AsyncWriteExt; -use serde_json::Value; -use base64::{Engine as _, engine::general_purpose}; -use rand::Rng; -use lazy_static::lazy_static; use purple_cello_mc_protocol::{ mc_types::{self, Result, Packet, ProtocolConnection}, @@ -19,13 +10,7 @@ use purple_cello_mc_protocol::{ use crate::listener; use crate::info_messages; - -const EXPIRATION_DURATION: Duration = Duration::from_secs(3600); - -struct CachedMotds { - motd_data: Value, - timestamp: Instant, -} +use crate::motd::{motd, favicon}; async fn online_players( proxy_info: listener::ProxyInfo, @@ -34,108 +19,6 @@ async fn online_players( Ok(get_upstream_status(proxy_info, server_conn).await?.players) } -fn load_motds() -> Value { - let file_path = "./motd.json"; - - let data = match fs::read_to_string(file_path) { - Ok(data) => data, - Err(_) => return Value::Null, - }; - - let motd_data: Value = match serde_json::from_str(&data) { - Ok(value) => value, - Err(_) => return Value::Null, - }; - - motd_data -} - -fn get_motds() -> Value { - lazy_static! { - static ref MOTDS_CACHE: Arc>> = - Arc::new(Mutex::new(None)); - } - - let mut cache = MOTDS_CACHE.lock().unwrap(); - - if let Some(cached_motds) = cache.as_ref() { - if cached_motds.timestamp.elapsed() >= EXPIRATION_DURATION { - println!("Refreshing MOTD cache"); - *cache = Some(CachedMotds { - motd_data: load_motds(), - timestamp: Instant::now(), - }); - } - } else { - *cache = Some(CachedMotds { - motd_data: load_motds(), - timestamp: Instant::now(), - }); - } - - let motds = cache.as_ref().unwrap().motd_data.clone(); - - std::mem::drop(cache); - - motds -} - -fn motd() -> String { - let default = "A Minecraft Server Proxy".to_string(); - - let motd_data = get_motds(); - - if motd_data == Value::Null { - return default; - } - - let length1 = motd_data["line1"].as_array().map_or(0, |v| v.len()); - let length2 = motd_data["line2"].as_array().map_or(0, |v| v.len()); - - if length1 == 0 || length2 == 0 { - return default; - } - - let mut rng = rand::thread_rng(); - let rand1 = rng.gen_range(0..length1) as usize; - let rand2 = rng.gen_range(0..length2) as usize; - - let line1: &str = match motd_data["line1"][rand1].as_str() { - Some(s) => s, - None => return default, - }; - - // TODO: Birthdays, Holidays, and Announcements - - let line2: &str = match motd_data["line2"][rand2].as_str() { - Some(s) => s, - None => return default, - }; - - let line: String = format!("{}\n{}", line1, line2); - line -} - -fn favicon() -> Option { - let file_path = "./icon.png"; - - let mut file = match File::open(file_path) { - Ok(file) => file, - Err(_) => return None, - }; - - let mut buffer = Vec::new(); - if let Err(_) = file.read_to_end(&mut buffer) { - return None - }; - - let base64_string = general_purpose::STANDARD_NO_PAD.encode(buffer); - let full_string: String = - format!("data:image/png;base64,{}", base64_string); - - Some(full_string) -} - pub async fn respond_status( proxy_info: listener::ProxyInfo, client_conn: &mut ProtocolConnection<'_>, diff --git a/src/whitelist.rs b/src/whitelist.rs index 0c5afc7..bee88d4 100644 --- a/src/whitelist.rs +++ b/src/whitelist.rs @@ -6,7 +6,8 @@ use serde_json::Value; use crate::info_messages; -const EXPIRATION_DURATION: Duration = Duration::from_secs(60); +// Refresh every 5 minutes +const EXPIRATION_DURATION: Duration = Duration::from_secs(300); #[derive(PartialEq)] pub struct Player {