use crate::{ controllers::site_settings, models::_entities::{comments, friend_links, site_settings as site_settings_model}, services::subscriptions, }; use loco_rs::prelude::*; fn notification_channel_type(settings: &site_settings_model::Model) -> &'static str { match settings .notification_channel_type .as_deref() .map(str::trim) .map(str::to_ascii_lowercase) .as_deref() { Some("ntfy") => subscriptions::CHANNEL_NTFY, _ => subscriptions::CHANNEL_WEBHOOK, } } fn trim_to_option(value: Option) -> Option { value.and_then(|item| { let trimmed = item.trim().to_string(); if trimmed.is_empty() { None } else { Some(trimmed) } }) } fn excerpt(value: Option<&str>, limit: usize) -> Option { let flattened = value? .split_whitespace() .collect::>() .join(" ") .trim() .to_string(); if flattened.is_empty() { return None; } let mut shortened = flattened.chars().take(limit).collect::(); if flattened.chars().count() > limit { shortened.push_str("..."); } Some(shortened) } pub async fn notify_new_comment(ctx: &AppContext, item: &comments::Model) { let settings = match site_settings::load_current(ctx).await { Ok(settings) => settings, Err(error) => { tracing::warn!("failed to load site settings before comment notification: {error}"); return; } }; let payload = serde_json::json!({ "event_type": subscriptions::EVENT_COMMENT_CREATED, "id": item.id, "post_slug": item.post_slug, "author": item.author, "email": item.email, "scope": item.scope, "paragraph_key": item.paragraph_key, "approved": item.approved.unwrap_or(false), "excerpt": excerpt(item.content.as_deref(), 200), "created_at": item.created_at.to_rfc3339(), }); let text = format!( "收到一条新的评论。\n\n文章:{}\n作者:{}\n范围:{}\n状态:{}\n摘要:{}", item.post_slug .clone() .unwrap_or_else(|| "未知文章".to_string()), item.author.clone().unwrap_or_else(|| "匿名".to_string()), item.scope, if item.approved.unwrap_or(false) { "已通过" } else { "待审核" }, excerpt(item.content.as_deref(), 200).unwrap_or_else(|| "无".to_string()), ); if let Err(error) = subscriptions::queue_event_for_active_subscriptions( ctx, subscriptions::EVENT_COMMENT_CREATED, "新评论通知", &text, payload.clone(), trim_to_option(settings.site_name.clone()), trim_to_option(settings.site_url.clone()), ) .await { tracing::warn!("failed to queue comment subscription notification: {error}"); } if settings.notification_comment_enabled.unwrap_or(false) { if let Some(target) = trim_to_option(settings.notification_webhook_url.clone()) { let channel_type = notification_channel_type(&settings); if let Err(error) = subscriptions::queue_direct_notification( ctx, channel_type, &target, subscriptions::EVENT_COMMENT_CREATED, "新评论通知", &text, payload, trim_to_option(settings.site_name), trim_to_option(settings.site_url), ) .await { tracing::warn!("failed to queue comment admin notification: {error}"); } } } } pub async fn notify_new_friend_link(ctx: &AppContext, item: &friend_links::Model) { let settings = match site_settings::load_current(ctx).await { Ok(settings) => settings, Err(error) => { tracing::warn!("failed to load site settings before friend-link notification: {error}"); return; } }; let payload = serde_json::json!({ "event_type": subscriptions::EVENT_FRIEND_LINK_CREATED, "id": item.id, "site_name": item.site_name, "site_url": item.site_url, "category": item.category, "status": item.status, "description": item.description, "created_at": item.created_at.to_rfc3339(), }); let text = format!( "收到新的友链申请。\n\n站点:{}\n链接:{}\n分类:{}\n状态:{}\n描述:{}", item.site_name .clone() .unwrap_or_else(|| "未命名站点".to_string()), item.site_url, item.category .clone() .unwrap_or_else(|| "未分类".to_string()), item.status.clone().unwrap_or_else(|| "pending".to_string()), item.description.clone().unwrap_or_else(|| "无".to_string()), ); if let Err(error) = subscriptions::queue_event_for_active_subscriptions( ctx, subscriptions::EVENT_FRIEND_LINK_CREATED, "新友链申请通知", &text, payload.clone(), trim_to_option(settings.site_name.clone()), trim_to_option(settings.site_url.clone()), ) .await { tracing::warn!("failed to queue friend-link subscription notification: {error}"); } if settings.notification_friend_link_enabled.unwrap_or(false) { if let Some(target) = trim_to_option(settings.notification_webhook_url.clone()) { let channel_type = notification_channel_type(&settings); if let Err(error) = subscriptions::queue_direct_notification( ctx, channel_type, &target, subscriptions::EVENT_FRIEND_LINK_CREATED, "新友链申请通知", &text, payload, trim_to_option(settings.site_name), trim_to_option(settings.site_url), ) .await { tracing::warn!("failed to queue friend-link admin notification: {error}"); } } } }