feat: telegram moderation is optional
This commit is contained in:
parent
75f1644cc1
commit
21cadb630b
6 changed files with 44 additions and 33 deletions
|
|
@ -7,11 +7,11 @@ BOOK_DATA_DIR=./data
|
||||||
# Site title shown in nav and page title.
|
# Site title shown in nav and page title.
|
||||||
BOOK_SITE_TITLE=guestbook
|
BOOK_SITE_TITLE=guestbook
|
||||||
|
|
||||||
# Telegram bot token.
|
# Telegram bot token. Optional — if unset, telegram moderation is disabled.
|
||||||
BOOK_TELEGRAM_BOT_TOKEN=your-bot-token-here
|
# BOOK_TELEGRAM_BOT_TOKEN=your-bot-token-here
|
||||||
|
|
||||||
# Telegram chat ID for moderation messages.
|
# Telegram chat ID for moderation messages. Required if bot token is set.
|
||||||
BOOK_TELEGRAM_CHAT_ID=0
|
# BOOK_TELEGRAM_CHAT_ID=0
|
||||||
|
|
||||||
# Enable honeypot field for spam prevention.
|
# Enable honeypot field for spam prevention.
|
||||||
BOOK_ENABLE_HONEYPOT=true
|
BOOK_ENABLE_HONEYPOT=true
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
telegram = {
|
telegram = {
|
||||||
|
enable = mkEnableOption "Telegram moderation notifications";
|
||||||
|
|
||||||
botTokenFile = mkOption {
|
botTokenFile = mkOption {
|
||||||
type = types.path;
|
type = types.path;
|
||||||
description = "Path to a file containing the Telegram bot token.";
|
description = "Path to a file containing the Telegram bot token.";
|
||||||
|
|
@ -222,7 +224,6 @@ in
|
||||||
BOOK_DATA_DIR = cfg.dataDir;
|
BOOK_DATA_DIR = cfg.dataDir;
|
||||||
BOOK_SITE_TITLE = cfg.siteTitle;
|
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_HONEYPOT = if cfg.security.enableHoneypot then "true" else "false";
|
||||||
BOOK_ENABLE_SUBMISSIONS = if cfg.security.enableSubmissions 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";
|
BOOK_ENABLE_HTML_INJECTION = if cfg.security.enableHtmlInjection then "true" else "false";
|
||||||
|
|
@ -248,6 +249,8 @@ in
|
||||||
BOOK_STYLE_FILE = cfg.styles.cssFile;
|
BOOK_STYLE_FILE = cfg.styles.cssFile;
|
||||||
} // lib.optionalAttrs (cfg.styles.templateFile != null) {
|
} // lib.optionalAttrs (cfg.styles.templateFile != null) {
|
||||||
BOOK_TEMPLATE = cfg.styles.templateFile;
|
BOOK_TEMPLATE = cfg.styles.templateFile;
|
||||||
|
} // lib.optionalAttrs cfg.telegram.enable {
|
||||||
|
BOOK_TELEGRAM_CHAT_ID = toString cfg.telegram.chatId;
|
||||||
};
|
};
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
|
|
@ -261,7 +264,9 @@ in
|
||||||
ReadWritePaths = [ cfg.dataDir ];
|
ReadWritePaths = [ cfg.dataDir ];
|
||||||
};
|
};
|
||||||
script = ''
|
script = ''
|
||||||
|
${lib.optionalString cfg.telegram.enable ''
|
||||||
export BOOK_TELEGRAM_BOT_TOKEN="$(< "${cfg.telegram.botTokenFile}")"
|
export BOOK_TELEGRAM_BOT_TOKEN="$(< "${cfg.telegram.botTokenFile}")"
|
||||||
|
''}
|
||||||
exec ${cfg.package}/bin/guestbook
|
exec ${cfg.package}/bin/guestbook
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ pub struct Config {
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
pub site_title: String,
|
pub site_title: String,
|
||||||
|
|
||||||
pub telegram_bot_token: String,
|
pub telegram_bot_token: Option<String>,
|
||||||
pub telegram_chat_id: i64,
|
pub telegram_chat_id: Option<i64>,
|
||||||
pub enable_honeypot: bool,
|
pub enable_honeypot: bool,
|
||||||
pub max_name_length: usize,
|
pub max_name_length: usize,
|
||||||
pub max_message_length: usize,
|
pub max_message_length: usize,
|
||||||
|
|
@ -49,12 +49,11 @@ impl Config {
|
||||||
.unwrap_or_else(|_| PathBuf::from("./data")),
|
.unwrap_or_else(|_| PathBuf::from("./data")),
|
||||||
site_title: env::var("BOOK_SITE_TITLE").unwrap_or_else(|_| "guestbook".into()),
|
site_title: env::var("BOOK_SITE_TITLE").unwrap_or_else(|_| "guestbook".into()),
|
||||||
|
|
||||||
telegram_bot_token: env::var("BOOK_TELEGRAM_BOT_TOKEN")
|
telegram_bot_token: env::var("BOOK_TELEGRAM_BOT_TOKEN").ok(),
|
||||||
.map_err(|_| "BOOK_TELEGRAM_BOT_TOKEN is required")?,
|
|
||||||
telegram_chat_id: env::var("BOOK_TELEGRAM_CHAT_ID")
|
telegram_chat_id: env::var("BOOK_TELEGRAM_CHAT_ID")
|
||||||
.map_err(|_| "BOOK_TELEGRAM_CHAT_ID is required")?
|
.ok()
|
||||||
.parse()
|
.map(|v| v.parse().map_err(|_| "BOOK_TELEGRAM_CHAT_ID must be an integer"))
|
||||||
.map_err(|_| "BOOK_TELEGRAM_CHAT_ID must be an integer")?,
|
.transpose()?,
|
||||||
enable_honeypot: env::var("BOOK_ENABLE_HONEYPOT")
|
enable_honeypot: env::var("BOOK_ENABLE_HONEYPOT")
|
||||||
.map(|v| v != "false")
|
.map(|v| v != "false")
|
||||||
.unwrap_or(true),
|
.unwrap_or(true),
|
||||||
|
|
@ -149,7 +148,8 @@ mod tests {
|
||||||
assert_eq!(config.listen_addr(), "127.0.0.1:9999");
|
assert_eq!(config.listen_addr(), "127.0.0.1:9999");
|
||||||
assert_eq!(config.data_dir, PathBuf::from("/tmp/gb"));
|
assert_eq!(config.data_dir, PathBuf::from("/tmp/gb"));
|
||||||
assert_eq!(config.site_title, "test.rs");
|
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
|
// Clean up
|
||||||
env::remove_var("BOOK_PORT");
|
env::remove_var("BOOK_PORT");
|
||||||
|
|
@ -175,13 +175,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_missing_required() {
|
fn test_telegram_optional() {
|
||||||
let _lock = ENV_LOCK.lock().unwrap();
|
let _lock = ENV_LOCK.lock().unwrap();
|
||||||
env::remove_var("BOOK_TELEGRAM_BOT_TOKEN");
|
env::remove_var("BOOK_TELEGRAM_BOT_TOKEN");
|
||||||
env::remove_var("BOOK_TELEGRAM_CHAT_ID");
|
env::remove_var("BOOK_TELEGRAM_CHAT_ID");
|
||||||
|
|
||||||
let result = Config::from_env();
|
let config = Config::from_env().unwrap();
|
||||||
assert!(result.is_err());
|
assert!(config.telegram_bot_token.is_none());
|
||||||
|
assert!(config.telegram_chat_id.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
23
src/main.rs
23
src/main.rs
|
|
@ -15,25 +15,30 @@ async fn main() {
|
||||||
let config = config::Config::from_env().expect("failed to load config");
|
let config = config::Config::from_env().expect("failed to load config");
|
||||||
let listen = config.listen_addr();
|
let listen = config.listen_addr();
|
||||||
let entries_dir = config.data_dir.join("entries");
|
let entries_dir = config.data_dir.join("entries");
|
||||||
let chat_id = ChatId(config.telegram_chat_id);
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&entries_dir).ok();
|
std::fs::create_dir_all(&entries_dir).ok();
|
||||||
|
|
||||||
let bot = Bot::new(&config.telegram_bot_token);
|
|
||||||
|
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
||||||
|
|
||||||
let state = Arc::new(web::AppState { config, tx });
|
// Spawn telegram tasks if configured
|
||||||
let app = web::router(state);
|
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);
|
||||||
|
|
||||||
// Spawn telegram notification sender
|
|
||||||
let notify_bot = bot.clone();
|
let notify_bot = bot.clone();
|
||||||
tokio::spawn(telegram::notification_task(notify_bot, chat_id, rx));
|
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();
|
let cmd_entries_dir = entries_dir.clone();
|
||||||
tokio::spawn(telegram::bot_task(cmd_bot, chat_id, cmd_entries_dir));
|
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);
|
||||||
|
|
||||||
// Run web server
|
// Run web server
|
||||||
tracing::info!("listening on {listen}");
|
tracing::info!("listening on {listen}");
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,8 @@ mod tests {
|
||||||
data_dir: PathBuf::from("./data"),
|
data_dir: PathBuf::from("./data"),
|
||||||
site_title: "test".into(),
|
site_title: "test".into(),
|
||||||
|
|
||||||
telegram_bot_token: "fake".into(),
|
telegram_bot_token: None,
|
||||||
telegram_chat_id: 0,
|
telegram_chat_id: None,
|
||||||
enable_honeypot: true,
|
enable_honeypot: true,
|
||||||
max_name_length: 0,
|
max_name_length: 0,
|
||||||
max_message_length: 0,
|
max_message_length: 0,
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,8 @@ mod tests {
|
||||||
data_dir: dir.to_path_buf(),
|
data_dir: dir.to_path_buf(),
|
||||||
site_title: "test".into(),
|
site_title: "test".into(),
|
||||||
|
|
||||||
telegram_bot_token: "fake".into(),
|
telegram_bot_token: None,
|
||||||
telegram_chat_id: 0,
|
telegram_chat_id: None,
|
||||||
enable_honeypot: true,
|
enable_honeypot: true,
|
||||||
max_name_length: 0,
|
max_name_length: 0,
|
||||||
max_message_length: 0,
|
max_message_length: 0,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue