From cc4df25754ee645b13c1d8e1db73d07d98ac6407 Mon Sep 17 00:00:00 2001 From: fkrebs Date: Sat, 23 May 2026 09:40:42 +0200 Subject: [PATCH] init: export 3 workflows, add IDEAS.md + README --- IDEAS.md | 70 +++++++++++ README.md | 32 ++++- flows/karakeep-to-memos.json | 178 +++++++++++++++++++++++++++ flows/recipe.json | 227 +++++++++++++++++++++++++++++++++++ flows/rss-archive-proxy.json | 215 +++++++++++++++++++++++++++++++++ 5 files changed, 720 insertions(+), 2 deletions(-) create mode 100644 IDEAS.md create mode 100644 flows/karakeep-to-memos.json create mode 100644 flows/recipe.json create mode 100644 flows/rss-archive-proxy.json diff --git a/IDEAS.md b/IDEAS.md new file mode 100644 index 0000000..d52378d --- /dev/null +++ b/IDEAS.md @@ -0,0 +1,70 @@ +# n8n Flow Ideas + +Backlog of automation ideas to build. Implement via n8n UI at https://n8n.nuclide.systems or API. + +## 🔴 High priority + +### Homelab Health Check (hourly) +**Status:** Prototype as bash/systemd on nuc. Migrate to n8n. +**What:** Every hour — check critical containers (Pocket-ID, LiteLLM, Grafana, Homarr, Nextcloud, HA), disk usage, key HTTP endpoints. Auto-restart stopped containers. Send Gotify alert for crash loops, disk >90%, VM down. +**Nodes:** Cron → SSH (per host, check docker ps) → IF (status != running) → Execute Command (docker start) → Gotify HTTP. +**Gotify token:** Az9NpC-m1jjf571 (homelab-health app), URL: http://192.168.1.40:10003 + +### Backrest backup result notification +**Status:** Not built. +**What:** After each Backrest snapshot (Pocket-ID, media, services plans), notify Gotify with summary: plan name, files added, size, duration, next run. On failure, priority 10 alert. +**How:** Backrest has webhooks (post-backup hook) → n8n webhook → Gotify. +**Nodes:** Webhook → Parse Backrest payload → Gotify. + +### Nextcloud/Paperless → Docling → embeddings pipeline +**Status:** Docling MCP deployed; pipeline not built. +**What:** Watch Nextcloud for new documents → send to Docling for OCR/extraction → chunk + embed → store in Chroma → index in LobeChat knowledge base. +**Nodes:** Cron (poll Nextcloud activity) or Nextcloud webhook → HTTP (Docling API) → HTTP (Chroma upsert) → optional Gotify confirmation. + +## 🟠 Medium priority + +### HA entity alert → Gotify bridge +**Status:** Gotify notify platform configured in HA but no automation rules written. +**What:** HA automations for: EV charger state changes, door open >10 min, CO₂ > 1200 ppm, low battery (<20%) on sensors. Send formatted Gotify push via n8n or directly. +**Nodes:** HA webhook automation → n8n webhook → format message → Gotify. + +### Gitea repo activity digest (daily) +**Status:** Not built. +**What:** Every morning at 08:00, pull yesterday's commits across all homelab repos, format into a digest, send to Gotify (priority 3 — FYI). +**Nodes:** Cron → HTTP (Gitea API /repos/fkrebs/*/commits?since=yesterday) → Code (aggregate) → Gotify. + +### Immich face/album auto-tagging +**Status:** Not built. +**What:** On new Immich upload, run face recognition → auto-assign to album based on person. Optional: generate caption via LiteLLM vision model. +**Nodes:** Cron (poll Immich /api/asset?isFavorite=false&order=asc) → HTTP (Immich album API) → optional LiteLLM caption. + +### WAL-G backup health monitor +**Status:** bash-level only; silent stall went undetected 13h. +**What:** Every hour, query Garage S3 for latest WAL-G backup timestamps per DB (immich, lobe). If last backup > 26h, send Gotify alert priority 8. +**Nodes:** Cron → Execute Command (mc ls on Garage) or HTTP (Garage S3 API) → IF (age > 26h) → Gotify. + +### Karakeep → memos enrichment (extend existing flow) +**Status:** Karakeep-to-memos flow exists; basic save only. +**What:** Extend: after saving link to Memos, also: (1) fetch page via crawl4ai MCP for summary, (2) generate tags via LiteLLM, (3) update Memos entry with summary + tags. +**Nodes:** Extend existing Karakeep-to-memos → HTTP (LiteLLM /v1/chat/completions) → HTTP (Memos PATCH). + +## 🔵 Nice-to-have + +### Coder workspace lifecycle alerts +**Status:** Planned (see memory: coder-monitoring-gotify). +**What:** When a Coder workspace starts/stops/fails build, send Gotify notification with workspace name, owner, duration. +**Nodes:** Cron (poll Coder API /api/v2/workspaces) → compare state → Gotify on change. + +### n8n workflow backup reminder +**Status:** Covered by this repo's daily sync cron. +**What:** If sync cron detects uncommitted changes, send Gotify reminder to review new/changed flows. + +### Paperless document classification +**Status:** Not built. +**What:** On new Paperless document → send text to LiteLLM → suggest title, tags, correspondent, document_type → auto-update via Paperless API. +**Nodes:** Paperless webhook or cron poll → HTTP (LiteLLM) → HTTP (Paperless PATCH /api/documents/{id}/). + +### Traccar GPS geofence → HA +**Status:** Not built. Traccar running on CT 104 :13005. +**What:** When a tracked device enters/leaves home geofence → call HA webhook to trigger automations (e.g. arm/disarm alarm, preheat EV). +**Nodes:** Traccar webhook → HTTP (HA webhook). diff --git a/README.md b/README.md index 665bfd6..c9f4858 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ -# n8n-flows +# n8n Flows -n8n workflow JSON exports + automation ideas backlog \ No newline at end of file +Workflow JSON exports + ideas for the nuclide.systems homelab n8n instance at . + +## Sync + +A daily cron on CT 104 (03:30) exports all workflows → splits by name → commits + pushes here. + +To restore a flow: import the JSON in n8n UI → **Workflows → Import from file**. + +## Contents + +``` +flows/ # exported workflow JSONs (one per flow) +IDEAS.md # automation ideas backlog +README.md # this file +``` + +## Live flows + +| Name | File | Status | +|---|---|---| +| Karakeep to memos | flows/karakeep-to-memos.json | active | +| RSS Archive Proxy | flows/rss-archive-proxy.json | active | +| Recipe | flows/recipe.json | active | + +## Connection info + +- n8n: https://n8n.nuclide.systems (OIDC via Pocket-ID) +- Gotify: http://192.168.1.40:10003 (homelab-health token: Az9NpC-m1jjf571) +- LiteLLM: http://192.168.1.40:4000 (Bearer sk-tapirnase) diff --git a/flows/karakeep-to-memos.json b/flows/karakeep-to-memos.json new file mode 100644 index 0000000..58b79d2 --- /dev/null +++ b/flows/karakeep-to-memos.json @@ -0,0 +1,178 @@ +{ + "updatedAt": "2026-05-05T14:20:10.843Z", + "createdAt": "2026-04-30T12:43:53.619Z", + "id": "RUKqMgUeDhHihpjl", + "name": "Karakeep to memos", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "url": "=https://hoarder.nuclide.systems/api/v1/bookmarks/{{ $json.body.bookmarkId }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpBearerAuth", + "options": {} + }, + "id": "ba3c1bdf-4ad5-4511-8955-a99b4e48b3c5", + "name": "Get Bookmark Details", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 16, + 0 + ], + "credentials": { + "httpHeaderAuth": { + "id": "Zo5gX3WVup7SHgb9", + "name": "Header Auth account" + }, + "httpBearerAuth": { + "id": "pMCAiDIO9dOnP1Q3", + "name": "karakeep" + } + } + }, + { + "parameters": { + "httpMethod": "POST", + "path": "karakeep-to-memos", + "options": {} + }, + "id": "f99858b6-a963-4f4e-85fe-1e1d8f269490", + "name": "Webhook1", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + -208, + 0 + ], + "webhookId": "ec607cac-2d65-4eff-9e5e-f2a4ee0cb8a6" + }, + { + "parameters": { + "method": "POST", + "url": "https://memos.nuclide.systems/api/v1/memos", + "authentication": "genericCredentialType", + "genericAuthType": "httpBearerAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"content\": \"\ud83d\udd16 **New Bookmark:** {{ $json.title }}\\n{{ $json.content.url }}\\n\\n> {{ $json.content.description || 'No description available' }}\\n\\n#source/karakeep {{ $json.tags.map(t => '#' + t.name).join(' ') }}\",\n \"visibility\": \"PRIVATE\"\n}", + "options": {} + }, + "id": "325efc5b-5b02-412d-8d2b-db7254ca2a43", + "name": "Post to Memos1", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + 0 + ], + "credentials": { + "httpHeaderAuth": { + "id": "Zo5gX3WVup7SHgb9", + "name": "Header Auth account" + }, + "httpBearerAuth": { + "id": "tNeQXyIEOdyqe2VI", + "name": "memos" + } + } + } + ], + "connections": { + "Get Bookmark Details": { + "main": [ + [ + { + "node": "Post to Memos1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook1": { + "main": [ + [ + { + "node": "Get Bookmark Details", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate" + }, + "staticData": null, + "meta": null, + "pinData": { + "Webhook1": [ + { + "json": { + "headers": { + "host": "n8n.nuclide.systems", + "user-agent": "node-fetch", + "content-length": "184", + "accept": "*/*", + "accept-encoding": "gzip, deflate, br", + "connection": "keep-alive", + "content-type": "application/json", + "x-forwarded-for": "192.168.1.1", + "x-forwarded-host": "n8n.nuclide.systems", + "x-forwarded-proto": "https", + "x-forwarded-server": "zoraxy-5280418d-4b2e-4b04-9868-bd361bb11c66", + "x-real-ip": "192.168.1.1" + }, + "params": {}, + "query": {}, + "body": { + "jobId": "21080", + "bookmarkId": "vzul58l5ocml1u5pugx331xc", + "userId": "sxcjjxhynks252ih65ud9e7z", + "url": "https://www.daytona.io/docs/en/oss-deployment/", + "type": "link", + "operation": "crawled" + }, + "webhookUrl": "https://n8n.nuclide.systems/ - PUID=997/webhook/karakeep-to-memos", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "bff9acfc-6fa2-4b09-8df1-c005a3bc9a7a", + "activeVersionId": "bff9acfc-6fa2-4b09-8df1-c005a3bc9a7a", + "versionCounter": 77, + "triggerCount": 1, + "tags": [], + "shared": [ + { + "updatedAt": "2026-04-30T12:43:53.622Z", + "createdAt": "2026-04-30T12:43:53.622Z", + "role": "workflow:owner", + "workflowId": "RUKqMgUeDhHihpjl", + "projectId": "hkIaTyp1CKVS8ywT", + "project": { + "updatedAt": "2026-03-09T07:21:36.510Z", + "createdAt": "2026-03-09T07:20:59.502Z", + "id": "hkIaTyp1CKVS8ywT", + "name": "Florian Krebs ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "8af5c813-f89c-4b83-bcf8-5d59864a038a" + } + } + ], + "versionMetadata": { + "name": "Version bff9acfc", + "description": "" + } +} \ No newline at end of file diff --git a/flows/recipe.json b/flows/recipe.json new file mode 100644 index 0000000..a0cde19 --- /dev/null +++ b/flows/recipe.json @@ -0,0 +1,227 @@ +{ + "updatedAt": "2026-04-11T10:18:14.686Z", + "createdAt": "2026-03-09T07:21:50.076Z", + "id": "mXYla2g5o8kLumAJ", + "name": "Recipe", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "recipe-import", + "options": {} + }, + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 0, + 0 + ], + "id": "77aa56a0-bb5c-4a0d-877e-940dc46a1169", + "webhookId": "ed5885d6-7a81-4a35-8998-8e540991ff21" + }, + { + "parameters": { + "url": "={{$node[\"Webhook\"].json[\"body\"][\"url\"]}}", + "options": {} + }, + "name": "Fetch HTML", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 224, + 0 + ], + "id": "9f8833dd-be35-4d09-bbc0-53ac053ea3f2", + "retryOnFail": true, + "notesInFlow": false + }, + { + "parameters": { + "method": "POST", + "url": "https://nc.nuclide.systems/index.php/apps/cookbook/api/v1/recipes", + "authentication": "genericCredentialType", + "genericAuthType": "httpBasicAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.output }}", + "options": {} + }, + "name": "Post to Cookbook", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1088, + 0 + ], + "id": "478bdf04-c93e-4e81-ad81-7584ee5e2c62", + "credentials": { + "httpBasicAuth": { + "id": "LIjnFZEJ6LNkhOAR", + "name": "Nextcloud" + } + } + }, + { + "parameters": { + "text": "={{ $json.data }}", + "schemaType": "manual", + "inputSchema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"title\": \"Recipe (schema.org)\",\n \"description\": \"Ein Kochrezept basierend auf schema.org/Recipe\",\n \"type\": \"object\",\n \"required\": [\"@context\", \"@type\", \"name\", \"recipeIngredient\", \"recipeInstructions\"],\n \"properties\": {\n \"@context\": {\n \"type\": \"string\",\n \"const\": \"https://schema.org\"\n },\n \"@type\": {\n \"type\": \"string\",\n \"const\": \"Recipe\"\n },\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Der Name des Gerichts.\"\n },\n \"image\": {\n \"oneOf\": [\n { \"type\": \"string\", \"format\": \"uri\" },\n { \"type\": \"array\", \"items\": { \"type\": \"string\", \"format\": \"uri\" } }\n ],\n \"description\": \"URL zu einem Bild des fertigen Gerichts.\"\n },\n \"author\": {\n \"type\": \"object\",\n \"properties\": {\n \"@type\": { \"type\": \"string\", \"enum\": [\"Person\", \"Organization\"] },\n \"name\": { \"type\": \"string\" }\n }\n },\n \"datePublished\": {\n \"type\": \"string\",\n \"format\": \"date\"\n },\n \"description\": {\n \"type\": \"string\",\n \"description\": \"Kurze Zusammenfassung des Rezepts.\"\n },\n \"prepTime\": {\n \"type\": \"string\",\n \"pattern\": \"^P([0-9]+Y)?([0-9]+M)?([0-9]+D)?T?([0-9]+H)?([0-9]+M)?([0-9]+S)?$\",\n \"description\": \"Vorbereitungszeit im ISO 8601 Format (z.B. PT15M).\"\n },\n \"cookTime\": {\n \"type\": \"string\",\n \"pattern\": \"^P([0-9]+Y)?([0-9]+M)?([0-9]+D)?T?([0-9]+H)?([0-9]+M)?([0-9]+S)?$\",\n \"description\": \"Kochzeit im ISO 8601 Format.\"\n },\n \"totalTime\": {\n \"type\": \"string\",\n \"pattern\": \"^P([0-9]+Y)?([0-9]+M)?([0-9]+D)?T?([0-9]+H)?([0-9]+M)?([0-9]+S)?$\",\n \"description\": \"Gesamtzeit im ISO 8601 Format.\"\n },\n \"recipeCategory\": {\n \"type\": \"string\",\n \"description\": \"z.B. 'Vorspeise' oder 'Dessert'.\"\n },\n \"recipeCuisine\": {\n \"type\": \"string\",\n \"description\": \"z.B. 'Italienisch' oder 'Franz\u00f6sisch'.\"\n },\n \"recipeYield\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n { \"type\": \"number\" }\n ],\n \"description\": \"Menge, die das Rezept ergibt (z.B. '4 Portionen').\"\n },\n \"nutrition\": {\n \"type\": \"object\",\n \"properties\": {\n \"@type\": { \"type\": \"string\", \"const\": \"NutritionInformation\" },\n \"calories\": { \"type\": \"string\", \"description\": \"z.B. '240 calories'\" },\n \"fatContent\": { \"type\": \"string\" },\n \"proteinContent\": { \"type\": \"string\" },\n \"carbohydrateContent\": { \"type\": \"string\" }\n }\n },\n \"recipeIngredient\": {\n \"type\": \"array\",\n \"items\": { \"@type\": \"PropertyValue\", \"value\": 1, \"name\": \"egg\" },\n \"description\": \"Liste der ben\u00f6tigten Zutaten.\"\n },\n \"recipeInstructions\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n {\n \"type\": \"object\",\n \"properties\": {\n \"@type\": { \"type\": \"string\", \"const\": \"HowToStep\" },\n \"text\": { \"type\": \"string\" },\n \"name\": { \"type\": \"string\" },\n \"url\": { \"type\": \"string\", \"format\": \"uri\" },\n \"image\": { \"type\": \"string\", \"format\": \"uri\" }\n },\n \"required\": [\"text\"]\n }\n ]\n },\n \"description\": \"Schritt-f\u00fcr-Schritt-Anleitung.\"\n },\n \"aggregateRating\": {\n \"type\": \"object\",\n \"properties\": {\n \"@type\": { \"type\": \"string\", \"const\": \"AggregateRating\" },\n \"ratingValue\": { \"type\": \"number\" },\n \"reviewCount\": { \"type\": \"number\" }\n }\n }\n }\n}", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.informationExtractor", + "typeVersion": 1.2, + "position": [ + 688, + 0 + ], + "id": "5f9d6b78-9430-4008-85bb-7633568e6311", + "name": "Information Extractor" + }, + { + "parameters": { + "html": "={{ $json.data }}", + "options": { + "useLinkReferenceDefinitions": true + } + }, + "type": "n8n-nodes-base.markdown", + "typeVersion": 1, + "position": [ + 432, + 0 + ], + "id": "b363c14e-2ee8-4608-a8ff-e567583e8097", + "name": "Markdown" + }, + { + "parameters": { + "model": { + "__rl": true, + "value": "SAIA/llama-3.1-sauerkrautlm-70b-instruct", + "mode": "list", + "cachedResultName": "SAIA/llama-3.1-sauerkrautlm-70b-instruct" + }, + "responsesApiEnabled": false, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 688, + 192 + ], + "id": "c7012e63-c468-45d4-bcb5-ca1b1d9e2180", + "name": "OpenAI Chat Model", + "credentials": { + "openAiApi": { + "id": "5sHTE0Wl8Qi6twzX", + "name": "nuclide.systems" + } + } + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Fetch HTML", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fetch HTML": { + "main": [ + [ + { + "node": "Markdown", + "type": "main", + "index": 0 + } + ] + ] + }, + "Information Extractor": { + "main": [ + [ + { + "node": "Post to Cookbook", + "type": "main", + "index": 0 + } + ] + ] + }, + "Markdown": { + "main": [ + [ + { + "node": "Information Extractor", + "type": "main", + "index": 0 + } + ] + ] + }, + "OpenAI Chat Model": { + "ai_languageModel": [ + [ + { + "node": "Information Extractor", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false, + "timeSavedMode": "fixed", + "timezone": "Europe/Berlin", + "callerPolicy": "workflowsFromSameOwner", + "saveDataSuccessExecution": "none", + "redactionPolicy": "none" + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": {}, + "versionId": "de91ceef-da7e-4bbc-9d91-a2ea04763d6f", + "activeVersionId": "de91ceef-da7e-4bbc-9d91-a2ea04763d6f", + "versionCounter": 373, + "triggerCount": 1, + "tags": [], + "shared": [ + { + "updatedAt": "2026-03-09T07:21:50.078Z", + "createdAt": "2026-03-09T07:21:50.078Z", + "role": "workflow:owner", + "workflowId": "mXYla2g5o8kLumAJ", + "projectId": "hkIaTyp1CKVS8ywT", + "project": { + "updatedAt": "2026-03-09T07:21:36.510Z", + "createdAt": "2026-03-09T07:20:59.502Z", + "id": "hkIaTyp1CKVS8ywT", + "name": "Florian Krebs ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "8af5c813-f89c-4b83-bcf8-5d59864a038a" + } + } + ], + "versionMetadata": { + "name": "Version de91ceef", + "description": "" + } +} \ No newline at end of file diff --git a/flows/rss-archive-proxy.json b/flows/rss-archive-proxy.json new file mode 100644 index 0000000..ee191b1 --- /dev/null +++ b/flows/rss-archive-proxy.json @@ -0,0 +1,215 @@ +{ + "updatedAt": "2026-04-29T12:51:16.156Z", + "createdAt": "2026-04-29T12:29:42.948Z", + "id": "ddebbUNfpW4kE6Ns", + "name": "RSS Archive Proxy", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "transform-opml", + "responseMode": "lastNode", + "options": {} + }, + "id": "6ca1acab-6d27-488b-bf40-f5198d021743", + "name": "Webhook: Transform OPML", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 352, + 112 + ], + "webhookId": "22cc0a92-1076-479e-88ec-68eadc808f10" + }, + { + "parameters": { + "path": "news-proxy", + "responseMode": "lastNode", + "options": {} + }, + "id": "65a125d7-f8b4-4693-bbf3-1be2e7101cdd", + "name": "Webhook: RSS Proxy", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 352, + 320 + ], + "webhookId": "b5dd99aa-6986-436e-aa7e-6e7650c87f4b" + }, + { + "parameters": { + "url": "={{ $json.query.url }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + } + ] + }, + "options": {} + }, + "id": "aab36531-e74d-44f2-8dc9-2ad534c14b0e", + "name": "HTTP Request: Fetch Feed", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 608, + 320 + ] + }, + { + "parameters": { + "jsCode": "// Extrahiert den Feed-Inhalt\nlet content = items[0].json.data || items[0].text || items[0].json.body || \"\";\n\n// Ersetzt normale Links durch archive.ph Links\ncontent = content.replace(/(.*?)<\\/link>/g, (match, url) => {\n if (url.includes('archive.ph')) return match;\n return `https://archive.ph/newest/${url}`;\n});\n\n// Ersetzt GUIDs (wichtig f\u00fcr manche Reader)\ncontent = content.replace(/(.*?)<\\/guid>/g, (match, attr, url) => {\n if (url.includes('archive.ph')) return match;\n return `https://archive.ph/newest/${url}`;\n});\n\nreturn [{ json: { body: content } }];" + }, + "id": "0f8912ea-a6eb-4c31-8a99-11e82dd1bdd4", + "name": "Code: RSS Link Fixer", + "type": "n8n-nodes-base.code", + "typeVersion": 1, + "position": [ + 832, + 320 + ] + }, + { + "parameters": { + "operation": "text", + "options": {} + }, + "id": "25335b7c-6d18-4f3d-b1bb-084d9e1c1aa1", + "name": "Extract From File", + "type": "n8n-nodes-base.extractFromFile", + "typeVersion": 1, + "position": [ + 608, + 112 + ] + }, + { + "parameters": { + "jsCode": "// Wir greifen direkt auf das Feld \"data\" zu, das n8n uns geliefert hat\nlet xml = items[0].json.data;\n\nconst baseUrl = \"https://n8n.nuclide.systems/webhook/news-proxy?url=\";\n\nif (!xml) {\n throw new Error(\"Kein Text im Feld 'data' gefunden. Pr\u00fcfe die Webhook-Ausgabe.\");\n}\n\n// Der eigentliche Replace-Vorgang f\u00fcr deine Feeds (FAZ, ZEIT, etc.)\nxml = xml.replace(/xmlUrl=\"(.*?)\"/g, (match, url) => {\n // Falls es schon ein Proxy-Link ist, nichts tun\n if (url.includes('news-proxy')) return match;\n return `xmlUrl=\"${baseUrl}${encodeURIComponent(url)}\"`;\n});\n\n// Wir geben das Ergebnis im Feld \"body\" zur\u00fcck\nreturn [{ json: { body: xml } }];" + }, + "id": "18bd4563-694b-4fc2-8b32-605afe520ec8", + "name": "Code: OPML Transformer", + "type": "n8n-nodes-base.code", + "typeVersion": 1, + "position": [ + 832, + 112 + ] + } + ], + "connections": { + "Webhook: Transform OPML": { + "main": [ + [ + { + "node": "Extract From File", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract From File": { + "main": [ + [ + { + "node": "Code: OPML Transformer", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook: RSS Proxy": { + "main": [ + [ + { + "node": "HTTP Request: Fetch Feed", + "type": "main", + "index": 0 + } + ] + ] + }, + "HTTP Request: Fetch Feed": { + "main": [ + [ + { + "node": "Code: RSS Link Fixer", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate" + }, + "staticData": null, + "meta": null, + "pinData": { + "Webhook: RSS Proxy": [ + { + "json": { + "headers": { + "host": "n8n.nuclide.systems", + "user-agent": "NextCloud-News/28.2.0", + "accept": "application/rss+xml, application/rdf+xml;q=0.8, application/atom+xml;q=0.6, application/xml;q=0.4, text/xml;q=0.4, */*;q=0.2", + "accept-encoding": "gzip, deflate, br", + "x-forwarded-for": "192.168.1.1", + "x-forwarded-host": "n8n.nuclide.systems", + "x-forwarded-proto": "https", + "x-forwarded-server": "zoraxy-5280418d-4b2e-4b04-9868-bd361bb11c66", + "x-real-ip": "192.168.1.1" + }, + "params": {}, + "query": { + "url": "https://www.tagesschau.de/xml/rss2" + }, + "webhookUrl": "http://n8n.nuclide.systems:5678/webhook/news-proxy", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "2663f8cd-de8e-416e-a054-441aca12cd97", + "activeVersionId": "2663f8cd-de8e-416e-a054-441aca12cd97", + "versionCounter": 77, + "triggerCount": 2, + "tags": [], + "shared": [ + { + "updatedAt": "2026-04-29T12:29:42.950Z", + "createdAt": "2026-04-29T12:29:42.950Z", + "role": "workflow:owner", + "workflowId": "ddebbUNfpW4kE6Ns", + "projectId": "hkIaTyp1CKVS8ywT", + "project": { + "updatedAt": "2026-03-09T07:21:36.510Z", + "createdAt": "2026-03-09T07:20:59.502Z", + "id": "hkIaTyp1CKVS8ywT", + "name": "Florian Krebs ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "8af5c813-f89c-4b83-bcf8-5d59864a038a" + } + } + ], + "versionMetadata": { + "name": "Version 2663f8cd", + "description": "" + } +} \ No newline at end of file