diff --git a/.forgejo/workflows/rust.yml b/.forgejo/workflows/rust.yml new file mode 100644 index 0000000..788c0d0 --- /dev/null +++ b/.forgejo/workflows/rust.yml @@ -0,0 +1,18 @@ +on: [push, workflow_dispatch] +jobs: + lint-n-test: + # Run on the 9950x that Aria has access to :3 + runs-on: azuki-new + container: + image: rust + steps: + # nodejs is required for the checkout action + - run: curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs + - uses: actions/checkout@v4 + - run: rustup component add rustfmt clippy + - run: cargo fmt -- --check + continue-on-error: true + - run: cargo clippy -- -D warnings + continue-on-error: true + - run: cargo check + - run: cargo test diff --git a/src/main.rs b/src/main.rs index 198f6f5..3ec9c3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,10 +47,6 @@ enum OutputFormat { Txt, } -/// follow up by extracting just the user and the message -static EXTRACT_MSG: Lazy = Lazy::new(|| Regex::new(r"<.*> .*").unwrap()); -static EXTRACT_TIME: Lazy = Lazy::new(|| Regex::new(r"^\[.*?\]").unwrap()); - fn main() -> Result<()> { let args = Args::parse(); @@ -71,18 +67,8 @@ fn main() -> Result<()> { .map(|x| { format!( "{} {}", - EXTRACT_TIME - .captures(x) - .expect("Unable to extract time") - .get(0) - .unwrap() - .as_str(), - EXTRACT_MSG - .captures(x) - .expect("Unable to extract message") - .get(0) - .unwrap() - .as_str() + extract_date_time(x).first().unwrap(), + extract_message(x) ) }) .collect(); @@ -202,33 +188,50 @@ fn save_csv_file( } for msg in selected { - let time: Vec<&str> = EXTRACT_TIME - .captures(&msg) - .expect("Unable to extract time") - .get(0) - .unwrap() - .as_str() - .strip_prefix("[") - .expect("Unable to remove time prefix") - .strip_suffix("]") - .expect("Unable to remove time suffix") - .split(' ') - .collect(); - out_file.write_record([ - time[0], - time[1], - EXTRACT_MSG - .captures(&msg) - .expect("Unable to extract time") - .get(0) - .unwrap() - .as_str(), - ])?; + let mut time: Vec<&str> = extract_date_time(&msg); + + // if there is only 1 entry in the vec then it has to be a client time so we can just add a pointless entry for the csv + if time.len() == 1 { + time.insert(0, "Null"); + } + + out_file.write_record([time[0], time[1], &extract_message(&msg)])?; } Ok(()) } +fn extract_message(msg: &str) -> String { + /// follow up by extracting just the user and the message + static EXTRACT_MSG: Lazy = Lazy::new(|| Regex::new(r"<.*> .*").unwrap()); + + EXTRACT_MSG + .captures(msg) + .expect("Unable to extract time") + .get(0) + .unwrap() + .as_str() + .to_string() +} + +fn extract_date_time(msg: &str) -> Vec<&str> { + static EXTRACT_TIME: Lazy = Lazy::new(|| Regex::new(r"^\[.*?\]").unwrap()); + + let time: Vec<&str> = EXTRACT_TIME + .captures(msg) + .expect("Unable to extract time") + .get(0) + .unwrap() + .as_str() + .strip_prefix("[") + .expect("Unable to remove time prefix") + .strip_suffix("]") + .expect("Unable to remove time suffix") + .split(' ') + .collect(); + time +} + fn is_possible_chat_msg(input: &str) -> bool { /// first pass to find all possible lines that could have a chat message static SERVER_MSG_RE: Lazy = Lazy::new(|| { @@ -237,10 +240,113 @@ fn is_possible_chat_msg(input: &str) -> bool { ) .unwrap() }); - static CLIENT_MSG_RE: Lazy = Lazy::new(|| { + static CLIENT_PRISM_MSG_RE: Lazy = Lazy::new(|| { Regex::new(r"\[.*\] \[Render thread\/INFO\] \[minecraft\/ChatComponent\]: \[CHAT\] <.*>*") .unwrap() }); + static CLIENT_LOG_MSG_RE: Lazy = Lazy::new(|| { + Regex::new(r"\[.*\] \[Render thread\/INFO\] \[net.minecraft.client.gui.components.ChatComponent\/\]: \[CHAT\] <.*>*") + .unwrap() + }); - SERVER_MSG_RE.is_match(input) || CLIENT_MSG_RE.is_match(input) + // This is here just in case I need to get a chat message from a strange place + static _CLIENT_CATCHALL_MSG_RE: Lazy = + Lazy::new(|| Regex::new(r".*?: \[CHAT\] .*").unwrap()); + + SERVER_MSG_RE.is_match(input) + || CLIENT_PRISM_MSG_RE.is_match(input) + || CLIENT_LOG_MSG_RE.is_match(input) +} + +#[cfg(test)] +mod tests { + use super::*; + const TEST_SERVER_MSG: &str = "[05Jul2025 12:41:12.295] [Server thread/INFO] [net.minecraft.server.MinecraftServer/]: <🐈 Vftdan> :3"; + const TEST_CLIENT_LOG_MSG: &str = "[11Jul2025 20:30:20.286] [Render thread/INFO] [net.minecraft.client.gui.components.ChatComponent/]: [CHAT] tehe"; + const TEST_PRISM_MSG: &str = "[16:53:50] [Render thread/INFO] [minecraft/ChatComponent]: [CHAT] ;3"; + + const RANDOM_LOG_LINES: [&str; 5] = [ + "[05Jul2025 13:08:11.886] [VoiceChatPacketProcessingThread/INFO] [voicechat/]: [voicechat] Player 399aedb6-a257-49d1-930b-af62fc328ae7 timed out", + "[05Jul2025 13:09:19.890] [Server thread/INFO] [me.ichun.mods.serverpause.common.core.MinecraftServerMethods/]: Saving and pausing game...", + "[05Jul2025 12:02:58.057] [Server thread/INFO] [net.minecraft.server.MinecraftServer/]: alto joined the game", + "java.lang.NullPointerException: Cannot invoke \"net.minecraft.world.Container.getContainerSize()\" because the return value of \"net.neoforged.neoforge.items.wrapper.InvWrapper.getInv()\" is null", + "[05Jul2025 10:31:36.112] [Server thread/INFO] [owo/]: Receiving client config", + ]; + + #[test] + fn detect_server_chat_messages() { + assert!(is_possible_chat_msg(TEST_SERVER_MSG)); + } + + #[test] + fn detect_prism_chat_messages() { + assert!(is_possible_chat_msg(TEST_PRISM_MSG)); + } + + #[test] + fn detect_client_log_chat_messages() { + assert!(is_possible_chat_msg(TEST_CLIENT_LOG_MSG)); + } + + #[test] + fn extract_chat_message_server() { + assert_eq!("<🐈 Vftdan> :3", &extract_message(TEST_SERVER_MSG)); + } + #[test] + fn extract_chat_message_client_log() { + assert_eq!( + " tehe", + &extract_message(TEST_CLIENT_LOG_MSG) + ); + } + #[test] + fn extract_chat_message_prism_log() { + assert_eq!( + " ;3", + &extract_message(TEST_PRISM_MSG) + ); + } + + #[test] + fn extract_datetime_server_messages() { + let msg_string = TEST_SERVER_MSG.to_string(); + let datetime: Vec<&str> = extract_date_time(&msg_string); + println!("server datetime: {:?}", datetime); + + let correct_datetime: Vec<&str> = vec![&"05Jul2025", &"12:41:12.295"]; + assert_eq!(datetime, correct_datetime); + } + + #[test] + fn extract_datetime_client_messages() { + let msg_string = TEST_CLIENT_LOG_MSG.to_string(); + let datetime: Vec<&str> = extract_date_time(&msg_string); + println!("server datetime: {:?}", datetime); + + let correct_datetime: Vec<&str> = vec![&"11Jul2025", &"20:30:20.286"]; + assert_eq!(datetime, correct_datetime); + } + + #[test] + fn extract_datetime_prism_messages() { + let msg_string = TEST_PRISM_MSG.to_string(); + let datetime: Vec<&str> = extract_date_time(&msg_string); + println!("prism datetime: {:?}", datetime); + + let correct_datetime: Vec<&str> = vec![&"16:53:50"]; + assert_eq!(datetime, correct_datetime); + } + + #[test] + fn ignore_random_log_lines() { + let mut is_msg: bool = false; + + for line in RANDOM_LOG_LINES { + if is_possible_chat_msg(line) { + is_msg = true; + } + } + + assert!(!is_msg); + } }