huskies: merge 472_story_discord_chat_transport

This commit is contained in:
dave
2026-04-04 12:08:39 +00:00
parent ee86e4a3d3
commit c56e462340
14 changed files with 1960 additions and 3 deletions
+127 -1
View File
@@ -64,7 +64,8 @@ pub struct BotConfig {
/// manually while the bot is running.
#[serde(default)]
pub ambient_rooms: Vec<String>,
/// Chat transport to use: `"matrix"` (default) or `"whatsapp"`.
/// Chat transport to use: `"matrix"` (default), `"whatsapp"`, `"slack"`,
/// or `"discord"`.
///
/// Selects which [`ChatTransport`] implementation the bot uses for
/// sending and editing messages. Currently only read during bot
@@ -134,6 +135,20 @@ pub struct BotConfig {
/// Slack channel IDs the bot should listen in.
#[serde(default)]
pub slack_channel_ids: Vec<String>,
// ── Discord Bot API fields ──────────────────────────────────────
// These are only required when `transport = "discord"`.
/// Discord bot token from the Discord Developer Portal.
#[serde(default)]
pub discord_bot_token: Option<String>,
/// Discord channel IDs the bot should listen in.
#[serde(default)]
pub discord_channel_ids: Vec<String>,
/// Discord user IDs allowed to interact with the bot.
/// When empty or absent, all users in configured channels are allowed.
#[serde(default)]
pub discord_allowed_users: Vec<String>,
}
fn default_transport() -> String {
@@ -241,6 +256,22 @@ impl BotConfig {
);
return None;
}
} else if config.transport == "discord" {
// Validate Discord-specific fields.
if config.discord_bot_token.as_ref().is_none_or(|s| s.is_empty()) {
eprintln!(
"[bot] bot.toml: transport=\"discord\" requires \
discord_bot_token"
);
return None;
}
if config.discord_channel_ids.is_empty() {
eprintln!(
"[bot] bot.toml: transport=\"discord\" requires \
at least one discord_channel_ids entry"
);
return None;
}
} else {
// Default transport is Matrix — validate Matrix-specific fields.
if config.homeserver.as_ref().is_none_or(|s| s.is_empty()) {
@@ -1054,4 +1085,99 @@ whatsapp_allowed_phones = ["+15551234567", "+15559876543"]
vec!["+15551234567", "+15559876543"]
);
}
// ── Discord config tests ──────────────────────────────────────────
#[test]
fn load_discord_transport_reads_config() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".huskies");
fs::create_dir_all(&sk).unwrap();
fs::write(
sk.join("bot.toml"),
r#"
enabled = true
transport = "discord"
discord_bot_token = "Bot.Token.Here"
discord_channel_ids = ["123456789012345678"]
"#,
)
.unwrap();
let config = BotConfig::load(tmp.path()).unwrap();
assert_eq!(config.transport, "discord");
assert_eq!(
config.discord_bot_token.as_deref(),
Some("Bot.Token.Here")
);
assert_eq!(
config.discord_channel_ids,
vec!["123456789012345678"]
);
}
#[test]
fn load_discord_returns_none_when_missing_bot_token() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".huskies");
fs::create_dir_all(&sk).unwrap();
fs::write(
sk.join("bot.toml"),
r#"
enabled = true
transport = "discord"
discord_channel_ids = ["123456789012345678"]
"#,
)
.unwrap();
assert!(BotConfig::load(tmp.path()).is_none());
}
#[test]
fn load_discord_returns_none_when_missing_channel_ids() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".huskies");
fs::create_dir_all(&sk).unwrap();
fs::write(
sk.join("bot.toml"),
r#"
enabled = true
transport = "discord"
discord_bot_token = "Bot.Token.Here"
"#,
)
.unwrap();
assert!(BotConfig::load(tmp.path()).is_none());
}
#[test]
fn discord_allowed_users_defaults_to_empty_when_absent() {
let config: BotConfig = toml::from_str(
r#"
enabled = true
transport = "discord"
discord_bot_token = "Bot.Token.Here"
discord_channel_ids = ["123456789"]
"#,
)
.unwrap();
assert!(config.discord_allowed_users.is_empty());
}
#[test]
fn discord_allowed_users_deserializes_list() {
let config: BotConfig = toml::from_str(
r#"
enabled = true
transport = "discord"
discord_bot_token = "Bot.Token.Here"
discord_channel_ids = ["123456789"]
discord_allowed_users = ["111222333", "444555666"]
"#,
)
.unwrap();
assert_eq!(
config.discord_allowed_users,
vec!["111222333", "444555666"]
);
}
}