commit so i can work elsewhere
This commit is contained in:
parent
58fa9ee185
commit
686d1d74fe
2 changed files with 208 additions and 0 deletions
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "rawnetchat"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
time = { version = "0.3.37", features = ["formatting", "local-offset"] }
|
201
src/main.rs
Normal file
201
src/main.rs
Normal file
|
@ -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<Mutex<UnwrappedChannel>>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct UnwrappedChannel {
|
||||||
|
name: String,
|
||||||
|
messages: Vec<Message>,
|
||||||
|
online_users: Vec<User>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<u64>() / (18446744073709551615u64 / 20);
|
||||||
|
sleep(std::time::Duration::from_millis(240 + randomval));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = Vec::<u8>::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::<Vec<&str>>();
|
||||||
|
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(())
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue