From 65086a3b3c67c1d7ad408df6e973b26ba06df701 Mon Sep 17 00:00:00 2001 From: Arthur Beck Date: Tue, 4 Mar 2025 15:31:24 +0000 Subject: [PATCH] Basics working. --- src/main.rs | 165 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 146 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6d0ff6a..1e7becf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ #![feature(random)] #![feature(inline_const_pat)] -use std::io::prelude::*; +use std::io::{ErrorKind, prelude::*}; use std::net::{TcpListener, TcpStream}; use std::random; use std::sync::{Arc, Mutex}; -use std::thread::{self, sleep}; +use std::thread::{self, JoinHandle, sleep}; -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] enum UserStatus { ONLINE, DISCONNECTED, @@ -18,10 +18,10 @@ enum UserStatus { impl UserStatus { fn get_status_string(&self) -> String { match self { - UserStatus::ONLINE => "\x1b[0m\x1b[32mOnline\x1b[0m".to_string(), - UserStatus::IDLE => "\x1b[0m\x1b[33mIdle\x1b[0m".to_string(), - UserStatus::DISCONNECTED => "\x1b[0m\x1b[31mDisconnected\x1b[0m".to_string(), - UserStatus::CUSTOM(val) => format!("\x1b[0m\x1b[34m{}\x1b[0m", val) + UserStatus::ONLINE => "\x1b[0m\x1b[32monline\x1b[0m".to_string(), + UserStatus::IDLE => "\x1b[0m\x1b[33midle\x1b[0m".to_string(), + UserStatus::DISCONNECTED => "\x1b[0m\x1b[31mdisconnected\x1b[0m".to_string(), + UserStatus::CUSTOM(val) => format!("\x1b[0m\x1b[34m{}\x1b[0m", val), } } } @@ -55,7 +55,7 @@ fn handle_client(mut stream: TcpStream, channel: Channel) { let mut buf = [0u8; 30]; let mut i = 0usize; let mut buf1 = [0u8]; - while buf1[0] != b'\n' || i == 0 { + 'bufreadloop: while buf1[0] != b'\n' || i == 0 { stream.read_exact(&mut buf1).unwrap(); if buf1[0] == b'\n' && i == 0 { stream.write_all( @@ -73,6 +73,19 @@ fn handle_client(mut stream: TcpStream, channel: Channel) { i = 0; continue; } else if buf1[0] == b'\n' { + for user in &channel.lock().unwrap().online_users { + if user.name == String::from_utf8_lossy(&(buf[..i])).to_string() + && user.status != UserStatus::DISCONNECTED + { + stream.write_all( + b"\x1b[0m\x1b[31;1mSorry, but that nickname is taken.\n\x1b[0m\x1b[33mEnter your chosen nickname(up to 30 characters):\x1b[0m\x1b[4m\n" + ).unwrap(); + buf = [0u8; 30]; + buf1[0] = b'\0'; + i = 0; + continue 'bufreadloop; + } + } break; } if i < 30 { @@ -89,22 +102,34 @@ fn handle_client(mut stream: TcpStream, channel: Channel) { )) .unwrap(); - let user = User { + let mut user = User { name: String::from_utf8_lossy(&(buf[..i])).to_string(), status: UserStatus::ONLINE, }; + let netbot = User { + name: "Netbot".to_owned(), + status: UserStatus::ONLINE, + }; + channel.lock().unwrap().online_users.push(user.clone()); + let (recieve_terminate_send, recieve_terminate_recv) = std::sync::mpsc::channel::<()>(); + let recieve_thread: JoinHandle<_>; + { let channel = channel.clone(); let mut stream = stream.try_clone().unwrap(); let time_format = time::format_description::well_known::Rfc2822; - thread::spawn(move || { + recieve_thread = thread::spawn(move || { let mut current_message_count = channel.lock().unwrap().messages.len(); loop { + if let Ok(_) = recieve_terminate_recv.try_recv() { + stream.shutdown(std::net::Shutdown::Both).unwrap(); + panic!("recieve thread exiting"); + } let new_message_count = channel.lock().unwrap().messages.len(); if current_message_count != new_message_count { let new_messages = &channel.lock().unwrap().messages[current_message_count..]; @@ -133,7 +158,27 @@ fn handle_client(mut stream: TcpStream, channel: Channel) { let mut buf1 = [0u8]; loop { - stream.read_exact(&mut buf1).unwrap(); + if let Err(err) = stream.read_exact(&mut buf1) { + if err.kind() == ErrorKind::UnexpectedEof { + user.status = UserStatus::DISCONNECTED; + for usr in &mut channel.lock().unwrap().online_users { + if usr.name == user.name { + usr.status = user.status.clone(); + } + } + + channel.lock().unwrap().messages.push(Message { + sender: netbot.clone(), + contents: format!("User {} has left.", user.name), + timestamp: time::OffsetDateTime::now_local().unwrap(), + }); + + recieve_terminate_send.send(()).unwrap(); + let _ = recieve_thread.join(); + stream.shutdown(std::net::Shutdown::Both).unwrap(); + return; + } + } if buf1[0] == b'\x08' { buf.pop(); @@ -148,25 +193,107 @@ fn handle_client(mut stream: TcpStream, channel: Channel) { timestamp: time::OffsetDateTime::now_local().unwrap(), }); } else { - if contents.starts_with("/help") || contents.starts_with("/?") { + if contents.split(" ").collect::>()[0] == "/help" || contents.split(" ").collect::>()[0] == "/?" { stream .write_all( b"\x1b[0m\x1b[32mValid commands:\n \ /help, /?: Print this help.\n \ - /status online|idle|disconnected|(any value): Set your status.\n \ - /user nick: Get information about a user.\x1b[0m", + /status [online|idle|disconnected|(any value)]: Set your status.\n \ + /user [nick]: Get information about a user.\n \ + /server [new name]: Get or set the server name.\n \ + /users: List users.\x1b[0m\n", ) .unwrap(); - } else if contents.starts_with("/status") { + } else if contents.split(" ").collect::>()[0] == "/status" || contents == "/user" { let contents = contents.split(" ").collect::>(); if contents.len() == 1 { stream - .write_fmt( - format_args!("\x1b[0m\x1b[32mYou are currently marked as {}\n\x1b[0m"), - - ) + .write_fmt(format_args!( + "\x1b[0m\x1b[32mYou are currently marked as {}\n\x1b[0m", + user.clone().status.get_status_string() + )) + .unwrap(); + } else { + match contents[1] { + "online" => { + user.status = UserStatus::ONLINE; + } + "idle" => { + user.status = UserStatus::IDLE; + } + "disconnected" => { + user.status = UserStatus::DISCONNECTED; + } + other => { + user.status = UserStatus::CUSTOM(other.to_owned()); + } + } + for usr in &mut channel.lock().unwrap().online_users { + if usr.name == user.name { + usr.status = user.status.clone(); + } + } + stream + .write_fmt(format_args!( + "\x1b[0m\x1b[32mYou are now marked as {}\n\x1b[0m", + user.status.get_status_string() + )) .unwrap(); } + } else if contents.split(" ").collect::>()[0] == "/server" { + let contents = contents.split(" ").collect::>(); + if contents.len() == 1 { + stream + .write_fmt(format_args!( + "\x1b[0m\x1b[32mCurrent server name: {}.\n\x1b[0m", + channel.lock().unwrap().name + )) + .unwrap(); + } else { + channel.lock().unwrap().name = contents[1].to_owned(); + stream + .write_fmt(format_args!( + "\x1b[0m\x1b[32mNew server name: {}.\n\x1b[0m", + channel.lock().unwrap().name + )) + .unwrap(); + } + } else if contents.split(" ").collect::>()[0] == "/user" { + let contents = contents.split(" ").collect::>(); + let mut found_user = false; + for usr in &mut channel.lock().unwrap().online_users { + if usr.name == contents[1] { + stream + .write_fmt(format_args!( + "\x1b[0m\x1b[32mUser {} is currently {}.\n\x1b[0m", + usr.name, + usr.status.get_status_string() + )) + .unwrap(); + found_user = true; + break; + } + } + if !found_user { + stream + .write_fmt(format_args!( + "\x1b[0m\x1b[32mUnknown user \"{}\".\n\x1b[0m", + contents[1] + )) + .unwrap(); + } + } else if contents.starts_with("/users") { + stream.write_all(b"\x1b[0m\x1b[32mUsers:\x1b[0m").unwrap(); + for usr in &channel.lock().unwrap().online_users { + stream + .write_fmt(format_args!( + "\n{} (currently {})", + usr.name, + usr.status.get_status_string() + )) + .unwrap(); + } + stream.write_all(b"\n\x1b[0m").unwrap(); } else { stream .write_all(b"\x1b[0m\x1b[31;1mThat's an invalid command!\n\x1b[0m")