use std::env; use std::time::Duration; use anyhow::anyhow; use crate::model::MailAccount; #[derive(Debug, Clone)] pub struct MailPollerConfig { pub mail_account_id: Option, pub name: String, pub host: String, pub port: u16, pub username: String, pub password: String, pub mailbox: String, pub source_key: String, pub notify_account_id: String, pub notify_user_id: String, pub notification_subject_keywords: Vec, pub trusted_senders: Vec, pub poll_interval: Duration, pub fetch_limit: usize, pub accept_invalid_certs: bool, } impl MailPollerConfig { pub fn from_account(account: MailAccount) -> Self { Self { mail_account_id: Some(account.id), name: account.name, source_key: account.source_key, host: account.imap_host, port: account.imap_port.clamp(1, u16::MAX as i32) as u16, username: account.imap_username, password: account.imap_password, mailbox: account.mailbox, notify_account_id: account.notify_account_id, notify_user_id: account.notify_user_id, notification_subject_keywords: normalize_setting_values( account.notification_subject_keywords, ), trusted_senders: normalize_setting_values(account.trusted_senders), poll_interval: Duration::from_secs(account.poll_seconds.max(10) as u64), fetch_limit: account.fetch_limit.clamp(1, 100) as usize, accept_invalid_certs: account.accept_invalid_certs, } } pub fn from_env() -> anyhow::Result> { if !env_bool("IA_MAIL_ENABLED", true) { return Ok(None); } let host = optional_env("IA_MAIL_IMAP_HOST"); let username = optional_env("IA_MAIL_IMAP_USERNAME"); let password = optional_env("IA_MAIL_IMAP_PASSWORD"); let notify_account_id = optional_env("IA_MAIL_NOTIFY_ACCOUNT_ID"); let notify_user_id = optional_env("IA_MAIL_NOTIFY_USER_ID") .or_else(|| optional_env("IA_MAIL_NOTIFY_FROM_USER_ID")); let required_values = [ host.as_ref(), username.as_ref(), password.as_ref(), notify_account_id.as_ref(), notify_user_id.as_ref(), ]; if required_values.iter().all(|value| value.is_none()) { return Ok(None); } if required_values.iter().any(|value| value.is_none()) { return Err(anyhow!( "邮件轮询配置不完整,需要 IA_MAIL_IMAP_HOST、IA_MAIL_IMAP_USERNAME、IA_MAIL_IMAP_PASSWORD、IA_MAIL_NOTIFY_ACCOUNT_ID、IA_MAIL_NOTIFY_USER_ID" )); } let host = host.unwrap(); let username = username.unwrap(); let port = optional_env("IA_MAIL_IMAP_PORT") .and_then(|value| value.parse::().ok()) .unwrap_or(993); let mailbox = optional_env("IA_MAIL_IMAP_MAILBOX").unwrap_or_else(|| "INBOX".to_string()); let poll_seconds = optional_env("IA_MAIL_POLL_SECONDS") .and_then(|value| value.parse::().ok()) .unwrap_or(60) .max(10); let fetch_limit = optional_env("IA_MAIL_FETCH_LIMIT") .and_then(|value| value.parse::().ok()) .unwrap_or(10) .clamp(1, 100); let notification_subject_keywords = optional_env_list(&[ "IA_MAIL_NOTIFICATION_SUBJECT_KEYWORDS", "IA_MAIL_NOTIFY_SUBJECT_KEYWORDS", ]); let trusted_senders = optional_env_list(&["IA_MAIL_TRUSTED_SENDERS"]); Ok(Some(Self { mail_account_id: None, name: username.clone(), source_key: format!("imap:{host}:{username}"), host, port, username, password: password.unwrap(), mailbox, notify_account_id: notify_account_id.unwrap(), notify_user_id: notify_user_id.unwrap(), notification_subject_keywords, trusted_senders, poll_interval: Duration::from_secs(poll_seconds), fetch_limit, accept_invalid_certs: env_bool("IA_MAIL_ACCEPT_INVALID_CERTS", false), })) } } fn optional_env(name: &str) -> Option { env::var(name) .ok() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) } fn optional_env_list(names: &[&str]) -> Vec { names .iter() .find_map(|name| optional_env(name)) .map(|value| parse_setting_list(&value)) .unwrap_or_default() } pub(crate) fn parse_setting_list(value: &str) -> Vec { normalize_setting_values( value .split([',', ',', ';', ';', '\n']) .map(ToString::to_string), ) } pub(crate) fn normalize_setting_values(values: impl IntoIterator) -> Vec { values .into_iter() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) .collect() } fn env_bool(name: &str, default: bool) -> bool { env::var(name) .map(|value| { let value = value.trim().to_ascii_lowercase(); !matches!(value.as_str(), "0" | "false" | "no" | "off") }) .unwrap_or(default) }