const axios = require('axios'); const adminConfig = require('./adminConfig'); const { readNotificationSettings } = require('./userSettingsStore'); function formatDateLabel(dateInput) { try { const date = new Date(dateInput); if (Number.isNaN(date.getTime())) { return 'unbekanntes Datum'; } return date.toLocaleString('de-DE', { weekday: 'short', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } catch (_error) { return 'unbekanntes Datum'; } } async function sendNtfyNotification(adminNtfy, userNtfy, payload) { if (!adminNtfy?.enabled || !userNtfy?.enabled || !userNtfy.topic) { return; } const server = (userNtfy.serverUrl || adminNtfy.serverUrl || 'https://ntfy.sh').replace(/\/+$/, ''); const sanitizedPrefix = (adminNtfy.topicPrefix || '').replace(/^-+|-+$/g, ''); const sanitizedTopic = (userNtfy.topic || '').replace(/^-+|-+$/g, ''); const topic = `${sanitizedPrefix}${sanitizedTopic}` || sanitizedPrefix || sanitizedTopic; let url = `${server}/${topic}`; if (payload.title) { const delimiter = url.includes('?') ? '&' : '?'; url = `${url}${delimiter}title=${encodeURIComponent(payload.title)}`; } const headers = { Priority: payload.priority || 'default', 'Content-Type': 'text/plain; charset=utf-8' }; if (adminNtfy.username && adminNtfy.password) { const credentials = Buffer.from(`${adminNtfy.username}:${adminNtfy.password}`).toString('base64'); headers.Authorization = `Basic ${credentials}`; } await axios.post(url, payload.message, { headers, timeout: 15000 }); } async function sendTelegramNotification(adminTelegram, userTelegram, payload) { if (!adminTelegram?.enabled || !adminTelegram.botToken || !userTelegram?.enabled || !userTelegram.chatId) { return; } const endpoint = `https://api.telegram.org/bot${adminTelegram.botToken}/sendMessage`; await axios.post( endpoint, { chat_id: userTelegram.chatId, text: payload.title ? `*${payload.title}*\n${payload.message}` : payload.message, parse_mode: payload.title ? 'Markdown' : undefined, disable_web_page_preview: true }, { timeout: 15000 } ); } async function notifyChannels(profileId, template) { const adminSettings = adminConfig.readSettings(); const userSettings = readNotificationSettings(profileId); try { await Promise.allSettled([ sendNtfyNotification(adminSettings.notifications?.ntfy, userSettings.notifications?.ntfy, template), sendTelegramNotification(adminSettings.notifications?.telegram, userSettings.notifications?.telegram, template) ]); } catch (error) { console.error('[NOTIFICATIONS] Versand fehlgeschlagen:', error.message); } } async function sendSlotNotification({ profileId, storeName, pickupDate, onlyNotify, booked, storeId }) { const dateLabel = formatDateLabel(pickupDate); const title = onlyNotify ? `Slot verfügbar bei ${storeName}` : booked ? `Slot gebucht bei ${storeName}` : `Slot gefunden bei ${storeName}`; const baseMessage = onlyNotify ? `Es wurde ein freier Slot am ${dateLabel} entdeckt.` : booked ? `Der Slot am ${dateLabel} wurde erfolgreich gebucht.` : `Es wurde ein Slot am ${dateLabel} gefunden.`; const storeLink = storeId ? `https://foodsharing.de/store/${storeId}` : null; const fullMessage = storeLink ? `${baseMessage}\n${storeLink}` : baseMessage; await notifyChannels(profileId, { title, message: fullMessage, link: storeLink, priority: booked ? 'high' : 'default' }); } async function sendStoreWatchNotification({ profileId, storeName, storeId, regionName }) { const storeLink = storeId ? `https://foodsharing.de/store/${storeId}` : null; const title = `Team sucht Verstärkung: ${storeName}`; const regionText = regionName ? ` (${regionName})` : ''; const messageBase = `Der Betrieb${regionText} sucht wieder aktiv neue Teammitglieder.`; const message = storeLink ? `${messageBase}\n${storeLink}` : messageBase; await notifyChannels(profileId, { title, message, link: storeLink, priority: 'high' }); } async function sendDesiredWindowMissedNotification({ profileId, storeName, desiredWindowLabel }) { if (!profileId) { return; } const title = `Keine Slots gefunden: ${storeName}`; const messageBase = desiredWindowLabel ? `Für den gewünschten Zeitraum ${desiredWindowLabel} konnte kein Slot gefunden werden.` : 'Für den gewünschten Zeitraum konnte kein Slot gefunden werden.'; const message = `${messageBase} Der Eintrag wurde deaktiviert.`; await notifyChannels(profileId, { title, message, link: null, priority: 'default' }); } async function sendTestNotification(profileId, channel) { const title = 'Pickup Benachrichtigung (Test)'; const message = 'Das ist eine Testnachricht. Bei Fragen wende dich bitte an den Admin.'; const adminSettings = adminConfig.readSettings(); const userSettings = readNotificationSettings(profileId); const tasks = []; if (!channel || channel === 'ntfy') { tasks.push( sendNtfyNotification(adminSettings.notifications?.ntfy, userSettings.notifications?.ntfy, { title, message, priority: 'default' }) ); } if (!channel || channel === 'telegram') { tasks.push( sendTelegramNotification(adminSettings.notifications?.telegram, userSettings.notifications?.telegram, { title, message, priority: 'default' }) ); } if (tasks.length === 0) { throw new Error('Kein unterstützter Kanal oder Kanal deaktiviert.'); } await Promise.all(tasks); } module.exports = { sendSlotNotification, sendStoreWatchNotification, sendTestNotification, sendDesiredWindowMissedNotification };