const settings = require('./settings'); const db = require('./db'); const nodemailer = require('nodemailer'); // ─── Channel senders ────────────────────────────────────────────────────────── async function sendGotify(title, message) { const { gotify } = settings.get(); if (!gotify.enabled || !gotify.url || !gotify.token) return; const base = gotify.url.replace(/\/$/, ''); try { const res = await fetch(`${base}/message?token=${encodeURIComponent(gotify.token)}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, message, priority: gotify.priority ?? 5 }), }); if (!res.ok) console.error(`[notify] Gotify error ${res.status}: ${await res.text().catch(() => '')}`); } catch (err) { console.error('[notify] Gotify failed:', err.message); } } async function sendNtfy(title, message) { const { ntfy } = settings.get(); if (!ntfy.enabled || !ntfy.url || !ntfy.topic) return; const base = ntfy.url.replace(/\/$/, ''); const headers = { 'Content-Type': 'text/plain', 'Title': title, 'Priority': String(ntfy.priority ?? 3), }; if (ntfy.token) headers['Authorization'] = `Bearer ${ntfy.token}`; try { const res = await fetch(`${base}/${encodeURIComponent(ntfy.topic)}`, { method: 'POST', headers, body: message, }); if (!res.ok) console.error(`[notify] ntfy error ${res.status}: ${await res.text().catch(() => '')}`); } catch (err) { console.error('[notify] ntfy failed:', err.message); } } async function sendSmtp(title, message) { const { smtp } = settings.get(); if (!smtp.enabled || !smtp.host || !smtp.to || !smtp.from) return; try { const transporter = nodemailer.createTransport({ host: smtp.host, port: Number(smtp.port) || 587, secure: smtp.secure === true || smtp.secure === 'true', auth: smtp.username ? { user: smtp.username, pass: smtp.password } : undefined, }); await transporter.sendMail({ from: smtp.from, to: smtp.to, subject: title, text: message, }); } catch (err) { console.error('[notify] SMTP failed:', err.message); } } async function sendWebhook(title, message) { const { webhook } = settings.get(); if (!webhook.enabled || !webhook.url) return; const body = JSON.stringify({ content: `**${title}**\n${message}` }); const headers = { 'Content-Type': 'application/json' }; if (webhook.secret) headers['X-Webhook-Secret'] = webhook.secret; try { const res = await fetch(webhook.url, { method: 'POST', headers, body }); if (!res.ok) console.error(`[notify] Webhook error ${res.status}: ${await res.text().catch(() => '')}`); } catch (err) { console.error('[notify] Webhook failed:', err.message); } } // ─── Broadcast to all enabled channels ─────────────────────────────────────── async function notify(title, message) { await Promise.all([ sendGotify(title, message), sendNtfy(title, message), sendSmtp(title, message), sendWebhook(title, message), ]); } // ─── Exported senders for test routes ──────────────────────────────────────── async function testGotify(cfg) { const base = cfg.url.replace(/\/$/, ''); const res = await fetch(`${base}/message?token=${encodeURIComponent(cfg.token)}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: '🦥 Sloth Manager — Test', message: 'Gotify notifications are working correctly.', priority: cfg.priority ?? 5 }), }); if (!res.ok) throw new Error(`Gotify returned ${res.status}: ${await res.text().catch(() => '')}`); } async function testNtfy(cfg) { const base = cfg.url.replace(/\/$/, ''); const headers = { 'Content-Type': 'text/plain', Title: '🦥 Sloth Manager — Test', Priority: String(cfg.priority ?? 3) }; if (cfg.token) headers['Authorization'] = `Bearer ${cfg.token}`; const res = await fetch(`${base}/${encodeURIComponent(cfg.topic)}`, { method: 'POST', headers, body: 'ntfy notifications are working correctly.' }); if (!res.ok) throw new Error(`ntfy returned ${res.status}: ${await res.text().catch(() => '')}`); } async function testSmtp(cfg) { const transporter = nodemailer.createTransport({ host: cfg.host, port: Number(cfg.port) || 587, secure: cfg.secure === true || cfg.secure === 'true', auth: cfg.username ? { user: cfg.username, pass: cfg.password } : undefined, }); await transporter.verify(); await transporter.sendMail({ from: cfg.from, to: cfg.to, subject: '🦥 Sloth Manager — Test', text: 'SMTP notifications are working correctly.', }); } async function testWebhook(cfg) { const headers = { 'Content-Type': 'application/json' }; if (cfg.secret) headers['X-Webhook-Secret'] = cfg.secret; const res = await fetch(cfg.url, { method: 'POST', headers, body: JSON.stringify({ content: '**🦥 Sloth Manager — Test**\nWebhook notifications are working correctly.' }), }); if (!res.ok) throw new Error(`Webhook returned ${res.status}: ${await res.text().catch(() => '')}`); } // ─── Event helpers ──────────────────────────────────────────────────────────── function eventEnabled(key) { return settings.get().notifications?.[key] !== false; } function recordAdded(provider, zoneId, record) { if (!eventEnabled('dns_add')) return; const zoneName = db.getZoneName(provider, zoneId); return notify(`DNS Record Added`, `[${provider}] ${zoneName}\n+ ${record.type} ${record.name} → ${record.content}`); } function recordUpdated(provider, zoneId, oldRecord, newRecord) { if (!eventEnabled('dns_update')) return; const zoneName = db.getZoneName(provider, zoneId); return notify(`DNS Record Updated`, `[${provider}] ${zoneName}\n✎ ${oldRecord.type} ${oldRecord.name}\n ${oldRecord.content} → ${newRecord.content}`); } function recordDeleted(provider, zoneId, record) { if (!eventEnabled('dns_delete')) return; const zoneName = db.getZoneName(provider, zoneId); return notify(`DNS Record Deleted`, `[${provider}] ${zoneName}\n− ${record.type} ${record.name} ${record.content}`); } module.exports = { notify, testGotify, testNtfy, testSmtp, testWebhook, recordAdded, recordUpdated, recordDeleted };