initial commit
This commit is contained in:
commit
59da3d909e
7 changed files with 399 additions and 0 deletions
7
Dockerfile
Normal file
7
Dockerfile
Normal file
|
|
@ -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"]
|
||||||
71
README.md
Normal file
71
README.md
Normal file
|
|
@ -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 |
|
||||||
26
docker-compose.yml
Normal file
26
docker-compose.yml
Normal file
|
|
@ -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"
|
||||||
94
feeds.js
Normal file
94
feeds.js
Normal file
|
|
@ -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 };
|
||||||
55
formatter.js
Normal file
55
formatter.js
Normal file
|
|
@ -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 = `<h3>📰 Täglicher News-Digest</h3>`;
|
||||||
|
html += `<p><em>${date}</em></p><hr>`;
|
||||||
|
|
||||||
|
for (const [category, items] of Object.entries(grouped)) {
|
||||||
|
if (items.length === 0) continue;
|
||||||
|
|
||||||
|
plain += `${category}\n${'─'.repeat(30)}\n`;
|
||||||
|
html += `<h4>${category}</h4><ul>`;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const source = item.feedName ? ` [${item.feedName}]` : '';
|
||||||
|
plain += `• ${item.title}${source}\n ${item.link}\n\n`;
|
||||||
|
html += `<li><a href="${item.link}"><strong>${item.title}</strong></a>`;
|
||||||
|
if (item.feedName) html += ` <em>(${item.feedName})</em>`;
|
||||||
|
if (item.summary) html += `<br><small>${item.summary}</small>`;
|
||||||
|
html += `</li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
plain += '\n';
|
||||||
|
html += `</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalItems = Object.values(grouped).flat().length;
|
||||||
|
plain += `\n📊 ${totalItems} Artikel heute · Powered by noctura newsbot`;
|
||||||
|
html += `<p><small>📊 ${totalItems} Artikel heute · Powered by noctura newsbot</small></p>`;
|
||||||
|
|
||||||
|
return { plain, html };
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNoNews() {
|
||||||
|
return {
|
||||||
|
plain: '📰 Heute keine neuen Artikel in den konfigurierten Feeds.',
|
||||||
|
html: '<p>📰 Heute keine neuen Artikel in den konfigurierten Feeds.</p>'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatError(feedName, error) {
|
||||||
|
return `⚠️ Feed-Fehler [${feedName}]: ${error.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { formatDailyDigest, formatNoNews, formatError };
|
||||||
132
index.js
Normal file
132
index.js
Normal file
|
|
@ -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);
|
||||||
|
});
|
||||||
14
package.json
Normal file
14
package.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue