Initial commit: EifelDC - Discord-like Matrix chat platform
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.
This commit is contained in:
root
2026-04-28 08:23:23 +02:00
commit 0978d0c2e9
82 changed files with 12417 additions and 0 deletions

132
bot-sdk/src/client.rs Normal file
View File

@@ -0,0 +1,132 @@
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);
}
}