commit 59da3d909ee72c549d86f37d7207ba914d85a583 Author: JaysonCleve Date: Tue Apr 14 08:37:57 2026 +0200 initial commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4197a86 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:20-alpine +WORKDIR /app +COPY package.json . +RUN npm install --production +COPY src/ ./src/ +VOLUME ["/data"] +CMD ["node", "src/index.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..deb1bdd --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# noctura newsbot — Setup + +## 1. Bot-User anlegen (falls noch nicht getan) + +```bash +docker exec -it synapse register_new_matrix_user -u newsbot -p sicherespasswort --no-admin -c /data/homeserver.yaml http://localhost:8008 +``` + +## 2. Access Token holen + +```bash +curl -s -X POST http://localhost:8008/_matrix/client/v3/login \ + -H "Content-Type: application/json" \ + -d '{"type":"m.login.password","user":"newsbot","password":"sicherespasswort"}' \ + | grep -o '"access_token":"[^"]*"' +``` + +Den Token aus der Ausgabe kopieren. + +## 3. Room ID herausfinden + +Im Matrix Client (z.B. Element): +- In den gewünschten Room gehen +- Raumeinstellungen → Erweitert → Room ID kopieren (beginnt mit !) + +Den Bot in den Room einladen: +``` +/invite @newsbot:noctura.dev +``` + +## 4. docker-compose.yml anpassen + +```yaml +MATRIX_ACCESS_TOKEN: "slt_DEIN_TOKEN" +MATRIX_ROOM_ID: "!xxxxx:noctura.dev" +``` + +## 5. Bot starten + +```bash +cd /opt/noctura/newsbot +mkdir data +docker compose up -d --build +``` + +## 6. Test-Digest sofort senden + +```bash +# TEST_RUN kurz auf true setzen +docker compose run --rm -e TEST_RUN=true newsbot +``` + +## Feeds anpassen + +Einfach in `src/feeds.js` neue Feeds hinzufügen: + +```js +{ + category: "🎮 Gaming", + url: "https://www.rockpapershotgun.com/feed", + name: "Rock Paper Shotgun" +} +``` + +## Cron-Zeiten + +| Wert | Bedeutung | +|------|-----------| +| `0 8 * * *` | Täglich 08:00 Uhr | +| `0 8,18 * * *`| Täglich 08:00 und 18:00 Uhr | +| `0 8 * * 1-5` | Nur Montag–Freitag 08:00 Uhr | diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b4748b0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + newsbot: + build: . + container_name: newsbot + restart: unless-stopped + volumes: + - ./data:/data + environment: + # Matrix Homeserver (intern via Docker Host) + MATRIX_HOMESERVER: "http://host.docker.internal:8008" + + # Access Token des newsbot Users (siehe README) + MATRIX_ACCESS_TOKEN: "DEIN_ACCESS_TOKEN_HIER" + + # Room ID des Ziel-Rooms (beginnt mit !) + MATRIX_ROOM_ID: "!ROOM_ID_HIER:noctura.dev" + + # Wann soll der Digest gesendet werden? (Cron-Format) + # Standard: täglich 08:00 Uhr + CRON_SCHEDULE: "0 8 * * *" + + # Auf 'true' setzen um beim Start sofort einen Test zu senden + TEST_RUN: "false" + + extra_hosts: + - "host.docker.internal:host-gateway" diff --git a/feeds.js b/feeds.js new file mode 100644 index 0000000..2db149b --- /dev/null +++ b/feeds.js @@ -0,0 +1,94 @@ +// ============================================================ +// feeds.js — RSS-Feed Konfiguration +// Einfach neue Feeds hinzufügen oder Kategorien anpassen +// ============================================================ + +const FEEDS = [ + // --- KI & Machine Learning --- + { + category: "🤖 KI & Machine Learning", + url: "https://feeds.feedburner.com/blogspot/gJZg", + name: "Google AI Blog" + }, + { + category: "🤖 KI & Machine Learning", + url: "https://openai.com/blog/rss.xml", + name: "OpenAI Blog" + }, + { + category: "🤖 KI & Machine Learning", + url: "https://www.anthropic.com/rss.xml", + name: "Anthropic" + }, + { + category: "🤖 KI & Machine Learning", + url: "https://huggingface.co/blog/feed.xml", + name: "Hugging Face" + }, + + // --- IT Security --- + { + category: "🔐 IT Security", + url: "https://feeds.feedburner.com/TheHackersNews", + name: "The Hacker News" + }, + { + category: "🔐 IT Security", + url: "https://www.bleepingcomputer.com/feed/", + name: "BleepingComputer" + }, + { + category: "🔐 IT Security", + url: "https://krebsonsecurity.com/feed/", + name: "Krebs on Security" + }, + + // --- Smart Home & IoT --- + { + category: "🏠 Smart Home & IoT", + url: "https://www.home-assistant.io/atom.xml", + name: "Home Assistant" + }, + { + category: "🏠 Smart Home & IoT", + url: "https://www.heise.de/thema/smart-home.rss", + name: "Heise Smart Home" + }, + + // --- Web Development --- + { + category: "⟨/⟩ Web Development", + url: "https://css-tricks.com/feed/", + name: "CSS-Tricks" + }, + { + category: "⟨/⟩ Web Development", + url: "https://dev.to/feed", + name: "DEV.to" + }, + { + category: "⟨/⟩ Web Development", + url: "https://github.blog/feed/", + name: "GitHub Blog" + }, + + // --- Self-hosting & Linux --- + { + category: "🖥️ Self-hosting & Linux", + url: "https://selfh.st/articles/index.xml", + name: "selfh.st" + }, + { + category: "🖥️ Self-hosting & Linux", + url: "https://www.heise.de/open/news-atom.xml", + name: "Heise Open" + }, +]; + +// Wie viele Artikel pro Feed maximal gepostet werden +const MAX_ITEMS_PER_FEED = 2; + +// Wie viele Stunden rückwirkend Artikel berücksichtigt werden +const HOURS_LOOKBACK = 24; + +module.exports = { FEEDS, MAX_ITEMS_PER_FEED, HOURS_LOOKBACK }; diff --git a/formatter.js b/formatter.js new file mode 100644 index 0000000..b1517aa --- /dev/null +++ b/formatter.js @@ -0,0 +1,55 @@ +// ============================================================ +// formatter.js — Formatiert News-Nachrichten für Matrix +// ============================================================ + +function formatDailyDigest(grouped) { + const date = new Date().toLocaleDateString('de-DE', { + weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' + }); + + // Plain text version + let plain = `📰 Täglicher News-Digest — ${date}\n`; + plain += '─'.repeat(50) + '\n\n'; + + // HTML version (für Matrix Clients die Formatting unterstützen) + let html = `

📰 Täglicher News-Digest

`; + html += `

${date}


`; + + for (const [category, items] of Object.entries(grouped)) { + if (items.length === 0) continue; + + plain += `${category}\n${'─'.repeat(30)}\n`; + html += `

${category}

`; + } + + const totalItems = Object.values(grouped).flat().length; + plain += `\n📊 ${totalItems} Artikel heute · Powered by noctura newsbot`; + html += `

📊 ${totalItems} Artikel heute · Powered by noctura newsbot

`; + + return { plain, html }; +} + +function formatNoNews() { + return { + plain: '📰 Heute keine neuen Artikel in den konfigurierten Feeds.', + html: '

📰 Heute keine neuen Artikel in den konfigurierten Feeds.

' + }; +} + +function formatError(feedName, error) { + return `⚠️ Feed-Fehler [${feedName}]: ${error.message}`; +} + +module.exports = { formatDailyDigest, formatNoNews, formatError }; diff --git a/index.js b/index.js new file mode 100644 index 0000000..67c0a92 --- /dev/null +++ b/index.js @@ -0,0 +1,132 @@ +// ============================================================ +// 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); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..b333066 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "noctura-newsbot", + "version": "1.0.0", + "description": "Matrix News Bot für noctura.dev", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js" + }, + "dependencies": { + "matrix-bot-sdk": "^0.7.1", + "rss-parser": "^3.13.0", + "node-cron": "^3.0.3" + } +}