From 21cadb630b2a4123ba457281791485cba27b24b0 Mon Sep 17 00:00:00 2001 From: lew Date: Thu, 9 Apr 2026 19:15:37 +0100 Subject: [PATCH] feat: telegram moderation is optional --- .env.example | 8 ++++---- module.nix | 9 +++++++-- src/config.rs | 23 ++++++++++++----------- src/main.rs | 29 +++++++++++++++++------------ src/render.rs | 4 ++-- src/web.rs | 4 ++-- 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/.env.example b/.env.example index 4fda217..e70a646 100644 --- a/.env.example +++ b/.env.example @@ -7,11 +7,11 @@ BOOK_DATA_DIR=./data # Site title shown in nav and page title. BOOK_SITE_TITLE=guestbook -# Telegram bot token. -BOOK_TELEGRAM_BOT_TOKEN=your-bot-token-here +# Telegram bot token. Optional — if unset, telegram moderation is disabled. +# BOOK_TELEGRAM_BOT_TOKEN=your-bot-token-here -# Telegram chat ID for moderation messages. -BOOK_TELEGRAM_CHAT_ID=0 +# Telegram chat ID for moderation messages. Required if bot token is set. +# BOOK_TELEGRAM_CHAT_ID=0 # Enable honeypot field for spam prevention. BOOK_ENABLE_HONEYPOT=true diff --git a/module.nix b/module.nix index 9a4a241..bd23747 100644 --- a/module.nix +++ b/module.nix @@ -106,6 +106,8 @@ in }; telegram = { + enable = mkEnableOption "Telegram moderation notifications"; + botTokenFile = mkOption { type = types.path; description = "Path to a file containing the Telegram bot token."; @@ -222,7 +224,6 @@ in BOOK_DATA_DIR = cfg.dataDir; BOOK_SITE_TITLE = cfg.siteTitle; - BOOK_TELEGRAM_CHAT_ID = toString cfg.telegram.chatId; BOOK_ENABLE_HONEYPOT = if cfg.security.enableHoneypot then "true" else "false"; BOOK_ENABLE_SUBMISSIONS = if cfg.security.enableSubmissions then "true" else "false"; BOOK_ENABLE_HTML_INJECTION = if cfg.security.enableHtmlInjection then "true" else "false"; @@ -248,6 +249,8 @@ in BOOK_STYLE_FILE = cfg.styles.cssFile; } // lib.optionalAttrs (cfg.styles.templateFile != null) { BOOK_TEMPLATE = cfg.styles.templateFile; + } // lib.optionalAttrs cfg.telegram.enable { + BOOK_TELEGRAM_CHAT_ID = toString cfg.telegram.chatId; }; serviceConfig = { Type = "simple"; @@ -261,7 +264,9 @@ in ReadWritePaths = [ cfg.dataDir ]; }; script = '' - export BOOK_TELEGRAM_BOT_TOKEN="$(< "${cfg.telegram.botTokenFile}")" + ${lib.optionalString cfg.telegram.enable '' + export BOOK_TELEGRAM_BOT_TOKEN="$(< "${cfg.telegram.botTokenFile}")" + ''} exec ${cfg.package}/bin/guestbook ''; }; diff --git a/src/config.rs b/src/config.rs index f5872c3..4fc48f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,8 +7,8 @@ pub struct Config { pub data_dir: PathBuf, pub site_title: String, - pub telegram_bot_token: String, - pub telegram_chat_id: i64, + pub telegram_bot_token: Option, + pub telegram_chat_id: Option, pub enable_honeypot: bool, pub max_name_length: usize, pub max_message_length: usize, @@ -49,12 +49,11 @@ impl Config { .unwrap_or_else(|_| PathBuf::from("./data")), site_title: env::var("BOOK_SITE_TITLE").unwrap_or_else(|_| "guestbook".into()), - telegram_bot_token: env::var("BOOK_TELEGRAM_BOT_TOKEN") - .map_err(|_| "BOOK_TELEGRAM_BOT_TOKEN is required")?, + telegram_bot_token: env::var("BOOK_TELEGRAM_BOT_TOKEN").ok(), telegram_chat_id: env::var("BOOK_TELEGRAM_CHAT_ID") - .map_err(|_| "BOOK_TELEGRAM_CHAT_ID is required")? - .parse() - .map_err(|_| "BOOK_TELEGRAM_CHAT_ID must be an integer")?, + .ok() + .map(|v| v.parse().map_err(|_| "BOOK_TELEGRAM_CHAT_ID must be an integer")) + .transpose()?, enable_honeypot: env::var("BOOK_ENABLE_HONEYPOT") .map(|v| v != "false") .unwrap_or(true), @@ -149,7 +148,8 @@ mod tests { assert_eq!(config.listen_addr(), "127.0.0.1:9999"); assert_eq!(config.data_dir, PathBuf::from("/tmp/gb")); assert_eq!(config.site_title, "test.rs"); - assert_eq!(config.telegram_chat_id, 12345); + assert_eq!(config.telegram_bot_token.as_deref(), Some("123:ABC")); + assert_eq!(config.telegram_chat_id, Some(12345)); // Clean up env::remove_var("BOOK_PORT"); @@ -175,13 +175,14 @@ mod tests { } #[test] - fn test_missing_required() { + fn test_telegram_optional() { let _lock = ENV_LOCK.lock().unwrap(); env::remove_var("BOOK_TELEGRAM_BOT_TOKEN"); env::remove_var("BOOK_TELEGRAM_CHAT_ID"); - let result = Config::from_env(); - assert!(result.is_err()); + let config = Config::from_env().unwrap(); + assert!(config.telegram_bot_token.is_none()); + assert!(config.telegram_chat_id.is_none()); } #[test] diff --git a/src/main.rs b/src/main.rs index 76df872..d2875b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,26 +15,31 @@ async fn main() { let config = config::Config::from_env().expect("failed to load config"); let listen = config.listen_addr(); let entries_dir = config.data_dir.join("entries"); - let chat_id = ChatId(config.telegram_chat_id); std::fs::create_dir_all(&entries_dir).ok(); - let bot = Bot::new(&config.telegram_bot_token); - let (tx, rx) = tokio::sync::mpsc::channel(32); + // Spawn telegram tasks if configured + match (&config.telegram_bot_token, config.telegram_chat_id) { + (Some(token), Some(chat_id)) => { + let chat_id = ChatId(chat_id); + let bot = Bot::new(token); + + let notify_bot = bot.clone(); + tokio::spawn(telegram::notification_task(notify_bot, chat_id, rx)); + + let cmd_entries_dir = entries_dir.clone(); + tokio::spawn(telegram::bot_task(bot, chat_id, cmd_entries_dir)); + } + _ => { + tracing::info!("telegram not configured, moderation notifications disabled"); + } + } + let state = Arc::new(web::AppState { config, tx }); let app = web::router(state); - // Spawn telegram notification sender - let notify_bot = bot.clone(); - tokio::spawn(telegram::notification_task(notify_bot, chat_id, rx)); - - // Spawn telegram command listener - let cmd_bot = bot.clone(); - let cmd_entries_dir = entries_dir.clone(); - tokio::spawn(telegram::bot_task(cmd_bot, chat_id, cmd_entries_dir)); - // Run web server tracing::info!("listening on {listen}"); let listener = tokio::net::TcpListener::bind(&listen).await.unwrap(); diff --git a/src/render.rs b/src/render.rs index e535424..1951b65 100644 --- a/src/render.rs +++ b/src/render.rs @@ -118,8 +118,8 @@ mod tests { data_dir: PathBuf::from("./data"), site_title: "test".into(), - telegram_bot_token: "fake".into(), - telegram_chat_id: 0, + telegram_bot_token: None, + telegram_chat_id: None, enable_honeypot: true, max_name_length: 0, max_message_length: 0, diff --git a/src/web.rs b/src/web.rs index 820a011..3276a26 100644 --- a/src/web.rs +++ b/src/web.rs @@ -160,8 +160,8 @@ mod tests { data_dir: dir.to_path_buf(), site_title: "test".into(), - telegram_bot_token: "fake".into(), - telegram_chat_id: 0, + telegram_bot_token: None, + telegram_chat_id: None, enable_honeypot: true, max_name_length: 0, max_message_length: 0,