Some checks failed
docker-images / resolve-build-targets (push) Successful in 7s
ui-regression / playwright-regression (push) Failing after 13m4s
docker-images / build-and-push (admin) (push) Successful in 1m17s
docker-images / build-and-push (backend) (push) Successful in 28m13s
docker-images / build-and-push (frontend) (push) Successful in 47s
docker-images / submit-indexnow (push) Successful in 13s
style: enhance global CSS for better responsiveness of terminal chips and navigation pills test: remove inline subscription test and add maintenance mode access code test feat: implement media library picker dialog for selecting images from the media library feat: add media URL controls for uploading and managing media assets feat: add migration for music_enabled and maintenance_mode settings in site settings feat: implement maintenance mode functionality with access control feat: create maintenance page with access code input and error handling chore: add TypeScript declaration for QR code module
190 lines
6.1 KiB
Rust
190 lines
6.1 KiB
Rust
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<String>) -> Option<String> {
|
|
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<String> {
|
|
let flattened = value?
|
|
.split_whitespace()
|
|
.collect::<Vec<_>>()
|
|
.join(" ")
|
|
.trim()
|
|
.to_string();
|
|
|
|
if flattened.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let mut shortened = flattened.chars().take(limit).collect::<String>();
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|