Some checks failed
CI / Rust Check (push) Has been cancelled
CI / Rust Tests (push) Has been cancelled
CI / Frontend Check (push) Has been cancelled
CI / Build Tauri (macOS) (push) Has been cancelled
CI / Build Tauri (macOS Intel) (push) Has been cancelled
CI / Build Tauri (Linux) (push) Has been cancelled
Includes server (Rust/Axum API proxy with voice management), Tauri desktop client with Svelte UI, bot-sdk, Docker infra (Synapse, PostgreSQL, Coturn, Nginx), and CI/CD pipeline.
132 lines
4.5 KiB
Rust
132 lines
4.5 KiB
Rust
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<RwLock<Option<Client>>>,
|
|
auth: Arc<RwLock<BotAuth>>,
|
|
commands: Arc<RwLock<CommandRegistry>>,
|
|
event_handler: Arc<RwLock<EventHandler>>,
|
|
room_manager: Arc<RwLock<RoomManager>>,
|
|
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<String> = 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<dyn Fn(&str, &str) + Send + Sync>) {
|
|
let mut commands = self.commands.write().await;
|
|
commands.register(name, handler);
|
|
}
|
|
|
|
pub async fn on_event(&self, handler: Box<dyn Fn(&str) + Send + Sync>) {
|
|
let mut event_handler = self.event_handler.write().await;
|
|
event_handler.add_handler(handler);
|
|
}
|
|
|
|
pub async fn get_rooms(&self) -> Vec<crate::room::RoomInfo> {
|
|
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);
|
|
}
|
|
} |