story-kit: merge 320_story_whatsapp_business_api_integration_for_bot_commands

This commit is contained in:
Dave
2026-03-19 23:03:35 +00:00
parent cc0110e577
commit 351f770516
8 changed files with 721 additions and 90 deletions

View File

@@ -66,6 +66,20 @@ pub struct BotConfig {
/// round-tripping.
#[serde(default = "default_transport")]
pub transport: String,
// ── WhatsApp Business API fields ─────────────────────────────────
// These are only required when `transport = "whatsapp"`.
/// WhatsApp Business phone number ID from the Meta dashboard.
#[serde(default)]
pub whatsapp_phone_number_id: Option<String>,
/// Long-lived access token for the WhatsApp Business API.
#[serde(default)]
pub whatsapp_access_token: Option<String>,
/// Verify token used in the webhook handshake (you choose this value
/// and configure it in the Meta webhook settings).
#[serde(default)]
pub whatsapp_verify_token: Option<String>,
}
fn default_transport() -> String {
@@ -97,7 +111,31 @@ impl BotConfig {
{
config.room_ids.push(single);
}
if config.room_ids.is_empty() {
if config.transport == "whatsapp" {
// Validate WhatsApp-specific fields.
if config.whatsapp_phone_number_id.as_ref().is_none_or(|s| s.is_empty()) {
eprintln!(
"[bot] bot.toml: transport=\"whatsapp\" requires \
whatsapp_phone_number_id"
);
return None;
}
if config.whatsapp_access_token.as_ref().is_none_or(|s| s.is_empty()) {
eprintln!(
"[bot] bot.toml: transport=\"whatsapp\" requires \
whatsapp_access_token"
);
return None;
}
if config.whatsapp_verify_token.as_ref().is_none_or(|s| s.is_empty()) {
eprintln!(
"[bot] bot.toml: transport=\"whatsapp\" requires \
whatsapp_verify_token"
);
return None;
}
} else if config.room_ids.is_empty() {
eprintln!(
"[matrix-bot] bot.toml has no room_ids configured — \
add `room_ids = [\"!roomid:example.com\"]` to bot.toml"
@@ -556,10 +594,88 @@ password = "secret"
room_ids = ["!abc:example.com"]
enabled = true
transport = "whatsapp"
whatsapp_phone_number_id = "123456"
whatsapp_access_token = "EAAtoken"
whatsapp_verify_token = "my-verify"
"#,
)
.unwrap();
let config = BotConfig::load(tmp.path()).unwrap();
assert_eq!(config.transport, "whatsapp");
assert_eq!(
config.whatsapp_phone_number_id.as_deref(),
Some("123456")
);
assert_eq!(
config.whatsapp_access_token.as_deref(),
Some("EAAtoken")
);
assert_eq!(
config.whatsapp_verify_token.as_deref(),
Some("my-verify")
);
}
#[test]
fn load_whatsapp_returns_none_when_missing_phone_number_id() {
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"
enabled = true
transport = "whatsapp"
whatsapp_access_token = "EAAtoken"
whatsapp_verify_token = "my-verify"
"#,
)
.unwrap();
assert!(BotConfig::load(tmp.path()).is_none());
}
#[test]
fn load_whatsapp_returns_none_when_missing_access_token() {
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"
enabled = true
transport = "whatsapp"
whatsapp_phone_number_id = "123456"
whatsapp_verify_token = "my-verify"
"#,
)
.unwrap();
assert!(BotConfig::load(tmp.path()).is_none());
}
#[test]
fn load_whatsapp_returns_none_when_missing_verify_token() {
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"
enabled = true
transport = "whatsapp"
whatsapp_phone_number_id = "123456"
whatsapp_access_token = "EAAtoken"
"#,
)
.unwrap();
assert!(BotConfig::load(tmp.path()).is_none());
}
}