import { getSystemConfig } from '../admin/system-config.service'; import { getAllNotifiers, getNotifier, getAllNotifierParams, notifyWith } from './notifiers'; import { findPushUserForConfig, getPushUserByAccount } from './push-user.service'; import { getDb } from '../database/database'; type NotifyLevel = 'info' | 'warn' | 'error'; interface NotifyChannel { name: string; params: Record; } // Re-export for API routes export { getAllNotifiers, getNotifier, getAllNotifierParams }; // ======================== Global channel management ======================== let _globalChannelsCache: NotifyChannel[] | null = null; let _configHash = ''; /** Returns the full global_notify_config JSON */ export function getGlobalNotifyConfig(): Record { const raw = getSystemConfig('global_notify_config') || '{}'; try { return JSON.parse(raw); } catch { return {}; } } export function getGlobalNotifyConfigs(): NotifyChannel[] { const raw = getSystemConfig('global_notify_config') || '{}'; let globalConfig: any = {}; try { globalConfig = JSON.parse(raw); } catch {} const channels: NotifyChannel[] = []; if (globalConfig.channels) { for (const [name, params] of Object.entries(globalConfig.channels)) { if (params && typeof params === 'object') { channels.push({ name, params: { ...(params as any), title: 'CloudSearch' } }); } } } return channels; } function checkEventEnabled(eventName: string): boolean { try { const raw = getSystemConfig('global_notify_config') || '{}'; let globalConfig: any = {}; try { globalConfig = JSON.parse(raw); } catch {} if (globalConfig.events && globalConfig.events[`on_${eventName}`] !== undefined) { return globalConfig.events[`on_${eventName}`] !== false; } const val = getSystemConfig(`notify_on_${eventName}`); return val !== 'false' && val !== '0'; } catch { return true; } } // ======================== Core send ======================== export async function sendToChannels(channels: NotifyChannel[], title: string, content: string, level: NotifyLevel): Promise { for (const ch of channels) { try { await notifyWith(ch.name, { ...ch.params, title, content, level, }); } catch (err: any) { console.error(`[Notify] ${ch.name} error:`, err.message); } } } // ======================== Export API ======================== export function notify(title: string, content: string, level: NotifyLevel = 'info'): void { const channels = getGlobalNotifyConfigs(); if (channels.length === 0) return; sendToChannels(channels, title, content, level).catch(() => {}); } export function notifyError(title: string, detail: string): void { notify(title, detail, 'error'); } export function notifyWarn(title: string, detail: string): void { notify(title, detail, 'warn'); } export function notifyInfo(title: string, detail: string): void { notify(title, detail, 'info'); } function applyTemplate(template: string, vars: Record): string { return template.replace(/\{([^}]+)\}/g, (_, key) => vars[key] || '{' + key + '}'); } function getEventTemplates(): Record { try { const raw = getSystemConfig('global_notify_config') || '{}'; const cfg = JSON.parse(raw); return cfg.eventTemplates || {}; } catch { return {}; } } export function notifyEvent(eventName: string, title: string, content: string, level: NotifyLevel = 'info', templateVars?: Record): void { if (!checkEventEnabled(eventName)) return; const eventKey = 'on_' + eventName; const templates = getEventTemplates(); const tmpl = templates[eventKey]; if (tmpl && tmpl.content) { const vars = { ...(templateVars || {}) }; if (!vars.content && content) vars.content = content; if (!vars.title && title) vars.title = title; title = applyTemplate(tmpl.title || title, vars); content = applyTemplate(tmpl.content, vars); } notify(title, content, level); } export function notifyConfigEvent( configId: number, eventName: string, title: string, content: string, level: NotifyLevel = 'info', templateVars?: Record ): void { // Apply global template if available const eventKey = 'on_' + eventName; const templates = getEventTemplates(); const tmpl = templates[eventKey]; if (tmpl && tmpl.content) { const vars = { ...(templateVars || {}) }; if (!vars.content && content) vars.content = content; if (!vars.title && title) vars.title = title; title = applyTemplate(tmpl.title || title, vars); content = applyTemplate(tmpl.content, vars); } // Find matching push user by cloud_configs.promotion_account const pushUser = findPushUserForConfig(configId); if (!pushUser) { notifyEvent(eventName, title, content, level, templateVars); return; } let notifyConfig: any = {}; try { notifyConfig = JSON.parse(pushUser.notify_config); } catch {} if (notifyConfig.events && notifyConfig.events[eventKey] === false) return; const userChannels: NotifyChannel[] = []; if (notifyConfig.channels) { for (const [name, params] of Object.entries(notifyConfig.channels)) { userChannels.push({ name, params: { ...(params as any), title } }); } } if (userChannels.length > 0) { sendToChannels(userChannels, title, content, level).catch(() => {}); } else { notifyEvent(eventName, title, content, level, templateVars); } } export async function testChannel( channelName: string, account?: string, directParams?: Record ): Promise<{ success: boolean; message: string }> { let params: Record = {}; if (directParams) { params = directParams; } else if (account) { const user = getPushUserByAccount(account); if (user) { let notifyConfig: any = {}; try { notifyConfig = JSON.parse(user.notify_config); } catch {} const chParams = notifyConfig.channels?.[channelName]; if (!chParams) return { success: false, message: 'User has not configured this channel' }; params = chParams; } else { return { success: false, message: 'Push user not found' }; } } else { const channels = getGlobalNotifyConfigs(); const ch = channels.find(c => c.name === channelName); if (!ch) return { success: false, message: 'Channel not configured globally' }; params = ch.params; } const result = await notifyWith(channelName, { ...params, title: '\u{1F514} CloudSearch Test', content: `Test message\\nChannel: ${channelName}\\nTime: ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`, level: 'info', }); return result; } export function saveConfigNotifySettings(configId: number, notify: any): void { try { const db = getDb(); db.prepare('UPDATE cloud_configs SET notify_config = ? WHERE id = ?').run(JSON.stringify(notify), configId); } catch (err: any) { console.error('[Notify] Failed to save config notify settings:', err.message); } } function getConfigNotifySettings(configId: number): any { try { const db = getDb(); const row = db.prepare('SELECT notify_config FROM cloud_configs WHERE id = ?').get(configId) as any; if (row && row.notify_config) return JSON.parse(row.notify_config); } catch {} return {}; } export function getConfigNotifySettingsJSON(configId: number): any { return getConfigNotifySettings(configId); }