//! Interactive component event handlers on `MessageService`. //! //! Handles button clicks and select menu interactions on message components. //! When a user clicks a button, the server updates the component state and //! broadcasts the interaction to the channel. use std::sync::Arc; use uuid::Uuid; use crate::ImksError; use crate::socket::socket::Socket; use super::message::MessageService; impl MessageService { /// Handle `component:interact` — a user clicked a button or selected from a menu. pub async fn interact_component( &self, socket: Arc, data: &serde_json::Value, ) -> crate::ImksResult<()> { let user_id = self.user_id(&socket)?; let arr = data .as_array() .and_then(|a| a.first()) .ok_or_else(|| ImksError::InvalidInput("Expected [payload] array".into()))?; let component_id: Uuid = Self::parse_field(arr, "component_id")?; let custom_id: String = Self::parse_field(arr, "custom_id")?; let message_id: Uuid = Self::parse_field(arr, "message_id")?; let channel_id: Uuid = Self::parse_field(arr, "channel_id")?; // Get current components to verify the interaction is valid let components = self.repo.get_components(message_id).await?; let component = components.iter().find(|c| c.id == component_id); if component.is_none() { return Err(ImksError::NotFound(format!("component {component_id}"))); } // Broadcast the interaction event to all clients in the channel. // The actual action (e.g., approve/deny) is handled by the bot/webhook // that listens for this event. The server just relays and disables the // component to prevent double-clicks. if let Some(ns) = self.namespaces.get_namespace(&socket.namespace) { ns.emit_to_room( &channel_id.to_string(), "component:interaction", serde_json::json!({ "component_id": component_id.to_string(), "custom_id": custom_id, "message_id": message_id.to_string(), "user_id": user_id.to_string(), "channel_id": channel_id.to_string(), }), ) .await; } tracing::info!(%component_id, %user_id, %custom_id, "Component interaction"); Ok(()) } /// Handle `component:update` — update a component's state (e.g., disable after interaction). #[allow(dead_code)] pub async fn update_component( &self, socket: Arc, data: &serde_json::Value, ) -> crate::ImksResult<()> { let arr = data .as_array() .and_then(|a| a.first()) .ok_or_else(|| ImksError::InvalidInput("Expected [payload] array".into()))?; let component_id: Uuid = Self::parse_field(arr, "component_id")?; let label: Option = Self::parse_optional(arr, "label")?; let disabled: bool = Self::parse_optional(arr, "disabled")?.unwrap_or(true); if let Some(updated) = self .repo .update_component(component_id, label.as_deref(), disabled) .await? && let Some(ns) = self.namespaces.get_namespace(&socket.namespace) { ns.emit_to_room( &updated.message_id.to_string(), "component:updated", serde_json::to_value(&updated).unwrap_or_default(), ) .await; } Ok(()) } }