Files
iAs/ias-mail/src/config.rs
T
wunianxiao af6cfdaa83 feat: 拆分 worker 架构并增强 assistant 安全保护
- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议

- 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险

- 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则

- 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
2026-07-10 00:41:36 +08:00

157 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<i64>,
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<String>,
pub trusted_senders: Vec<String>,
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<Option<Self>> {
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::<u16>().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::<u64>().ok())
.unwrap_or(60)
.max(10);
let fetch_limit = optional_env("IA_MAIL_FETCH_LIMIT")
.and_then(|value| value.parse::<usize>().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<String> {
env::var(name)
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
fn optional_env_list(names: &[&str]) -> Vec<String> {
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<String> {
normalize_setting_values(
value
.split([',', '', ';', '', '\n'])
.map(ToString::to_string),
)
}
pub(crate) fn normalize_setting_values(values: impl IntoIterator<Item = String>) -> Vec<String> {
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)
}