Files
storkit/server/src/matrix/config.rs

136 lines
4.1 KiB
Rust
Raw Normal View History

use serde::Deserialize;
use std::path::Path;
/// Configuration for the Matrix bot, read from `.story_kit/bot.toml`.
#[derive(Deserialize, Clone, Debug)]
pub struct BotConfig {
/// Matrix homeserver URL, e.g. `https://matrix.example.com`
pub homeserver: String,
/// Bot user ID, e.g. `@storykit:example.com`
pub username: String,
/// Bot password
pub password: String,
/// Matrix room ID to join, e.g. `!roomid:example.com`
pub room_id: String,
/// Set to `true` to enable the bot (default: false)
#[serde(default)]
pub enabled: bool,
/// Previously used to select an Anthropic model. Now ignored — the bot
/// uses Claude Code which manages its own model selection. Kept for
/// backwards compatibility so existing bot.toml files still parse.
#[allow(dead_code)]
pub model: Option<String>,
}
impl BotConfig {
/// Load bot configuration from `.story_kit/bot.toml`.
///
/// Returns `None` if the file does not exist, fails to parse, or has
/// `enabled = false`.
pub fn load(project_root: &Path) -> Option<Self> {
let path = project_root.join(".story_kit").join("bot.toml");
if !path.exists() {
return None;
}
let content = std::fs::read_to_string(&path)
.map_err(|e| eprintln!("[matrix-bot] Failed to read bot.toml: {e}"))
.ok()?;
let config: BotConfig = toml::from_str(&content)
.map_err(|e| eprintln!("[matrix-bot] Invalid bot.toml: {e}"))
.ok()?;
if !config.enabled {
return None;
}
Some(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn load_returns_none_when_file_missing() {
let tmp = tempfile::tempdir().unwrap();
let result = BotConfig::load(tmp.path());
assert!(result.is_none());
}
#[test]
fn load_returns_none_when_disabled() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".story_kit");
fs::create_dir_all(&sk).unwrap();
fs::write(
sk.join("bot.toml"),
r#"
homeserver = "https://matrix.example.com"
username = "@bot:example.com"
password = "secret"
room_id = "!abc:example.com"
enabled = false
"#,
)
.unwrap();
let result = BotConfig::load(tmp.path());
assert!(result.is_none());
}
#[test]
fn load_returns_config_when_enabled() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".story_kit");
fs::create_dir_all(&sk).unwrap();
fs::write(
sk.join("bot.toml"),
r#"
homeserver = "https://matrix.example.com"
username = "@bot:example.com"
password = "secret"
room_id = "!abc:example.com"
enabled = true
"#,
)
.unwrap();
let result = BotConfig::load(tmp.path());
assert!(result.is_some());
let config = result.unwrap();
assert_eq!(config.homeserver, "https://matrix.example.com");
assert_eq!(config.username, "@bot:example.com");
assert_eq!(config.room_id, "!abc:example.com");
assert!(config.model.is_none());
}
#[test]
fn load_returns_none_when_toml_invalid() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".story_kit");
fs::create_dir_all(&sk).unwrap();
fs::write(sk.join("bot.toml"), "not valid toml {{{").unwrap();
let result = BotConfig::load(tmp.path());
assert!(result.is_none());
}
#[test]
fn load_respects_optional_model() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".story_kit");
fs::create_dir_all(&sk).unwrap();
fs::write(
sk.join("bot.toml"),
r#"
homeserver = "https://matrix.example.com"
username = "@bot:example.com"
password = "secret"
room_id = "!abc:example.com"
enabled = true
model = "claude-sonnet-4-6"
"#,
)
.unwrap();
let config = BotConfig::load(tmp.path()).unwrap();
assert_eq!(config.model.as_deref(), Some("claude-sonnet-4-6"));
}
}