pub struct CredentialField {
pub name: &'static str,
pub env_var: &'static str,
pub prompt: &'static str,
pub is_secret: bool,
}
pub struct OnboardingStep {
pub instruction: &'static str,
pub url: Option<&'static str>,
}
pub struct OnboardingGuide {
pub platform: &'static str,
pub display_name: &'static str,
pub steps: Vec<OnboardingStep>,
pub credentials: Vec<CredentialField>,
pub webhook_path: &'static str,
}
pub fn available_platforms() -> Vec<(&'static str, &'static str)> {
vec![
("telegram", "Telegram"),
("slack", "Slack"),
("discord", "Discord"),
("whatsapp", "WhatsApp Business"),
("github", "GitHub"),
]
}
pub fn guide_for(platform: &str) -> Option<OnboardingGuide> {
match platform.to_lowercase().as_str() {
"telegram" => Some(OnboardingGuide {
platform: "telegram",
display_name: "Telegram",
steps: vec![
OnboardingStep {
instruction: "Open Telegram and message @BotFather",
url: Some("https://t.me/BotFather"),
},
OnboardingStep {
instruction: "Send /newbot and follow the prompts to create your bot",
url: None,
},
OnboardingStep {
instruction: "Copy the bot token (looks like: 123456789:ABCdefGHIjklMNOpqr)",
url: None,
},
OnboardingStep {
instruction: "The wizard will register the webhook automatically",
url: None,
},
],
credentials: vec![CredentialField {
name: "bot_token",
env_var: "TELEGRAM_BOT_TOKEN",
prompt: "Paste your Telegram bot token",
is_secret: true,
}],
webhook_path: "/api/channels/telegram/webhook",
}),
"slack" => Some(OnboardingGuide {
platform: "slack",
display_name: "Slack",
steps: vec![
OnboardingStep {
instruction: "Go to the Slack API portal and create a new app",
url: Some("https://api.slack.com/apps"),
},
OnboardingStep {
instruction: "Under 'OAuth & Permissions', add bot scopes: chat:write, channels:read, channels:history",
url: None,
},
OnboardingStep {
instruction: "Install the app to your workspace",
url: None,
},
OnboardingStep {
instruction: "Copy the Bot User OAuth Token (starts with xoxb-)",
url: None,
},
OnboardingStep {
instruction: "Under 'Event Subscriptions', set the Request URL to: https://YOUR_DOMAIN:6660/api/channels/slack/webhook",
url: None,
},
],
credentials: vec![CredentialField {
name: "bot_token",
env_var: "SLACK_BOT_TOKEN",
prompt: "Paste your Slack bot token (xoxb-...)",
is_secret: true,
}],
webhook_path: "/api/channels/slack/webhook",
}),
"discord" => Some(OnboardingGuide {
platform: "discord",
display_name: "Discord",
steps: vec![
OnboardingStep {
instruction: "Go to the Discord Developer Portal",
url: Some("https://discord.com/developers/applications"),
},
OnboardingStep {
instruction: "Click 'New Application' and give it a name",
url: None,
},
OnboardingStep {
instruction: "Go to the 'Bot' section and click 'Add Bot'",
url: None,
},
OnboardingStep {
instruction: "Enable 'Message Content Intent' under Privileged Gateway Intents",
url: None,
},
OnboardingStep {
instruction: "Copy the bot token",
url: None,
},
OnboardingStep {
instruction: "Use the OAuth2 URL Generator to invite the bot to your server with 'Send Messages' permission",
url: None,
},
],
credentials: vec![CredentialField {
name: "bot_token",
env_var: "DISCORD_BOT_TOKEN",
prompt: "Paste your Discord bot token",
is_secret: true,
}],
webhook_path: "/api/channels/discord/webhook",
}),
"whatsapp" => Some(OnboardingGuide {
platform: "whatsapp",
display_name: "WhatsApp Business",
steps: vec![
OnboardingStep {
instruction: "Go to Meta for Developers",
url: Some("https://developers.facebook.com"),
},
OnboardingStep {
instruction: "Create a new app and select 'Business' type",
url: None,
},
OnboardingStep {
instruction: "Add the WhatsApp product to your app",
url: None,
},
OnboardingStep {
instruction: "Get your Phone Number ID and Access Token from the WhatsApp settings",
url: None,
},
OnboardingStep {
instruction: "Configure the webhook URL to: https://YOUR_DOMAIN:6660/api/channels/whatsapp/webhook",
url: None,
},
],
credentials: vec![
CredentialField {
name: "access_token",
env_var: "WHATSAPP_ACCESS_TOKEN",
prompt: "Paste your WhatsApp access token",
is_secret: true,
},
CredentialField {
name: "phone_number_id",
env_var: "WHATSAPP_PHONE_NUMBER_ID",
prompt: "Paste your Phone Number ID",
is_secret: false,
},
],
webhook_path: "/api/channels/whatsapp/webhook",
}),
"github" => Some(OnboardingGuide {
platform: "github",
display_name: "GitHub",
steps: vec![
OnboardingStep {
instruction: "Go to GitHub Settings > Developer settings > Personal access tokens",
url: Some("https://github.com/settings/tokens"),
},
OnboardingStep {
instruction: "Generate a new token (classic) with 'repo' scope",
url: None,
},
OnboardingStep {
instruction: "Copy the token (starts with ghp_)",
url: None,
},
OnboardingStep {
instruction: "Set up a webhook on your repo pointing to: https://YOUR_DOMAIN:6660/api/channels/github/webhook",
url: None,
},
],
credentials: vec![CredentialField {
name: "token",
env_var: "GITHUB_TOKEN",
prompt: "Paste your GitHub personal access token",
is_secret: true,
}],
webhook_path: "/api/channels/github/webhook",
}),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_guide_for_telegram() {
let guide = guide_for("telegram").unwrap();
assert_eq!(guide.platform, "telegram");
assert_eq!(guide.display_name, "Telegram");
assert!(!guide.steps.is_empty());
assert_eq!(guide.credentials.len(), 1);
assert_eq!(guide.credentials[0].name, "bot_token");
assert!(guide.credentials[0].is_secret);
}
#[test]
fn test_guide_for_slack() {
let guide = guide_for("slack").unwrap();
assert_eq!(guide.platform, "slack");
assert_eq!(guide.credentials[0].env_var, "SLACK_BOT_TOKEN");
}
#[test]
fn test_guide_for_discord() {
let guide = guide_for("discord").unwrap();
assert_eq!(guide.platform, "discord");
assert_eq!(guide.display_name, "Discord");
}
#[test]
fn test_guide_for_whatsapp() {
let guide = guide_for("whatsapp").unwrap();
assert_eq!(guide.platform, "whatsapp");
assert_eq!(guide.credentials.len(), 2);
assert!(!guide.credentials[1].is_secret);
}
#[test]
fn test_guide_for_github() {
let guide = guide_for("github").unwrap();
assert_eq!(guide.platform, "github");
assert_eq!(guide.credentials[0].name, "token");
}
#[test]
fn test_guide_for_unknown_returns_none() {
assert!(guide_for("unknown_platform").is_none());
}
#[test]
fn test_guide_for_case_insensitive() {
assert!(guide_for("Telegram").is_some());
assert!(guide_for("SLACK").is_some());
assert!(guide_for("Discord").is_some());
}
#[test]
fn test_all_guides_have_webhook_path() {
for platform in &["telegram", "slack", "discord", "whatsapp", "github"] {
let guide = guide_for(platform).unwrap();
assert!(guide.webhook_path.starts_with("/api/channels/"));
}
}
#[test]
fn test_available_platforms_returns_all() {
let platforms = available_platforms();
assert_eq!(platforms.len(), 5);
let ids: Vec<&str> = platforms.iter().map(|(id, _)| *id).collect();
assert!(ids.contains(&"telegram"));
assert!(ids.contains(&"slack"));
assert!(ids.contains(&"discord"));
assert!(ids.contains(&"whatsapp"));
assert!(ids.contains(&"github"));
}
#[test]
fn test_available_platforms_match_guides() {
for (id, display) in available_platforms() {
let guide = guide_for(id).expect(&format!("{} should have a guide", id));
assert_eq!(guide.display_name, display);
}
}
#[test]
fn test_all_guides_have_steps_with_first_url() {
for platform in &["telegram", "slack", "discord", "whatsapp", "github"] {
let guide = guide_for(platform).unwrap();
assert!(
guide.steps[0].url.is_some(),
"{} first step should have a URL",
platform
);
}
}
}