use crate::types::{ChatState, ClientId, Command, RoomInfo}; use serde_json::{json, Value}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::RwLock; pub async fn authorize(client_id: ClientId, command: &Command, state: Arc>) -> bool { let auth_error = { let guard = state.read().await; let Some(client) = guard.clients.get(&client_id) else { return false; }; if !client.logged_in { Some("not_initialized") } else { match (&client.token, &command.token) { (Some(expected), Some(got)) if expected == got => None, (Some(_), Some(_)) => Some("invalid_token"), (Some(_), None) => Some("missing_token"), _ => None, } } }; if let Some(message) = auth_error { send_to_client(client_id, state, json!({"type":"error","message": message})).await; false } else { true } } pub async fn send_room_list(client_id: ClientId, state: Arc>) { let list = { let guard = state.read().await; let mut names = guard.room_meta.keys().cloned().collect::>(); for name in guard.rooms.keys() { if !guard.room_meta.contains_key(name) { names.push(name.clone()); } } names.sort(); names.dedup(); let mut rooms: Vec = names .into_iter() .map(|name| RoomInfo { users: guard.rooms.get(&name).map(|members| members.len()).unwrap_or(0), name, }) .collect(); rooms.sort_by(|a, b| a.name.cmp(&b.name)); rooms }; send_to_client(client_id, state, json!({"type":3, "message": list})).await; } pub async fn send_user_list(client_id: ClientId, state: Arc>) { let users = { let guard = state.read().await; let Some(client) = guard.clients.get(&client_id) else { return; }; if client.room.is_empty() { Vec::::new() } else if let Some(members) = guard.rooms.get(&client.room) { members .iter() .filter_map(|id| guard.clients.get(id)) .map(|u| json!({"name": u.user_name, "color": u.color})) .collect::>() } else { Vec::::new() } }; send_to_client(client_id, state, json!({"type":2, "message": users})).await; } pub async fn send_to_client(client_id: ClientId, state: Arc>, payload: Value) { let msg = payload.to_string(); let tx = { let guard = state.read().await; guard.clients.get(&client_id).map(|c| c.tx.clone()) }; if let Some(tx) = tx { let _ = tx.send(msg); } } pub async fn broadcast_all(state: Arc>, payload: Value) { let msg = payload.to_string(); let targets = { let guard = state.read().await; guard.clients.values().map(|c| c.tx.clone()).collect::>() }; for tx in targets { let _ = tx.send(msg.clone()); } } pub async fn broadcast_room( room: &str, state: Arc>, payload: Value, exclude: Option, ) { let msg = payload.to_string(); let targets = { let guard = state.read().await; let Some(members) = guard.rooms.get(room) else { return; }; members .iter() .filter_map(|id| { if exclude.is_some() && exclude == Some(*id) { return None; } guard.clients.get(id).map(|c| c.tx.clone()) }) .collect::>() }; for tx in targets { let _ = tx.send(msg.clone()); } } pub async fn disconnect_client(client_id: ClientId, state: Arc>) { let (old_room, old_name, token, was_logged_in) = { let mut guard = state.write().await; let Some(client) = guard.clients.remove(&client_id) else { return; }; if !client.room.is_empty() { if let Some(members) = guard.rooms.get_mut(&client.room) { members.remove(&client_id); if members.is_empty() { let is_temporary = guard .room_meta .get(&client.room) .map(|meta| meta.is_temporary) .unwrap_or(false); if is_temporary { if let Some(meta) = guard.room_meta.get_mut(&client.room) { if meta.empty_since_unix.is_none() { meta.empty_since_unix = Some(now_unix()); } } } else { guard.rooms.remove(&client.room); } } } } (client.room, client.user_name, client.token, client.logged_in) }; if let Some(token) = token { let mut guard = state.write().await; guard.tokens.remove(&token); if was_logged_in { guard.logged_in_names.remove(&old_name); } } if !old_room.is_empty() { broadcast_room( &old_room, state, json!({"type":"system","message": format!("{old_name} disconnected")}), Some(client_id), ) .await; } } pub async fn mark_room_occupied(room_name: &str, state: Arc>) { let mut guard = state.write().await; if let Some(meta) = guard.room_meta.get_mut(room_name) { meta.empty_since_unix = None; } } pub async fn mark_room_possibly_empty(room_name: &str, state: Arc>) { let mut guard = state.write().await; let is_empty = guard.rooms.get(room_name).map(|members| members.is_empty()).unwrap_or(true); if !is_empty { return; } if let Some(meta) = guard.room_meta.get_mut(room_name) { if meta.is_temporary && meta.empty_since_unix.is_none() { meta.empty_since_unix = Some(now_unix()); } } } pub async fn cleanup_stale_temporary_rooms(state: Arc>, max_empty_seconds: i64) -> usize { let mut guard = state.write().await; let now = now_unix(); let to_remove = guard .room_meta .iter() .filter_map(|(name, meta)| { if !meta.is_temporary { return None; } let empty_since = meta.empty_since_unix?; if now - empty_since >= max_empty_seconds { Some(name.clone()) } else { None } }) .collect::>(); for room in &to_remove { guard.room_meta.remove(room); guard.rooms.remove(room); guard.dice_games.remove(room); } to_remove.len() } fn now_unix() -> i64 { SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs() as i64) .unwrap_or(0) }