diff --git a/src/commands.rs b/src/commands.rs index a0330db..9825e5f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -892,14 +892,15 @@ async fn handle_create_room_command( if !state::authorize(client_id, command, Arc::clone(&state)).await { return; } - let raw = command - .message - .clone() - .or_else(|| string_from_value(command.value.as_ref())) - .unwrap_or_default(); - let parsed = match parse_create_room_args(&raw) { + let parsed = match parse_create_room_args_from_command(command) { Ok(v) => v, Err(msg) => { + if room_debug_enabled() { + eprintln!( + "[yourchat2][room-debug][create] client_id={client_id} parse_failed error='{msg}' command={:?}", + command + ); + } send_error(client_id, Arc::clone(&state), msg).await; return; } @@ -920,6 +921,12 @@ async fn handle_create_room_command( let mut guard = state.write().await; if resolve_room_name(&guard, &parsed.room_name).is_some() { drop(guard); + if room_debug_enabled() { + eprintln!( + "[yourchat2][room-debug][create] client_id={client_id} room='{}' duplicate", + parsed.room_name + ); + } send_error(client_id, Arc::clone(&state), "room_already_exists").await; return; } @@ -1408,6 +1415,90 @@ fn parse_create_room_args(raw: &str) -> Result { Ok(parsed) } +fn parse_create_room_args_from_command(command: &Command) -> Result { + let raw = command + .message + .clone() + .or_else(|| string_from_value(command.value.as_ref())) + .unwrap_or_default(); + if !raw.trim().is_empty() { + return parse_create_room_args(&raw); + } + + let room_name = command + .room + .clone() + .or(command.name.clone()) + .map(|s| s.trim().to_string()) + .unwrap_or_default(); + if room_name.is_empty() { + return Err("missing_room_name"); + } + let mut parsed = CreateRoomArgs { + room_name, + is_public: true, + gender_restriction_id: None, + min_age: None, + max_age: None, + password: command.password.clone().filter(|v| !v.trim().is_empty()), + friends_of_owner_only: false, + required_user_right_id: None, + room_type_id: None, + }; + + if let Some(Value::Object(map)) = command.value.as_ref() { + if let Some(value) = map.get("public").and_then(value_as_bool) { + parsed.is_public = value; + } + if let Some(value) = map.get("is_public").and_then(value_as_bool) { + parsed.is_public = value; + } + if let Some(value) = map.get("friends_only").and_then(value_as_bool) { + parsed.friends_of_owner_only = value; + } + if let Some(value) = map.get("friends_of_owner_only").and_then(value_as_bool) { + parsed.friends_of_owner_only = value; + } + if let Some(value) = map + .get("gender") + .and_then(value_as_string) + .and_then(|v| parse_gender_mf(&v)) + { + parsed.gender_restriction_id = value; + } + if let Some(value) = map.get("min_age").and_then(value_as_i32) { + parsed.min_age = Some(value); + } + if let Some(value) = map.get("max_age").and_then(value_as_i32) { + parsed.max_age = Some(value); + } + if let Some(value) = map.get("password").and_then(value_as_string) { + if !value.trim().is_empty() { + parsed.password = Some(value); + } + } + if let Some(value) = map.get("required_user_right_id").and_then(value_as_i32) { + parsed.required_user_right_id = Some(value); + } + if let Some(value) = map.get("right_id").and_then(value_as_i32) { + parsed.required_user_right_id = Some(value); + } + if let Some(value) = map.get("room_type_id").and_then(value_as_i32) { + parsed.room_type_id = Some(value); + } + if let Some(value) = map.get("type_id").and_then(value_as_i32) { + parsed.room_type_id = Some(value); + } + } + + if let (Some(min_age), Some(max_age)) = (parsed.min_age, parsed.max_age) { + if min_age > max_age { + return Err("invalid_age_range"); + } + } + Ok(parsed) +} + fn parse_bool(value: &str) -> Option { match value.trim().to_lowercase().as_str() { "1" | "true" | "yes" | "y" | "on" => Some(true), @@ -1416,6 +1507,32 @@ fn parse_bool(value: &str) -> Option { } } +fn value_as_bool(value: &Value) -> Option { + match value { + Value::Bool(b) => Some(*b), + Value::String(s) => parse_bool(s), + Value::Number(n) => n.as_i64().map(|v| v != 0), + _ => None, + } +} + +fn value_as_i32(value: &Value) -> Option { + match value { + Value::Number(n) => n.as_i64().and_then(|v| i32::try_from(v).ok()), + Value::String(s) => s.trim().parse::().ok(), + _ => None, + } +} + +fn value_as_string(value: &Value) -> Option { + match value { + Value::String(s) => Some(s.clone()), + Value::Number(n) => Some(n.to_string()), + Value::Bool(b) => Some(b.to_string()), + _ => None, + } +} + fn parse_gender_mf(value: &str) -> Option> { match value.trim().to_lowercase().as_str() { "m" | "male" => Some(Some(1)),