use matrix_sdk::Client; use std::sync::Arc; use tokio::sync::RwLock; use crate::auth::BotAuth; use crate::commands::CommandRegistry; use crate::event::EventHandler; use crate::room::RoomManager; pub struct BotClient { client: Arc>>, auth: Arc>, commands: Arc>, event_handler: Arc>, room_manager: Arc>, homeserver: String, } impl BotClient { pub fn new(homeserver: &str) -> Self { Self { client: Arc::new(RwLock::new(None)), auth: Arc::new(RwLock::new(BotAuth::new())), commands: Arc::new(RwLock::new(CommandRegistry::new())), event_handler: Arc::new(RwLock::new(EventHandler::new())), room_manager: Arc::new(RwLock::new(RoomManager::new())), homeserver: homeserver.to_string(), } } pub fn with_auth(self, username: &str, password: &str) -> Self { let auth = BotAuth::with_credentials(username, password); self.auth = Arc::new(RwLock::new(auth)); self } pub async fn start(&self) -> anyhow::Result<()> { let client = Client::builder() .homeserver_url(&self.homeserver) .build() .await?; { let mut guard = self.client.write().await; *guard = Some(client.clone()); } let auth = self.auth.read().await; client .matrix_auth() .login_username(&auth.username, &auth.password) .send() .await?; tracing::info!("Bot logged in as {}", auth.username); drop(auth); let mut sync_token: Option = None; loop { let mut settings = matrix_sdk::config::SyncSettings::new(); if let Some(token) = sync_token.as_ref() { settings = settings.token(token.clone()); } match client.sync_once(settings).await { Ok(response) => { sync_token = Some(response.next_batch); let handler = self.event_handler.read().await; handler.dispatch("sync"); drop(handler); let rooms = client.joined_rooms(); let mut room_mgr = self.room_manager.write().await; for room in rooms { let name = room.display_name().await.map(|n| n.to_string()).unwrap_or_default(); room_mgr.add_room(crate::room::RoomInfo { room_id: room.room_id().to_string(), name, is_encrypted: room.is_encrypted().await.unwrap_or(false), }); } } Err(e) => { tracing::error!("Sync error: {}", e); tokio::time::sleep(std::time::Duration::from_secs(5)).await; } } } } pub async fn stop(&self) -> anyhow::Result<()> { let mut guard = self.client.write().await; *guard = None; Ok(()) } pub async fn send_message(&self, room_id: &str, message: &str) -> anyhow::Result<()> { let guard = self.client.read().await; let client = guard.as_ref().ok_or(anyhow::anyhow!("Not connected"))?; let rid = matrix_sdk::ruma::room_id!(room_id) .map_err(|_| anyhow::anyhow!("Invalid room ID"))?; let room = client.get_room(&rid) .ok_or(anyhow::anyhow!("Room not found"))?; let content = matrix_sdk::ruma::events::room::message::RoomMessageEventContent::text_plain(message); let txn_id = matrix_sdk::ruma::TransactionId::new(); room.send(content, Some(&txn_id)).await?; Ok(()) } pub async fn on_command(&self, name: &str, handler: Box) { let mut commands = self.commands.write().await; commands.register(name, handler); } pub async fn on_event(&self, handler: Box) { let mut event_handler = self.event_handler.write().await; event_handler.add_handler(handler); } pub async fn get_rooms(&self) -> Vec { let room_mgr = self.room_manager.read().await; room_mgr.list_rooms().into_iter().cloned().collect() } pub async fn handle_message(&self, room_id: &str, sender: &str, body: &str) { let commands = self.commands.read().await; commands.parse_and_execute(body, sender); } }