Files
Pickup-Config/services/notificationService.js
2025-11-11 09:35:19 +01:00

153 lines
5.1 KiB
JavaScript

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 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
};