Implemented the `create_room` command to allow users to create new chat rooms with customizable settings such as privacy, age restrictions, and ownership. Enhanced room management by introducing functions to mark rooms as occupied or possibly empty, and added cleanup logic for stale temporary rooms. Updated the `RoomMeta` structure to include new fields for room creation timestamps and temporary status, ensuring better room lifecycle management.
229 lines
7.0 KiB
Rust
229 lines
7.0 KiB
Rust
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<RwLock<ChatState>>) -> 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<RwLock<ChatState>>) {
|
|
let list = {
|
|
let guard = state.read().await;
|
|
let mut names = guard.room_meta.keys().cloned().collect::<Vec<_>>();
|
|
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<RoomInfo> = 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<RwLock<ChatState>>) {
|
|
let users = {
|
|
let guard = state.read().await;
|
|
let Some(client) = guard.clients.get(&client_id) else {
|
|
return;
|
|
};
|
|
if client.room.is_empty() {
|
|
Vec::<Value>::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::<Vec<_>>()
|
|
} else {
|
|
Vec::<Value>::new()
|
|
}
|
|
};
|
|
send_to_client(client_id, state, json!({"type":2, "message": users})).await;
|
|
}
|
|
|
|
pub async fn send_to_client(client_id: ClientId, state: Arc<RwLock<ChatState>>, 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<RwLock<ChatState>>, payload: Value) {
|
|
let msg = payload.to_string();
|
|
let targets = {
|
|
let guard = state.read().await;
|
|
guard.clients.values().map(|c| c.tx.clone()).collect::<Vec<_>>()
|
|
};
|
|
for tx in targets {
|
|
let _ = tx.send(msg.clone());
|
|
}
|
|
}
|
|
|
|
pub async fn broadcast_room(
|
|
room: &str,
|
|
state: Arc<RwLock<ChatState>>,
|
|
payload: Value,
|
|
exclude: Option<ClientId>,
|
|
) {
|
|
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::<Vec<_>>()
|
|
};
|
|
for tx in targets {
|
|
let _ = tx.send(msg.clone());
|
|
}
|
|
}
|
|
|
|
pub async fn disconnect_client(client_id: ClientId, state: Arc<RwLock<ChatState>>) {
|
|
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<RwLock<ChatState>>) {
|
|
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<RwLock<ChatState>>) {
|
|
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<RwLock<ChatState>>, 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::<Vec<_>>();
|
|
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)
|
|
}
|