From 686d1d74fee6b2516c28880043326fdf275bc729 Mon Sep 17 00:00:00 2001 From: Arthur Beck Date: Tue, 4 Mar 2025 06:56:22 -0600 Subject: [PATCH] commit so i can work elsewhere --- Cargo.toml | 7 ++ src/main.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..da68dc0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rawnetchat" +version = "0.1.0" +edition = "2024" + +[dependencies] +time = { version = "0.3.37", features = ["formatting", "local-offset"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6d0ff6a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,201 @@ +#![feature(random)] +#![feature(inline_const_pat)] + +use std::io::prelude::*; +use std::net::{TcpListener, TcpStream}; +use std::random; +use std::sync::{Arc, Mutex}; +use std::thread::{self, sleep}; + +#[derive(Clone)] +enum UserStatus { + ONLINE, + DISCONNECTED, + IDLE, + CUSTOM(String), +} + +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) + } + } +} + +#[derive(Clone)] +struct User { + name: String, + status: UserStatus, +} + +#[derive(Clone)] +struct Message { + sender: User, + contents: String, + timestamp: time::OffsetDateTime, +} + +type Channel = Arc>; + +#[derive(Clone)] +struct UnwrappedChannel { + name: String, + messages: Vec, + online_users: Vec, +} + +fn handle_client(mut stream: TcpStream, channel: Channel) { + stream + .write_all(b"\x1b[33mEnter your chosen nickname(up to 30 characters):\x1b[0m\x1b[4m\n") + .unwrap(); + let mut buf = [0u8; 30]; + let mut i = 0usize; + let mut buf1 = [0u8]; + while buf1[0] != b'\n' || i == 0 { + stream.read_exact(&mut buf1).unwrap(); + if buf1[0] == b'\n' && i == 0 { + stream.write_all( + b"\x1b[0m\x1b[31;1mEnter at least one character.\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'; + continue; + } else if buf1[0] == b'\n' && i >= 30 { + stream.write_all( + b"\x1b[0m\x1b[31;1mEnter less than 30 characters.\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; + } else if buf1[0] == b'\n' { + break; + } + if i < 30 { + buf[i] = buf1[0]; + i += 1; + } + } + + stream + .write_fmt(format_args!( + "\x1b[0m\x1b[32;1mWelcome, {}! Joining server \"{}\".\x1b[0m\n", + String::from_utf8_lossy(&(buf[..i])), + channel.lock().unwrap().name + )) + .unwrap(); + + let user = User { + name: String::from_utf8_lossy(&(buf[..i])).to_string(), + status: UserStatus::ONLINE, + }; + + channel.lock().unwrap().online_users.push(user.clone()); + + { + let channel = channel.clone(); + let mut stream = stream.try_clone().unwrap(); + + let time_format = time::format_description::well_known::Rfc2822; + + thread::spawn(move || { + let mut current_message_count = channel.lock().unwrap().messages.len(); + loop { + 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..]; + for msg in new_messages { + stream + .write_fmt(format_args!( + "\x1b[0m\x1b[34m[{}]\x1b[0m \x1b[1m{}\x1b[0m \x1b[31m⇛\x1b[0m \x1b[35m{}\x1b[0m\n", + msg.timestamp.format(&time_format).unwrap(), + msg.sender.name, + msg.contents + )) + .unwrap(); + } + + current_message_count = new_message_count; + } + + // so that not all the threads will sync at once + let randomval = random::random::() / (18446744073709551615u64 / 20); + sleep(std::time::Duration::from_millis(240 + randomval)); + } + }); + } + + let mut buf = Vec::::new(); + let mut buf1 = [0u8]; + + loop { + stream.read_exact(&mut buf1).unwrap(); + + if buf1[0] == b'\x08' { + buf.pop(); + } + + if buf1[0] == b'\n' && buf.len() > 0 { + let contents = String::from_utf8_lossy(&buf).to_string(); + if !contents.starts_with("/") { + channel.lock().unwrap().messages.push(Message { + sender: user.clone(), + contents, + timestamp: time::OffsetDateTime::now_local().unwrap(), + }); + } else { + if contents.starts_with("/help") || contents.starts_with("/?") { + 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", + ) + .unwrap(); + } else if contents.starts_with("/status") { + 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"), + + ) + .unwrap(); + } + } else { + stream + .write_all(b"\x1b[0m\x1b[31;1mThat's an invalid command!\n\x1b[0m") + .unwrap(); + } + } + buf.clear(); + } + if buf1[0] == b'\n' { + continue; + } + buf.push(buf1[0]); + } +} + +fn main() -> std::io::Result<()> { + let listener = TcpListener::bind("0.0.0.0:7867")?; + let channel: Channel = Arc::new(Mutex::new(UnwrappedChannel { + name: "Rawnetchat".to_string(), + messages: vec![], + online_users: vec![], + })); + + for stream in listener.incoming() { + let channel: Channel = channel.clone(); + thread::spawn(|| { + handle_client(stream.unwrap(), channel); + }); + } + + Ok(()) +}