/* * MC Report System * Copyright (C) 2026 Sea Network Technology Studio * Author: CangLan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ const nodemailer = require('nodemailer'); const { getPool, query, getRow, getConfig } = require('./db'); const { logEmail, logSystem } = require('./logger'); async function getSmtpConfig() { try { const rows = await query("SELECT k, v FROM settings WHERE k LIKE 'smtp_%'"); const cfg = {}; for (const r of rows) cfg[r.k] = r.v; return { host: cfg.smtp_host || '', port: parseInt(cfg.smtp_port) || 587, secure: cfg.smtp_secure === '1', user: cfg.smtp_user || '', pass: cfg.smtp_pass || '', from: cfg.smtp_from || 'noreply@example.com', }; } catch { return { host: '', port: 587, secure: false, user: '', pass: '', from: '' }; } } async function getSiteName() { const row = await getRow("SELECT v FROM settings WHERE k = 'site_name'"); return row ? row.v : 'MC举报系统'; } async function getSiteUrl() { const row = await getRow("SELECT v FROM settings WHERE k = 'site_url'"); return row ? row.v : 'http://localhost:3100'; } async function getTemplate(code) { return getRow('SELECT * FROM email_templates WHERE code = ?', [code]); } function renderTemplate(template, vars) { let { subject, body } = template; for (const [k, v] of Object.entries(vars)) { const re = new RegExp(`\\{\\{${k}\\}\\}`, 'g'); const val = String(v ?? ''); subject = subject.replace(re, () => val); body = body.replace(re, () => val); } return { subject, body }; } let _transporter = null; let _transporterKey = ''; function getTransporter(cfg) { const key = `${cfg.host}:${cfg.port}:${cfg.user}`; if (_transporter && _transporterKey === key) return _transporter; _transporterKey = key; _transporter = nodemailer.createTransport({ host: cfg.host, port: cfg.port, secure: cfg.secure, auth: { user: cfg.user, pass: cfg.pass } }); return _transporter; } async function sendEmail(to, templateCode, vars) { try { const cfg = await getSmtpConfig(); if (!cfg.host || !cfg.user) { console.log('[Mailer] SMTP未配置'); await logSystem('warn', 'mailer', 'SMTP未配置,邮件未发送', { to, template: templateCode }); return false; } const template = await getTemplate(templateCode); if (!template) { console.log('[Mailer] 模板不存在:', templateCode); await logEmail(to, templateCode, '', 'failed', '模板不存在: ' + templateCode); return false; } const siteName = await getSiteName(); const allVars = { ...vars, site_name: siteName, site_url: await getSiteUrl() }; const { subject, body } = renderTemplate(template, allVars); const transporter = getTransporter(cfg); await transporter.sendMail({ from: `"${siteName}" <${cfg.from}>`, to, subject, html: body }); await logEmail(to, templateCode, subject, 'sent'); return true; } catch (err) { console.error('[Mailer]', err.message); await logEmail(to, templateCode, '', 'failed', err.message); return false; } } module.exports = { sendEmail, sendNotifyEmail, renderTemplate, getTemplate, getSmtpConfig, getSiteName, getSiteUrl }; // ---- 事件通知(email 类型配置消费) ---- // 读取 notification_configs 中 type='email' 且 active=1 的配置, 事件匹配则向收件人发信 // email 类型配置的 webhook_url 字段复用为收件人邮箱(逗号分隔) async function sendNotifyEmail(event, data) { try { const configs = await query("SELECT * FROM notification_configs WHERE type = 'email' AND active = 1"); for (const cfg of configs) { const events = cfg.events || 'all'; if (events !== 'all' && !events.split(',').map(s => s.trim()).includes(event)) continue; const recipients = String(cfg.webhook_url || '').split(',').map(s => s.trim()).filter(Boolean); if (!recipients.length) continue; const templateCode = { ticket_created: 'ticket_created', ticket_claimed: 'ticket_claimed', ticket_transferred: 'ticket_transferred', ticket_updated: 'ticket_updated' }[event] || 'ticket_updated'; for (const to of recipients) { sendEmail(to, templateCode, data).catch(() => {}); } } } catch (err) { console.error('[NotifyEmail]', err.message); } }