// ============================================================ // index.js — noctura newsbot // Matrix Bot der täglich News-Feeds postet // ============================================================ const { MatrixClient, SimpleFsStorageProvider, AutojoinRoomsMixin } = require('matrix-bot-sdk'); const RSSParser = require('rss-parser'); const cron = require('node-cron'); const { FEEDS, MAX_ITEMS_PER_FEED, HOURS_LOOKBACK } = require('./feeds'); const { formatDailyDigest, formatNoNews, formatError } = require('./formatter'); // ---- Konfiguration aus Umgebungsvariablen ---- const HOMESERVER = process.env.MATRIX_HOMESERVER || 'http://localhost:8008'; const ACCESS_TOKEN = process.env.MATRIX_ACCESS_TOKEN; const ROOM_ID = process.env.MATRIX_ROOM_ID; const CRON_SCHEDULE= process.env.CRON_SCHEDULE || '0 8 * * *'; // täglich 08:00 Uhr if (!ACCESS_TOKEN) { console.error('❌ MATRIX_ACCESS_TOKEN fehlt!'); process.exit(1); } if (!ROOM_ID) { console.error('❌ MATRIX_ROOM_ID fehlt!'); process.exit(1); } // ---- Matrix Client Setup ---- const storage = new SimpleFsStorageProvider('/data/bot-storage.json'); const client = new MatrixClient(HOMESERVER, ACCESS_TOKEN, storage); AutojoinRoomsMixin.setupOnClient(client); const parser = new RSSParser({ timeout: 10000, headers: { 'User-Agent': 'noctura-newsbot/1.0' } }); // ---- RSS Feed abrufen ---- async function fetchFeed(feed) { try { const parsed = await parser.parseURL(feed.url); const cutoff = Date.now() - HOURS_LOOKBACK * 60 * 60 * 1000; return parsed.items .filter(item => { const pubDate = item.pubDate ? new Date(item.pubDate).getTime() : Date.now(); return pubDate >= cutoff; }) .slice(0, MAX_ITEMS_PER_FEED) .map(item => ({ title: item.title?.trim() || 'Kein Titel', link: item.link || item.guid || '', summary: item.contentSnippet ? item.contentSnippet.slice(0, 120).trim() + '…' : '', feedName: feed.name, category: feed.category, pubDate: item.pubDate })); } catch (err) { console.error(`⚠️ Feed-Fehler [${feed.name}]: ${err.message}`); return []; } } // ---- Alle Feeds abrufen & gruppieren ---- async function fetchAllNews() { console.log('🔍 Feeds werden abgerufen...'); const results = await Promise.allSettled(FEEDS.map(fetchFeed)); const grouped = {}; results.forEach((result, i) => { if (result.status !== 'fulfilled') return; const items = result.value; items.forEach(item => { if (!grouped[item.category]) grouped[item.category] = []; grouped[item.category].push(item); }); }); return grouped; } // ---- Nachricht in Matrix Room senden ---- async function sendToMatrix(plain, html) { await client.sendMessage(ROOM_ID, { msgtype: 'm.text', body: plain, format: 'org.matrix.custom.html', formatted_body: html }); console.log('✅ Digest erfolgreich gesendet!'); } // ---- Haupt-Job ---- async function runDigest() { console.log(`\n⏰ [${new Date().toISOString()}] Täglicher Digest wird erstellt...`); try { const grouped = await fetchAllNews(); const totalItems = Object.values(grouped).flat().length; console.log(`📊 ${totalItems} Artikel gefunden`); if (totalItems === 0) { const { plain, html } = formatNoNews(); await sendToMatrix(plain, html); } else { const { plain, html } = formatDailyDigest(grouped); await sendToMatrix(plain, html); } } catch (err) { console.error('❌ Fehler beim Digest:', err); } } // ---- Bot starten ---- async function main() { console.log('🚀 noctura newsbot startet...'); console.log(` Homeserver: ${HOMESERVER}`); console.log(` Room: ${ROOM_ID}`); console.log(` Cron: ${CRON_SCHEDULE}`); await client.start(); console.log('✅ Matrix Client verbunden!'); // Cron Job registrieren cron.schedule(CRON_SCHEDULE, runDigest, { timezone: 'Europe/Berlin' }); console.log(`📅 Cron Job aktiv: ${CRON_SCHEDULE} (Europe/Berlin)`); // Beim Start einmal sofort ausführen wenn TEST_RUN gesetzt if (process.env.TEST_RUN === 'true') { console.log('🧪 TEST_RUN aktiv – einmalig sofort ausführen...'); await runDigest(); } } main().catch(err => { console.error('❌ Bot-Fehler:', err); process.exit(1); });