From 05d8488e4b8d04ef132d8be5e2d200ac9a525cf2 Mon Sep 17 00:00:00 2001 From: Lucas Orth Date: Tue, 22 Sep 2026 18:33:54 +0200 Subject: [PATCH] Alle 30 Artikel, WF2 veroeffentlicht direkt und schuetzt veroeffentlichte Beitraege --- build/artikel_schreiben.py | 12 ++ build/gen_wf2.py | 55 +++-- build/pruef_lokal.py | 2 +- build/wf2_deploy.py | 19 ++ workflows/pruefen-und-importieren.json | 285 +++++++++++++++++-------- 5 files changed, 275 insertions(+), 98 deletions(-) create mode 100644 build/artikel_schreiben.py create mode 100644 build/wf2_deploy.py diff --git a/build/artikel_schreiben.py b/build/artikel_schreiben.py new file mode 100644 index 0000000..0723e3c --- /dev/null +++ b/build/artikel_schreiben.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +"""Schreibt einen Artikel aus einer Python-Datei mit dict A und prueft ihn. Aufruf: python3 build/artikel_schreiben.py datei.py""" +import json, subprocess, sys +ns = {} +exec(open(sys.argv[1], encoding='utf-8').read(), ns) +a = ns['A'] +a.setdefault('stand_datum', '2026-09-22') +pfad = f"artikel/artikel-{a['slug']}.json" +json.dump(a, open(pfad, 'w', encoding='utf-8'), ensure_ascii=False, indent=2) +print('SEO', len(a['seo_titel']), len(a['seo_beschreibung'])) +subprocess.run(['python3', 'build/pruef_lokal.py', pfad]) +subprocess.run(['python3', 'build/nw_score.py', pfad]) diff --git a/build/gen_wf2.py b/build/gen_wf2.py index 6da8d6f..c4ec3cf 100644 --- a/build/gen_wf2.py +++ b/build/gen_wf2.py @@ -71,6 +71,10 @@ KONF = node("Konfiguration", "n8n-nodes-base.set", 3.4, [-240, 190], { {"id": nid(), "name": "wp_base", "value": "https://skrift.de", "type": "string"}, {"id": nid(), "name": "score_ziel", "value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}", "type": "number"}, {"id": nid(), "name": "pause_zwischen_artikeln", "value": 5, "type": "number"}, + # Zielstatus fuer neue Beitraege und fuer Entwuerfe: publish (Vorgabe seit 22.09.2026) oder draft + {"id": nid(), "name": "ziel_status", "value": "={{ $json.body && ['draft','publish'].includes($json.body.status) ? $json.body.status : 'publish' }}", "type": "string"}, + # Veroeffentlichte Beitraege werden nie angefasst, ausser ausdruecklich per Webhook erlaubt + {"id": nid(), "name": "veroeffentlichte_ueberschreiben", "value": "={{ !!($json.body && $json.body.veroeffentlichte_ueberschreiben === true) }}", "type": "boolean"}, {"id": nid(), "name": "elementor_ausgabe", "value": False, "type": "boolean"}, {"id": nid(), "name": "bild_aktiv", "value": True, "type": "boolean"}, {"id": nid(), "name": "bild_modell", "value": "gpt-image-2", "type": "string"}, @@ -383,7 +387,30 @@ KAT_SUCHE = http("Kategorie suchen", [2660, 240], "GET", "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/categories?search={{ encodeURIComponent($('Score auswerten').first().json.artikel.kategorie) }}&per_page=100", CRED_WP, auth_typ="httpBasicAuth", error_out=False, retry=True, extra={"options": {"response": {"response": {"neverError": True, "fullResponse": True}}, "timeout": 60000}}) -link(DUP, KAT_SUCHE) + +# Schutz: Ein bereits veroeffentlichter Beitrag wird weder ueberschrieben noch auf Entwurf gesetzt. +SCHUTZ = code("Veroeffentlicht pruefen", [2440, 440], r""" +const s = $('Score auswerten').first().json; +const dup = $input.first().json; +const K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data); +const kd = K(dup); +const treffer = (dup.statusCode === 200 && Array.isArray(kd)) ? kd.find(p => p.slug === s.slug) : null; +const konf = $('Konfiguration').first().json; +const geschuetzt = !!treffer && treffer.status !== 'draft' && konf.veroeffentlichte_ueberschreiben !== true; +return [{ json: { slug: s.slug, score: s.score, geschuetzt, + post_id: treffer ? treffer.id : null, alter_status: treffer ? treffer.status : null } }]; +""") +link(DUP, SCHUTZ) +SCHUTZ_IF = iff("Schon veroeffentlicht?", [2660, 440], "={{ $json.geschuetzt }}", "boolean", "true") +link(SCHUTZ, SCHUTZ_IF) +SCHUTZ_NOTIZ = code("Uebersprungen merken", [2880, 540], """ +const v = $input.first().json; +return [{ json: { slug: v.slug, ergebnis: 'uebersprungen', score: v.score, post_id: v.post_id, + status: v.alter_status, + meldung: 'Beitrag ' + v.post_id + ' ist bereits ' + v.alter_status + ' und bleibt unveraendert.' } }]; +""") +link(SCHUTZ_IF, SCHUTZ_NOTIZ, out=0) +link(SCHUTZ_IF, KAT_SUCHE, out=1) KAT_PRUEF = code("Kategorie pruefen", [2880, 240], """ const s = $('Score auswerten').first().json; @@ -429,6 +456,7 @@ WP_VOR = code("WordPress-Body bauen", [3760, 240], r""" const s = $input.first().json; const dup = $('Beitrag vorhanden?').first().json; const a = s.artikel; +const zielStatus = $('Konfiguration').first().json.ziel_status === 'draft' ? 'draft' : 'publish'; // Duplikatspruefung: exakter Slug-Treffer entscheidet ueber anlegen oder aktualisieren. // n8n liefert den Koerper bei fullResponse je nach Version als body oder data. @@ -522,7 +550,7 @@ const body = { title: a.titel, slug: s.slug, content: a.content_html, - status: 'draft', // harte Vorgabe: nie automatisch veroeffentlichen + status: zielStatus, // publish seit 22.09.2026, per Webhook {"status":"draft"} umschaltbar categories: [s.kategorie_id], meta, }; @@ -536,6 +564,7 @@ return [{ json: { wp_url: treffer ? `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts/${treffer.id}` : `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts`, + ziel_status: zielStatus, wp_body: JSON.stringify(body), }}]; """) @@ -555,9 +584,9 @@ if (!p.id) { throw new Error('WordPress lieferte keine Beitrags-ID zurueck. Antwort: ' + JSON.stringify(p).slice(0, 400)); } -if (p.status !== 'draft') { +if (p.status !== v.ziel_status) { throw new Error('Beitrag ' + p.id + ' hat Status "' + p.status - + '" statt "draft". Der Workflow darf nie veroeffentlichen. Bitte pruefen.'); + + '" statt "' + v.ziel_status + '". Bitte Rechte des Benutzers pruefen.'); } // Gegenprobe: sind die Meta-Felder wirklich angekommen? @@ -612,7 +641,7 @@ link(CACHE, CACHE_NOTIZ) PAUSE = node("Pause Rate Limit", "n8n-nodes-base.wait", 1.1, [4860, 420], {"amount": "={{ $('Konfiguration').first().json.pause_zwischen_artikeln }}", "unit": "seconds"}, webhookId=str(uuid.uuid4())) -for q in [NACH_OK, NACH_FEHLER, FEHLER_ART, FEHLER_EVAL]: +for q in [NACH_OK, NACH_FEHLER, FEHLER_ART, FEHLER_EVAL, SCHUTZ_NOTIZ]: link(q, PAUSE) link(PAUSE, LOOP) @@ -750,6 +779,7 @@ const alle = $input.all().map(i => i.json); const importiert = alle.filter(r => r.ergebnis === 'importiert'); const nachbessern = alle.filter(r => r.ergebnis === 'nachbessern'); const fehler = alle.filter(r => r.ergebnis === 'fehler'); +const uebersprungen = alle.filter(r => r.ergebnis === 'uebersprungen'); const zeilen = importiert.map(r => ({ slug: r.slug, score: r.score, datum: new Date().toISOString(), @@ -757,11 +787,11 @@ const zeilen = importiert.map(r => ({ aktion: r.aktion, kategorie: r.kategorie, })); -return [{ json: { alle, importiert, nachbessern, fehler, +return [{ json: { alle, importiert, nachbessern, fehler, uebersprungen, neue_zeilen: zeilen, anzahl_importiert: importiert.length, anzahl_nachbessern: nachbessern.length, - anzahl_fehler: fehler.length }}]; + anzahl_fehler: fehler.length, anzahl_uebersprungen: uebersprungen.length }}]; """) link(LOOP, SAMMELN, out=0) @@ -820,13 +850,13 @@ link(ST_SCHREIB, ST_FEHLER, out=1) MAIL = code("Meldung formulieren", [2000, 80], r""" const s = $('Ergebnisse sammeln').first().json; const z = []; -z.push(`Importiert: ${s.anzahl_importiert} | Nachbessern: ${s.anzahl_nachbessern} | Fehler: ${s.anzahl_fehler}`); +z.push(`Importiert: ${s.anzahl_importiert} | Unveraendert (veroeffentlicht): ${s.anzahl_uebersprungen} | Nachbessern: ${s.anzahl_nachbessern} | Fehler: ${s.anzahl_fehler}`); z.push(''); if (s.importiert.length) { - z.push('Als Entwurf in WordPress:'); + z.push('In WordPress (Status je Beitrag):'); s.importiert.forEach(r => { - z.push(` ${r.slug} (Score ${r.score}, ${r.aktion})`); + z.push(` ${r.slug} (Score ${r.score}, ${r.aktion}, ${r.status})`); z.push(` Vorschau: ${r.vorschau_url}`); z.push(` Bearbeiten: ${r.bearbeiten_url}`); if (r.elementor) z.push(` Layout: ${r.elementor}`); @@ -867,10 +897,11 @@ if (s.fehler.length) { s.fehler.forEach(r => z.push(` ${r.slug || '?'} [${r.stufe}]: ${r.meldung}`)); z.push(''); } -z.push('Kein Beitrag wurde veroeffentlicht. Alle Importe stehen auf Entwurf.'); +if (s.uebersprungen.length) { z.push('Bereits veroeffentlicht, nicht angefasst:'); s.uebersprungen.forEach(r => z.push(` ${r.slug} (Beitrag ${r.post_id})`)); z.push(''); } +z.push('Veroeffentlichte Beitraege werden nie ueberschrieben oder auf Entwurf gesetzt.'); return [{ json: { - betreff: `Skrift Ratgeber: ${s.anzahl_importiert} Entwurf/Entwuerfe` + betreff: `Skrift Ratgeber: ${s.anzahl_importiert} importiert` + (s.anzahl_nachbessern ? `, ${s.anzahl_nachbessern} zum Nachbessern` : '') + (s.anzahl_fehler ? `, ${s.anzahl_fehler} Fehler` : ''), text: z.join('\n') }}]; diff --git a/build/pruef_lokal.py b/build/pruef_lokal.py index 88c4774..bf7c4f9 100644 --- a/build/pruef_lokal.py +++ b/build/pruef_lokal.py @@ -28,7 +28,7 @@ if re.search('[–—]', c + a['kurzantwort'] + faqt): probleme.append("Gedanken if re.search(r'\bich\b', plain, re.I): probleme.append("Ich-Form") for s in re.findall(r'\[ratgeber_link slug="([^"]+)"', c): if s not in slugs: probleme.append(f"unbekannter Slug {s}") -if a['slug'] in ('kaltakquise-per-brief',) and 'Keine Rechtsberatung: Dieser Beitrag gibt den Stand unserer Praxiserfahrung wieder' not in plain: +if a['slug'] in ('kaltakquise-per-brief', 'serienbriefe-unterschreiben', 'dsgvo-briefwerbung') and 'Keine Rechtsberatung: Dieser Beitrag gibt den Stand unserer Praxiserfahrung wieder' not in plain: probleme.append("Disclaimer fehlt") if os.path.exists(bp): b = json.load(open(bp, encoding='utf-8')) diff --git a/build/wf2_deploy.py b/build/wf2_deploy.py new file mode 100644 index 0000000..59e14a8 --- /dev/null +++ b/build/wf2_deploy.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +"""Spielt workflows/pruefen-und-importieren.json in n8n ein, Credentials und Webhook-IDs bleiben erhalten.""" +import json, os, urllib.request +w = json.load(open('workflows/pruefen-und-importieren.json', encoding='utf-8')) +wid_ = json.load(open('build/workflow-ids.json'))['pruefen-und-importieren'] +H = {'X-N8N-API-KEY': os.environ['N8N_API_KEY'], 'Content-Type': 'application/json'} +base = os.environ['N8N_BASE_URL'].rstrip('/') + '/api/v1/workflows/' + wid_ +alt = json.load(urllib.request.urlopen(urllib.request.Request(base, headers=H))) +cred = {n['name']: n.get('credentials') for n in alt['nodes'] if n.get('credentials')} +hook = {n['name']: n.get('webhookId') for n in alt['nodes'] if n.get('webhookId')} +for n in w['nodes']: + if n['name'] in cred: n['credentials'] = cred[n['name']] + if n['name'] in hook: n['webhookId'] = hook[n['name']] +erlaubt = ('executionOrder', 'saveManualExecutions', 'callerPolicy', 'errorWorkflow', 'timezone', + 'saveDataErrorExecution', 'saveDataSuccessExecution', 'saveExecutionProgress', 'executionTimeout') +body = {'name': alt['name'], 'nodes': w['nodes'], 'connections': w['connections'], + 'settings': {k: v for k, v in (alt.get('settings') or {}).items() if k in erlaubt}} +d = json.load(urllib.request.urlopen(urllib.request.Request(base, data=json.dumps(body).encode(), headers=H, method='PUT'))) +print('aktualisiert', d['id'], 'aktiv' if d['active'] else 'inaktiv', len(d['nodes']), 'Nodes') diff --git a/workflows/pruefen-und-importieren.json b/workflows/pruefen-und-importieren.json index a53be79..068314c 100644 --- a/workflows/pruefen-und-importieren.json +++ b/workflows/pruefen-und-importieren.json @@ -9,7 +9,7 @@ -460, 100 ], - "id": "be3731b7-58d4-44e6-acc5-6272dbb3b681", + "id": "8dfcd4cf-3e46-472f-9169-206da188aa5e", "name": "Manuell starten" }, { @@ -25,9 +25,9 @@ -460, 280 ], - "id": "0755c10f-e537-4a17-a15a-9e405f5947ce", + "id": "6a8c3125-ee00-4db4-a5bc-41643290c852", "name": "Webhook Start", - "webhookId": "9866920d-2335-4c01-a289-c2b9aca47d8c" + "webhookId": "e5916b78-4476-423f-86af-837692d9491b" }, { "parameters": { @@ -35,115 +35,127 @@ "assignments": { "assignments": [ { - "id": "2f0d5d8d-4f12-4a78-8fb4-f66aa4a0d01a", + "id": "636f1ae6-aa7b-48d5-be02-363cb71d0d90", "name": "nw_base", "value": "https://app.neuronwriter.com/neuron-api/0.5/writer", "type": "string" }, { - "id": "d5bc9cac-5db5-40e3-bdad-2ceca39ac584", + "id": "b4331f4c-ff38-4525-9864-30cac5f9280b", "name": "gitea_base", "value": "http://gitea:3000", "type": "string" }, { - "id": "b91fd120-5168-4712-90a9-0b4049a47cdb", + "id": "0aad1f49-e77f-4c24-bc51-b8ed77df52ec", "name": "gitea_owner", "value": "lucas.orth", "type": "string" }, { - "id": "c757cc8c-f990-4e22-ab6a-7e7fda31e720", + "id": "70b0b497-86e6-4960-8ff0-6823c60a76fe", "name": "gitea_repo", "value": "Skrift-Auto-Article", "type": "string" }, { - "id": "b0fdc07a-a7d4-4e78-8b8f-db50fb5d8a00", + "id": "f3ca9037-0560-4e1f-ac62-cce8829f2e74", "name": "branch", "value": "main", "type": "string" }, { - "id": "84922b0c-84de-4d50-8bda-f8562594ea00", + "id": "cc272b17-7954-4732-ac36-83827206eba1", "name": "artikel_ordner", "value": "artikel", "type": "string" }, { - "id": "18d46746-ae2c-44c7-aab2-918133d4a29d", + "id": "fc1fa5dd-ff86-4358-a265-2cecb1726411", "name": "brief_ordner", "value": "briefe", "type": "string" }, { - "id": "24d34020-9424-47ea-8a2a-6ff1805c9424", + "id": "93011f19-b5a0-47c0-8263-457db34c0d50", "name": "nachbessern_ordner", "value": "nachbessern", "type": "string" }, { - "id": "cc559866-a0b2-46be-b3cb-f27d2b9ff47f", + "id": "19eac279-1305-4319-bf49-a467789e4708", "name": "status_pfad", "value": "status-importe.json", "type": "string" }, { - "id": "e5a30c70-dde6-4250-af13-1d49a8e4ab76", + "id": "20fe4cd8-5578-4841-b0a4-693af0a0b839", "name": "wp_base", "value": "https://skrift.de", "type": "string" }, { - "id": "af531179-ad15-40f9-bc3e-7ef6051653ba", + "id": "81c3304a-ba48-446f-b671-168f4d0dff1b", "name": "score_ziel", "value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}", "type": "number" }, { - "id": "794423e7-cd48-448d-94c6-787f66bb6af7", + "id": "e3c1042c-1615-4c8e-b414-01d10bd8ecad", "name": "pause_zwischen_artikeln", "value": 5, "type": "number" }, { - "id": "d046a1fc-c6c2-4b19-8fc6-744ecaded14a", + "id": "93592e6b-2ec9-4e47-8652-0414e901e0f4", + "name": "ziel_status", + "value": "={{ $json.body && ['draft','publish'].includes($json.body.status) ? $json.body.status : 'publish' }}", + "type": "string" + }, + { + "id": "0519f374-c7a8-490f-8cfe-d5c5223dc363", + "name": "veroeffentlichte_ueberschreiben", + "value": "={{ !!($json.body && $json.body.veroeffentlichte_ueberschreiben === true) }}", + "type": "boolean" + }, + { + "id": "d0a2c6d2-600b-4591-b926-32ee99b5d9e4", "name": "elementor_ausgabe", "value": false, "type": "boolean" }, { - "id": "8c21a814-4f0f-4d47-afdd-d17efa005a98", + "id": "b7b839d6-cff2-418d-8c59-dbe53190336c", "name": "bild_aktiv", "value": true, "type": "boolean" }, { - "id": "9e35d60a-4922-41be-bf71-a1d63da06ab6", + "id": "d7de3a65-b360-472a-9bad-12c0c2d4ac97", "name": "bild_modell", "value": "gpt-image-2", "type": "string" }, { - "id": "1103be64-00cd-4948-9a9f-2bc65af5b6cd", + "id": "4ee8a4a0-b50d-4d24-9554-effdcdb2cf52", "name": "bild_groesse", "value": "1200x1200", "type": "string" }, { - "id": "2da63505-15cc-43dc-9430-5091c8f4cc0c", + "id": "81f02b0c-4cf2-4f54-88a9-d067f1743d60", "name": "bild_qualitaet", "value": "medium", "type": "string" }, { - "id": "459d8553-1df2-4141-b9c1-45c505ef2b77", + "id": "e64d7ce1-be63-490a-a748-41eb61633a93", "name": "faq_ueberschrift", "value": "Häufige Fragen", "type": "string" }, { - "id": "bc459271-c4a2-408b-a64f-7945f943e785", + "id": "107f2b9a-8bed-4373-9f55-1f62966352e9", "name": "benachrichtigung_an", "value": "privat@lucas-orth.de", "type": "string" @@ -158,7 +170,7 @@ -240, 190 ], - "id": "59fcd646-0bc1-4ace-b073-dd3b634fc13a", + "id": "07896cf2-5b62-431b-9dc6-b8e1aa176bd5", "name": "Konfiguration" }, { @@ -183,7 +195,7 @@ -20, 190 ], - "id": "cc17c686-84fc-4ebc-84d1-09d39dedb732", + "id": "d0343c7f-f598-4121-9733-1db8828a3491", "name": "Artikelordner lesen", "credentials": { "httpHeaderAuth": { @@ -206,7 +218,7 @@ 200, 190 ], - "id": "25ffe9be-454d-4ce6-8ded-e1cbc29ae3ca", + "id": "dc764574-996e-4c3b-8f2a-7b666e691d65", "name": "Artikeldateien filtern" }, { @@ -220,7 +232,7 @@ }, "conditions": [ { - "id": "05d539f6-ce93-45de-adf5-d2833aa28cd1", + "id": "bdc37de0-462a-4dd9-9211-1938ca859828", "leftValue": "={{ $json.leer === true }}", "rightValue": "", "operator": { @@ -241,7 +253,7 @@ 420, 190 ], - "id": "f5084550-fb1d-4a97-8dc0-e0edea4f78a2", + "id": "3eb55a51-9521-4978-8a71-68fb3a79587a", "name": "Artikel vorhanden?" }, { @@ -252,7 +264,7 @@ 640, 40 ], - "id": "302a85d0-4238-4cf8-83d6-f508850fb265", + "id": "68d0b86b-2fe1-4e82-956a-d5e1fbee8ccd", "name": "Nichts zu tun" }, { @@ -268,7 +280,7 @@ 640, 320 ], - "id": "387aa2cd-4dfc-440c-8cdc-54c6bdb0f92f", + "id": "00d03ba5-ccca-4951-9eb6-a802c5c29844", "name": "Artikel nacheinander" }, { @@ -292,7 +304,7 @@ 900, 420 ], - "id": "08d2b6c6-e75b-49ea-a303-5f299977eb68", + "id": "f67b6e20-9ce9-4077-82b7-764acfec37c4", "name": "Artikeldatei laden", "credentials": { "httpHeaderAuth": { @@ -316,7 +328,7 @@ 900, 640 ], - "id": "b8dff49e-5773-4438-b2ea-806c0728667d", + "id": "e805470f-5857-4512-b739-a0f2900c8409", "name": "Fehler Artikeldatei", "onError": "continueRegularOutput" }, @@ -342,7 +354,7 @@ 1120, 420 ], - "id": "34db62e0-4b60-4db1-94cc-4d4634e77048", + "id": "fbe56e62-b6df-40e4-b654-fab5489bf953", "name": "Brief laden", "credentials": { "httpHeaderAuth": { @@ -362,7 +374,7 @@ 1340, 420 ], - "id": "f43fa611-de36-49c4-844b-7d127fbc5585", + "id": "29e2df6f-9ffd-405e-a19d-b4c21926c894", "name": "Artikel pruefen" }, { @@ -392,7 +404,7 @@ 1560, 420 ], - "id": "8baae2de-d269-4004-8a65-b56a3df52980", + "id": "e4321b66-6deb-4e66-b32c-ca891611c8e4", "name": "Score ermitteln", "credentials": { "httpHeaderAuth": { @@ -416,7 +428,7 @@ 1560, 640 ], - "id": "2345ad56-70bf-4e9e-95e5-078f32873908", + "id": "a8a17e68-41ed-4ae6-9815-65f108f1496a", "name": "Fehler Score", "onError": "continueRegularOutput" }, @@ -431,7 +443,7 @@ 1780, 420 ], - "id": "bb5f3c85-3ce9-4ef6-9472-5c5d1041119a", + "id": "6572a555-9bfd-42d8-824a-18cd74097b8e", "name": "Score auswerten" }, { @@ -445,7 +457,7 @@ }, "conditions": [ { - "id": "d6bb630a-83c0-4ef6-b58f-b3f04c7e0420", + "id": "7bca083e-4477-43c1-b3bf-0ddfb065199c", "leftValue": "={{ $json.bestanden }}", "rightValue": "", "operator": { @@ -466,7 +478,7 @@ 2000, 420 ], - "id": "77d875da-9aa0-4e70-84e5-14f73ec2a275", + "id": "72102d42-db0c-446d-84fd-f932028349ab", "name": "Score erreicht?" }, { @@ -480,7 +492,7 @@ 2220, 640 ], - "id": "fead1ab1-34ed-476e-96b9-0fac9d7b6758", + "id": "0d0e4e26-b6f9-47dd-8be0-646859bc5963", "name": "Fehlende Begriffe ermitteln" }, { @@ -505,7 +517,7 @@ 2440, 640 ], - "id": "c8629336-a50d-4b58-9ae4-da4ebfcb22d0", + "id": "26082611-adc6-4891-9fe6-689ddde0136a", "name": "Nachbesserung vorhanden?", "credentials": { "httpHeaderAuth": { @@ -525,7 +537,7 @@ 2660, 640 ], - "id": "946d4b02-8c02-43eb-b6c9-1347691487a5", + "id": "cba1c6f3-bb0f-4fd2-9b0c-c6c64792ba5a", "name": "Nachbesserung vorbereiten" }, { @@ -555,7 +567,7 @@ 2880, 640 ], - "id": "aeff7f66-195f-4434-92d9-ec9ddea02495", + "id": "7f74cc97-71d4-4cdb-af9f-daf78d2ce6f7", "name": "Nachbesserung committen", "credentials": { "httpHeaderAuth": { @@ -579,7 +591,7 @@ 2880, 860 ], - "id": "b868dae0-af73-442f-8962-f028ee5919ae", + "id": "8bf3f1d3-b23d-43ad-a99e-3593ade4b817", "name": "Fehler Nachbesserung", "onError": "continueRegularOutput" }, @@ -594,7 +606,7 @@ 3100, 640 ], - "id": "cfb38044-2ef6-4a04-98d5-e3b100c1abe3", + "id": "bc911e6b-c477-451a-a8d5-dc50a6e21588", "name": "Nachbesserung gemerkt" }, { @@ -624,7 +636,7 @@ 2220, 240 ], - "id": "87024225-d5c5-4f6e-8de0-2dc8c8779c18", + "id": "ee9645b0-1d05-4713-a0db-908fa9c55668", "name": "Revision zu NeuronWriter", "credentials": { "httpHeaderAuth": { @@ -648,7 +660,7 @@ 2220, 60 ], - "id": "477ca965-5538-4f95-ad0a-4f303a628ddf", + "id": "baa41bfa-a7f1-49e3-875a-7b9849b0d628", "name": "Fehler Revision", "onError": "continueRegularOutput" }, @@ -674,7 +686,7 @@ 2440, 240 ], - "id": "d66ed8ec-6c8c-4341-b470-5481b84ea3b9", + "id": "a87edaed-7b56-4d09-869e-7d24cd36864d", "name": "Beitrag vorhanden?", "credentials": { "httpBasicAuth": { @@ -708,7 +720,7 @@ 2660, 240 ], - "id": "93bbd3d1-f4d2-4e3a-b725-b439c04a0743", + "id": "bf18af8e-0c19-4126-b301-0860923e0090", "name": "Kategorie suchen", "credentials": { "httpBasicAuth": { @@ -720,6 +732,69 @@ "maxTries": 3, "waitBetweenTries": 3000 }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "\nconst s = $('Score auswerten').first().json;\nconst dup = $input.first().json;\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kd = K(dup);\nconst treffer = (dup.statusCode === 200 && Array.isArray(kd)) ? kd.find(p => p.slug === s.slug) : null;\nconst konf = $('Konfiguration').first().json;\nconst geschuetzt = !!treffer && treffer.status !== 'draft' && konf.veroeffentlichte_ueberschreiben !== true;\nreturn [{ json: { slug: s.slug, score: s.score, geschuetzt,\n post_id: treffer ? treffer.id : null, alter_status: treffer ? treffer.status : null } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2440, + 440 + ], + "id": "5d8d994d-23dd-428e-9aa9-6ad8ad532f22", + "name": "Veroeffentlicht pruefen" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 2 + }, + "conditions": [ + { + "id": "e6f8c006-bd95-426a-9a80-9edb2cdacffd", + "leftValue": "={{ $json.geschuetzt }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "looseTypeValidation": true, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + 2660, + 440 + ], + "id": "3c3cc9f1-11e2-4e58-94d8-dded22d2c71f", + "name": "Schon veroeffentlicht?" + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "\nconst v = $input.first().json;\nreturn [{ json: { slug: v.slug, ergebnis: 'uebersprungen', score: v.score, post_id: v.post_id,\n status: v.alter_status,\n meldung: 'Beitrag ' + v.post_id + ' ist bereits ' + v.alter_status + ' und bleibt unveraendert.' } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2880, + 540 + ], + "id": "a2e326e8-48d0-474f-92ee-1f5462e3c501", + "name": "Uebersprungen merken" + }, { "parameters": { "mode": "runOnceForAllItems", @@ -731,7 +806,7 @@ 2880, 240 ], - "id": "784c106d-1d45-4de5-9840-8e3d331c0a11", + "id": "be0d102b-3080-4783-91d2-1939e5562d0c", "name": "Kategorie pruefen" }, { @@ -745,7 +820,7 @@ }, "conditions": [ { - "id": "ae40aca1-0466-4e7f-97b5-032b36d76c33", + "id": "45a95063-b4ce-4b80-a52d-a1c6b47dabfa", "leftValue": "={{ $json.kategorie_fehlt }}", "rightValue": "", "operator": { @@ -766,7 +841,7 @@ 3100, 240 ], - "id": "8fe4d39e-a88f-420c-a35a-27e3eb5fd3e1", + "id": "62cc195c-8da1-4102-8eb1-258ea6e2a33b", "name": "Kategorie anlegen?" }, { @@ -796,7 +871,7 @@ 3320, 140 ], - "id": "be37bc13-b8a2-4a2c-ac3e-a253af1d5e05", + "id": "7cce5de6-79b6-446b-ab43-064da6bd620a", "name": "Kategorie anlegen", "credentials": { "httpBasicAuth": { @@ -819,13 +894,13 @@ 3540, 240 ], - "id": "64489c08-1877-409d-b680-5884846fffc5", + "id": "d274121c-5951-4a67-8a58-de4068309038", "name": "Kategorie festlegen" }, { "parameters": { "mode": "runOnceForAllItems", - "jsCode": "\nconst s = $input.first().json;\nconst dup = $('Beitrag vorhanden?').first().json;\nconst a = s.artikel;\n\n// Duplikatspruefung: exakter Slug-Treffer entscheidet ueber anlegen oder aktualisieren.\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kd = K(dup);\nconst treffer = (dup.statusCode === 200 && Array.isArray(kd))\n ? kd.find(p => p.slug === s.slug) : null;\n\nconst heute = new Date().toISOString().slice(0, 10);\n\nconst meta = {\n kurzantwort: a.kurzantwort || '',\n faq_ueberschrift: a.faq_ueberschrift || '',\n cta_ziel: a.cta_ziel || '',\n stand_datum: a.stand_datum || heute,\n ratgeber_reihenfolge: Number.isFinite(Number(a.reihenfolge)) && a.reihenfolge !== null && a.reihenfolge !== '' ? Number(a.reihenfolge) : 999,\n faq: Array.isArray(a.faq)\n ? a.faq.filter(f => f && f.frage).map(f => ({ frage: String(f.frage), antwort: String(f.antwort || '') }))\n : [],\n _seopress_titles_title: a.seo_titel || a.titel,\n _seopress_titles_desc: a.seo_beschreibung || '',\n _seopress_analysis_target_kw: a.keyword || '',\n};\n\n// --- Elementor-Struktur aus Artikel und FAQ ---\n// Zerlegt den Text an den H2-Grenzen in Text-Editor-Widgets und haengt das FAQ\n// als nested-accordion an, dasselbe Widget wie auf den Leistungsseiten.\nconst konf = $('Konfiguration').first().json;\nconst eid = () => Math.random().toString(16).slice(2, 9);\nconst textWidget = (h) => ({ id: eid(), elType: 'widget', settings: { editor: h },\n elements: [], widgetType: 'text-editor' });\nconst behaelter = (kinder, innen) => {\n const s = { content_width: 'full' };\n if (innen) Object.assign(s, {\n _title: 'Element',\n padding: { unit: 'px', top: '8', right: '8', bottom: '8', left: '8', isLinked: true },\n flex_gap: { column: '20', row: '20', isLinked: true, unit: 'px', size: 20 },\n });\n const c = { id: eid(), elType: 'container', settings: s, elements: kinder };\n if (innen) { c.isInner = true; c.isLocked = true; }\n return c;\n};\n\nfunction elementorBauen() {\n const wurzel = [];\n if (a.kurzantwort) {\n wurzel.push(behaelter([textWidget(\n `

${a.kurzantwort}

`)]));\n }\n const html = String(a.content_html || '');\n const stellen = [];\n const re2 = /]/g;\n let mm;\n while ((mm = re2.exec(html)) !== null) stellen.push(mm.index);\n const bloecke = [];\n if (!stellen.length) {\n if (html.trim()) bloecke.push(html.trim());\n } else {\n if (stellen[0] > 0 && html.slice(0, stellen[0]).trim()) bloecke.push(html.slice(0, stellen[0]).trim());\n stellen.forEach((s, i) => {\n const ende = i + 1 < stellen.length ? stellen[i + 1] : html.length;\n const b = html.slice(s, ende).trim();\n if (b) bloecke.push(b);\n });\n }\n bloecke.forEach(b => wurzel.push(behaelter([textWidget(b)])));\n\n if (meta.faq.length) {\n const items = [], kinder = [];\n meta.faq.forEach(f => {\n items.push({ item_title: f.frage, _id: eid() });\n kinder.push(behaelter([textWidget(`

${f.antwort || ''}

`)], true));\n });\n wurzel.push(behaelter([\n textWidget(`

${konf.faq_ueberschrift || 'Häufige Fragen'}

`),\n { id: eid(), elType: 'widget', widgetType: 'nested-accordion',\n settings: { items, faq_schema: 'yes', default_state: 'all_collapsed' },\n elements: kinder },\n ]));\n }\n return wurzel;\n}\n\nif (konf.elementor_ausgabe) {\n meta._elementor_data = JSON.stringify(elementorBauen());\n meta._elementor_edit_mode = 'builder';\n meta._elementor_template_type = 'wp-post';\n}\n\nconst body = {\n title: a.titel,\n slug: s.slug,\n content: a.content_html,\n status: 'draft', // harte Vorgabe: nie automatisch veroeffentlichen\n categories: [s.kategorie_id],\n meta,\n};\nif (a.auszug) body.excerpt = a.auszug;\n\nreturn [{ json: {\n ...s,\n vorhanden: !!treffer,\n post_id: treffer ? treffer.id : null,\n alter_status: treffer ? treffer.status : null,\n wp_url: treffer\n ? `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts/${treffer.id}`\n : `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts`,\n wp_body: JSON.stringify(body),\n}}];\n" + "jsCode": "\nconst s = $input.first().json;\nconst dup = $('Beitrag vorhanden?').first().json;\nconst a = s.artikel;\nconst zielStatus = $('Konfiguration').first().json.ziel_status === 'draft' ? 'draft' : 'publish';\n\n// Duplikatspruefung: exakter Slug-Treffer entscheidet ueber anlegen oder aktualisieren.\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kd = K(dup);\nconst treffer = (dup.statusCode === 200 && Array.isArray(kd))\n ? kd.find(p => p.slug === s.slug) : null;\n\nconst heute = new Date().toISOString().slice(0, 10);\n\nconst meta = {\n kurzantwort: a.kurzantwort || '',\n faq_ueberschrift: a.faq_ueberschrift || '',\n cta_ziel: a.cta_ziel || '',\n stand_datum: a.stand_datum || heute,\n ratgeber_reihenfolge: Number.isFinite(Number(a.reihenfolge)) && a.reihenfolge !== null && a.reihenfolge !== '' ? Number(a.reihenfolge) : 999,\n faq: Array.isArray(a.faq)\n ? a.faq.filter(f => f && f.frage).map(f => ({ frage: String(f.frage), antwort: String(f.antwort || '') }))\n : [],\n _seopress_titles_title: a.seo_titel || a.titel,\n _seopress_titles_desc: a.seo_beschreibung || '',\n _seopress_analysis_target_kw: a.keyword || '',\n};\n\n// --- Elementor-Struktur aus Artikel und FAQ ---\n// Zerlegt den Text an den H2-Grenzen in Text-Editor-Widgets und haengt das FAQ\n// als nested-accordion an, dasselbe Widget wie auf den Leistungsseiten.\nconst konf = $('Konfiguration').first().json;\nconst eid = () => Math.random().toString(16).slice(2, 9);\nconst textWidget = (h) => ({ id: eid(), elType: 'widget', settings: { editor: h },\n elements: [], widgetType: 'text-editor' });\nconst behaelter = (kinder, innen) => {\n const s = { content_width: 'full' };\n if (innen) Object.assign(s, {\n _title: 'Element',\n padding: { unit: 'px', top: '8', right: '8', bottom: '8', left: '8', isLinked: true },\n flex_gap: { column: '20', row: '20', isLinked: true, unit: 'px', size: 20 },\n });\n const c = { id: eid(), elType: 'container', settings: s, elements: kinder };\n if (innen) { c.isInner = true; c.isLocked = true; }\n return c;\n};\n\nfunction elementorBauen() {\n const wurzel = [];\n if (a.kurzantwort) {\n wurzel.push(behaelter([textWidget(\n `

${a.kurzantwort}

`)]));\n }\n const html = String(a.content_html || '');\n const stellen = [];\n const re2 = /]/g;\n let mm;\n while ((mm = re2.exec(html)) !== null) stellen.push(mm.index);\n const bloecke = [];\n if (!stellen.length) {\n if (html.trim()) bloecke.push(html.trim());\n } else {\n if (stellen[0] > 0 && html.slice(0, stellen[0]).trim()) bloecke.push(html.slice(0, stellen[0]).trim());\n stellen.forEach((s, i) => {\n const ende = i + 1 < stellen.length ? stellen[i + 1] : html.length;\n const b = html.slice(s, ende).trim();\n if (b) bloecke.push(b);\n });\n }\n bloecke.forEach(b => wurzel.push(behaelter([textWidget(b)])));\n\n if (meta.faq.length) {\n const items = [], kinder = [];\n meta.faq.forEach(f => {\n items.push({ item_title: f.frage, _id: eid() });\n kinder.push(behaelter([textWidget(`

${f.antwort || ''}

`)], true));\n });\n wurzel.push(behaelter([\n textWidget(`

${konf.faq_ueberschrift || 'Häufige Fragen'}

`),\n { id: eid(), elType: 'widget', widgetType: 'nested-accordion',\n settings: { items, faq_schema: 'yes', default_state: 'all_collapsed' },\n elements: kinder },\n ]));\n }\n return wurzel;\n}\n\nif (konf.elementor_ausgabe) {\n meta._elementor_data = JSON.stringify(elementorBauen());\n meta._elementor_edit_mode = 'builder';\n meta._elementor_template_type = 'wp-post';\n}\n\nconst body = {\n title: a.titel,\n slug: s.slug,\n content: a.content_html,\n status: zielStatus, // publish seit 22.09.2026, per Webhook {\"status\":\"draft\"} umschaltbar\n categories: [s.kategorie_id],\n meta,\n};\nif (a.auszug) body.excerpt = a.auszug;\n\nreturn [{ json: {\n ...s,\n vorhanden: !!treffer,\n post_id: treffer ? treffer.id : null,\n alter_status: treffer ? treffer.status : null,\n wp_url: treffer\n ? `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts/${treffer.id}`\n : `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts`,\n ziel_status: zielStatus,\n wp_body: JSON.stringify(body),\n}}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -833,7 +908,7 @@ 3760, 240 ], - "id": "5e619ffa-f0b4-4802-b01a-9c9cc0f30ef6", + "id": "752dea66-34cd-4cc3-b254-4e328af3b800", "name": "WordPress-Body bauen" }, { @@ -863,7 +938,7 @@ 3980, 240 ], - "id": "f2896d54-c081-4f82-b4c4-fa23a9a3c0fa", + "id": "f7ca5a4a-facd-42df-b24b-56825aa1e0ad", "name": "Beitrag anlegen oder aktualisieren", "credentials": { "httpBasicAuth": { @@ -878,7 +953,7 @@ { "parameters": { "mode": "runOnceForAllItems", - "jsCode": "\nconst v = $('WordPress-Body bauen').first().json;\nconst p = $input.first().json;\n\nif (!p.id) {\n throw new Error('WordPress lieferte keine Beitrags-ID zurueck. Antwort: '\n + JSON.stringify(p).slice(0, 400));\n}\nif (p.status !== 'draft') {\n throw new Error('Beitrag ' + p.id + ' hat Status \"' + p.status\n + '\" statt \"draft\". Der Workflow darf nie veroeffentlichen. Bitte pruefen.');\n}\n\n// Gegenprobe: sind die Meta-Felder wirklich angekommen?\nconst m = p.meta || {};\nconst fehlend = ['kurzantwort', 'cta_ziel', 'stand_datum', 'faq']\n .filter(k => m[k] === undefined);\nconst faq_ok = Array.isArray(m.faq) ? m.faq.length : -1;\n\nconst konf = $('Konfiguration').first().json;\nreturn [{ json: {\n slug: v.slug, ergebnis: 'importiert', score: v.score, score_ziel: v.score_ziel,\n post_id: p.id, featured_media: p.featured_media || 0, aktion: v.vorhanden ? 'aktualisiert' : 'neu angelegt',\n alter_status: v.alter_status, status: p.status,\n kategorie: v.kategorie_name, kategorie_id: v.kategorie_id,\n vorschau_url: `${konf.wp_base}/?p=${p.id}&preview=true`,\n bearbeiten_url: `${konf.wp_base}/wp-admin/post.php?post=${p.id}&action=edit`,\n meta_warnung: fehlend.length\n ? 'Diese Meta-Felder kamen nicht zurueck: ' + fehlend.join(', ')\n + '. Mu-Plugin skrift-ratgeber-felder.php installiert?'\n : null,\n faq_eintraege: faq_ok,\n elementor: (p.meta || {})._elementor_edit_mode === 'builder'\n ? (() => { try { return JSON.parse((p.meta || {})._elementor_data || '[]').length + ' Container'; }\n catch (e) { return 'ungueltig'; } })()\n : 'klassischer Inhalt',\n seo_titel: (p.meta || {})._seopress_titles_title || null,\n preis_warnung: v.preis_warnung || null,\n stil_hinweise: v.stil_hinweise || [],\n preis_shortcodes: v.preis_shortcodes || 0,\n}}];\n" + "jsCode": "\nconst v = $('WordPress-Body bauen').first().json;\nconst p = $input.first().json;\n\nif (!p.id) {\n throw new Error('WordPress lieferte keine Beitrags-ID zurueck. Antwort: '\n + JSON.stringify(p).slice(0, 400));\n}\nif (p.status !== v.ziel_status) {\n throw new Error('Beitrag ' + p.id + ' hat Status \"' + p.status\n + '\" statt \"' + v.ziel_status + '\". Bitte Rechte des Benutzers pruefen.');\n}\n\n// Gegenprobe: sind die Meta-Felder wirklich angekommen?\nconst m = p.meta || {};\nconst fehlend = ['kurzantwort', 'cta_ziel', 'stand_datum', 'faq']\n .filter(k => m[k] === undefined);\nconst faq_ok = Array.isArray(m.faq) ? m.faq.length : -1;\n\nconst konf = $('Konfiguration').first().json;\nreturn [{ json: {\n slug: v.slug, ergebnis: 'importiert', score: v.score, score_ziel: v.score_ziel,\n post_id: p.id, featured_media: p.featured_media || 0, aktion: v.vorhanden ? 'aktualisiert' : 'neu angelegt',\n alter_status: v.alter_status, status: p.status,\n kategorie: v.kategorie_name, kategorie_id: v.kategorie_id,\n vorschau_url: `${konf.wp_base}/?p=${p.id}&preview=true`,\n bearbeiten_url: `${konf.wp_base}/wp-admin/post.php?post=${p.id}&action=edit`,\n meta_warnung: fehlend.length\n ? 'Diese Meta-Felder kamen nicht zurueck: ' + fehlend.join(', ')\n + '. Mu-Plugin skrift-ratgeber-felder.php installiert?'\n : null,\n faq_eintraege: faq_ok,\n elementor: (p.meta || {})._elementor_edit_mode === 'builder'\n ? (() => { try { return JSON.parse((p.meta || {})._elementor_data || '[]').length + ' Container'; }\n catch (e) { return 'ungueltig'; } })()\n : 'klassischer Inhalt',\n seo_titel: (p.meta || {})._seopress_titles_title || null,\n preis_warnung: v.preis_warnung || null,\n stil_hinweise: v.stil_hinweise || [],\n preis_shortcodes: v.preis_shortcodes || 0,\n}}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -886,7 +961,7 @@ 4200, 240 ], - "id": "acab056e-56c5-4d45-adac-9ba7821cf345", + "id": "85441b34-da15-4fb7-aeb8-08bad8a92101", "name": "Ergebnis pruefen" }, { @@ -917,7 +992,7 @@ 4420, 240 ], - "id": "8fb3fedb-4f3e-46d8-a5e9-d7f0fa703a3a", + "id": "35a7ea11-84be-457f-adcb-1106d4247ce8", "name": "Cache leeren", "credentials": { "httpBasicAuth": { @@ -937,7 +1012,7 @@ 4640, 240 ], - "id": "432fbfc3-c81c-4974-94f6-1ebe160d37e1", + "id": "a3f0b62e-5026-4282-a76d-7519ec823595", "name": "Importergebnis merken" }, { @@ -951,9 +1026,9 @@ 4860, 420 ], - "id": "df9453e6-48aa-4a1c-8dc4-842593e4f330", + "id": "eaaf120f-3d38-4995-8910-b8b8aeae0673", "name": "Pause Rate Limit", - "webhookId": "c29d13d4-053b-4e05-bc34-a62ab21808ba" + "webhookId": "e9c48827-ed34-4c7e-849a-93d38a140200" }, { "parameters": { @@ -966,7 +1041,7 @@ }, "conditions": [ { - "id": "095329b9-6759-4e7e-a325-e012f3ec8825", + "id": "44cd17f3-4d59-428b-b9b5-b236a3036936", "leftValue": "={{ $('Konfiguration').first().json.bild_aktiv === true && !Number($json.featured_media) }}", "rightValue": "", "operator": { @@ -987,7 +1062,7 @@ 4860, 240 ], - "id": "65dbdf3c-cf02-4915-a03a-3f0fc0543356", + "id": "480242e0-dd3e-432f-9b3b-740130606462", "name": "Beitragsbild fehlt?" }, { @@ -1012,7 +1087,7 @@ 5080, 120 ], - "id": "9d88551b-363e-4f2d-8ba2-ce5c7c702c8f", + "id": "9d8eccf2-e68d-413d-a43a-0007972bc4c2", "name": "Bilddaten laden", "credentials": { "httpHeaderAuth": { @@ -1035,7 +1110,7 @@ 5300, 120 ], - "id": "835c8714-a9bf-4036-8f08-2830f0b29d94", + "id": "8e06e435-be8c-42a9-9722-81a26f730c45", "name": "Bildprompt bauen" }, { @@ -1049,7 +1124,7 @@ }, "conditions": [ { - "id": "c703bc15-3ff7-4b0f-9b6e-9141611aa975", + "id": "093720bb-cf26-4384-b669-100b25fef06a", "leftValue": "={{ !!$json.bild_prompt }}", "rightValue": "", "operator": { @@ -1070,7 +1145,7 @@ 5520, 120 ], - "id": "f1ab4c18-b561-4581-9b68-d8d3c9701651", + "id": "d563ef3e-3729-41b5-aa8d-9539caca1737", "name": "Bildprompt vorhanden?" }, { @@ -1100,7 +1175,7 @@ 5740, 40 ], - "id": "c406aa61-49c9-4561-857a-1c05ab21ed87", + "id": "f7fa3132-2ebb-462a-acd8-4df58fc1da88", "name": "Bild erzeugen", "credentials": { "httpHeaderAuth": { @@ -1124,7 +1199,7 @@ 5740, 260 ], - "id": "87181190-d3f6-4fe6-b927-0817afdea28f", + "id": "ec9f9400-f802-4840-b7eb-425a7f2dfb10", "name": "Fehler Bild", "onError": "continueRegularOutput" }, @@ -1139,7 +1214,7 @@ 5960, 40 ], - "id": "8bf6e26d-cd73-47df-b97a-128aba60626d", + "id": "33f45eb5-7890-436e-af87-719e24692cae", "name": "Bild als Datei" }, { @@ -1174,7 +1249,7 @@ 6180, 40 ], - "id": "7569c6db-d2e5-457e-b0aa-11c8d51356f3", + "id": "c138692d-e55d-4337-88a0-224930ead516", "name": "Bild in Mediathek", "credentials": { "httpBasicAuth": { @@ -1208,7 +1283,7 @@ 6400, 40 ], - "id": "7a9b4dd8-11b9-42fd-8852-17cf09d5e121", + "id": "14a349ee-286b-4ee0-a85e-728172c22600", "name": "Beitragsbild setzen", "credentials": { "httpBasicAuth": { @@ -1231,13 +1306,13 @@ 6620, 40 ], - "id": "99577ca6-750d-4afa-accc-f85eb3fde9dc", + "id": "6067671a-a0bb-4830-be6d-f1944fd63b1f", "name": "Bild gemerkt" }, { "parameters": { "mode": "runOnceForAllItems", - "jsCode": "\nconst alle = $input.all().map(i => i.json);\nconst importiert = alle.filter(r => r.ergebnis === 'importiert');\nconst nachbessern = alle.filter(r => r.ergebnis === 'nachbessern');\nconst fehler = alle.filter(r => r.ergebnis === 'fehler');\n\nconst zeilen = importiert.map(r => ({\n slug: r.slug, score: r.score, datum: new Date().toISOString(),\n post_id: r.post_id, vorschau_url: r.vorschau_url,\n aktion: r.aktion, kategorie: r.kategorie,\n}));\n\nreturn [{ json: { alle, importiert, nachbessern, fehler,\n neue_zeilen: zeilen,\n anzahl_importiert: importiert.length,\n anzahl_nachbessern: nachbessern.length,\n anzahl_fehler: fehler.length }}];\n" + "jsCode": "\nconst alle = $input.all().map(i => i.json);\nconst importiert = alle.filter(r => r.ergebnis === 'importiert');\nconst nachbessern = alle.filter(r => r.ergebnis === 'nachbessern');\nconst fehler = alle.filter(r => r.ergebnis === 'fehler');\nconst uebersprungen = alle.filter(r => r.ergebnis === 'uebersprungen');\n\nconst zeilen = importiert.map(r => ({\n slug: r.slug, score: r.score, datum: new Date().toISOString(),\n post_id: r.post_id, vorschau_url: r.vorschau_url,\n aktion: r.aktion, kategorie: r.kategorie,\n}));\n\nreturn [{ json: { alle, importiert, nachbessern, fehler, uebersprungen,\n neue_zeilen: zeilen,\n anzahl_importiert: importiert.length,\n anzahl_nachbessern: nachbessern.length,\n anzahl_fehler: fehler.length, anzahl_uebersprungen: uebersprungen.length }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -1245,7 +1320,7 @@ 900, 80 ], - "id": "19abdb52-8b48-4363-b8d8-0b651f6a088b", + "id": "e4bb24ce-79b9-4c90-ab17-934dbbb7dc92", "name": "Ergebnisse sammeln" }, { @@ -1270,7 +1345,7 @@ 1120, 80 ], - "id": "288fd94a-d9e4-49aa-a672-726d0dd6be20", + "id": "a94e0f0d-84a9-444a-9759-037372260f0a", "name": "Statustabelle lesen", "credentials": { "httpHeaderAuth": { @@ -1290,7 +1365,7 @@ 1340, 80 ], - "id": "8c6ef018-eb58-4999-bfa0-b3375d23bb95", + "id": "c7fd918f-fb2a-4f5a-a4b0-9a7a21df75a5", "name": "Statustabelle fortschreiben" }, { @@ -1304,7 +1379,7 @@ }, "conditions": [ { - "id": "d80c40a3-8b68-4c25-96ba-ec948933abec", + "id": "dc0fdf87-8d09-4c77-a05a-82a371c72cf4", "leftValue": "={{ $json.ueberspringen }}", "rightValue": "", "operator": { @@ -1325,7 +1400,7 @@ 1560, 80 ], - "id": "6be07c39-1d90-4d70-a44c-449e3e6fc7b9", + "id": "b1aba2d8-f65b-4d17-bbf1-528f6a0eba08", "name": "Gab es Importe?" }, { @@ -1355,7 +1430,7 @@ 1780, 160 ], - "id": "e7dcf291-a4ea-4dca-b213-839c7d3abf17", + "id": "d2d26bf0-8b77-48c3-8dfa-05c424a7b272", "name": "Statustabelle schreiben", "credentials": { "httpHeaderAuth": { @@ -1379,14 +1454,14 @@ 1780, 340 ], - "id": "8677d1e3-66eb-4559-b330-f0eff8a23601", + "id": "bd2d01e0-1303-4d7a-8d41-90b30b3d5a47", "name": "Fehler Statustabelle", "onError": "continueRegularOutput" }, { "parameters": { "mode": "runOnceForAllItems", - "jsCode": "\nconst s = $('Ergebnisse sammeln').first().json;\nconst z = [];\nz.push(`Importiert: ${s.anzahl_importiert} | Nachbessern: ${s.anzahl_nachbessern} | Fehler: ${s.anzahl_fehler}`);\nz.push('');\n\nif (s.importiert.length) {\n z.push('Als Entwurf in WordPress:');\n s.importiert.forEach(r => {\n z.push(` ${r.slug} (Score ${r.score}, ${r.aktion})`);\n z.push(` Vorschau: ${r.vorschau_url}`);\n z.push(` Bearbeiten: ${r.bearbeiten_url}`);\n if (r.elementor) z.push(` Layout: ${r.elementor}`);\n if (r.bild === 'neu erzeugt') z.push(` Bild: neu erzeugt (${r.bild_kb} KB), ${r.bild_url}`);\n else if (r.featured_media) z.push(` Bild: bereits vorhanden, nicht neu erzeugt`);\n if (r.bild_hinweis) z.push(` Bild: ${r.bild_hinweis}`);\n if (r.meta_warnung) z.push(` ACHTUNG: ${r.meta_warnung}`);\n if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`);\n if (r.stil_hinweise && r.stil_hinweise.length) {\n z.push(` STIL: ${r.stil_hinweise.length} Hinweis(e)`);\n r.stil_hinweise.slice(0, 6).forEach(h => z.push(` - ${h.art}: ${h.stelle}`));\n if (r.stil_hinweise.length > 6) z.push(` ... weitere im Nachbesserungsbericht bzw. in der Execution`);\n }\n if (r.cache_hinweis) z.push(` Hinweis: ${r.cache_hinweis}`);\n });\n z.push('');\n}\nif (s.nachbessern.length) {\n z.push('Score zu niedrig, Nachbesserung liegt im Repo:');\n s.nachbessern.forEach(r => {\n z.push(` ${r.slug}: Score ${r.score} von ${r.score_ziel}`);\n z.push(` ${r.anzahl_pflicht} Pflichtbegriffe fehlen, ${r.anzahl_erweitert} Zusatzbegriffe`);\n if (r.wortzahl && r.wortzahl.differenz !== null) {\n z.push(` Wortzahl ${r.wortzahl.ist} von ${r.wortzahl.ziel} (${r.wortzahl.differenz > 0 ? '+' : ''}${r.wortzahl.differenz})`);\n }\n z.push(` Datei: ${r.pfad}`);\n if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`);\n if (r.stil_hinweise && r.stil_hinweise.length) {\n z.push(` STIL: ${r.stil_hinweise.length} Hinweis(e)`);\n r.stil_hinweise.slice(0, 6).forEach(h => z.push(` - ${h.art}: ${h.stelle}`));\n if (r.stil_hinweise.length > 6) z.push(` ... weitere im Nachbesserungsbericht bzw. in der Execution`);\n }\n });\n z.push('');\n}\nif (s.fehler.length) {\n z.push('Fehler:');\n s.fehler.forEach(r => z.push(` ${r.slug || '?'} [${r.stufe}]: ${r.meldung}`));\n z.push('');\n}\nz.push('Kein Beitrag wurde veroeffentlicht. Alle Importe stehen auf Entwurf.');\n\nreturn [{ json: {\n betreff: `Skrift Ratgeber: ${s.anzahl_importiert} Entwurf/Entwuerfe`\n + (s.anzahl_nachbessern ? `, ${s.anzahl_nachbessern} zum Nachbessern` : '')\n + (s.anzahl_fehler ? `, ${s.anzahl_fehler} Fehler` : ''),\n text: z.join('\\n') }}];\n" + "jsCode": "\nconst s = $('Ergebnisse sammeln').first().json;\nconst z = [];\nz.push(`Importiert: ${s.anzahl_importiert} | Unveraendert (veroeffentlicht): ${s.anzahl_uebersprungen} | Nachbessern: ${s.anzahl_nachbessern} | Fehler: ${s.anzahl_fehler}`);\nz.push('');\n\nif (s.importiert.length) {\n z.push('In WordPress (Status je Beitrag):');\n s.importiert.forEach(r => {\n z.push(` ${r.slug} (Score ${r.score}, ${r.aktion}, ${r.status})`);\n z.push(` Vorschau: ${r.vorschau_url}`);\n z.push(` Bearbeiten: ${r.bearbeiten_url}`);\n if (r.elementor) z.push(` Layout: ${r.elementor}`);\n if (r.bild === 'neu erzeugt') z.push(` Bild: neu erzeugt (${r.bild_kb} KB), ${r.bild_url}`);\n else if (r.featured_media) z.push(` Bild: bereits vorhanden, nicht neu erzeugt`);\n if (r.bild_hinweis) z.push(` Bild: ${r.bild_hinweis}`);\n if (r.meta_warnung) z.push(` ACHTUNG: ${r.meta_warnung}`);\n if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`);\n if (r.stil_hinweise && r.stil_hinweise.length) {\n z.push(` STIL: ${r.stil_hinweise.length} Hinweis(e)`);\n r.stil_hinweise.slice(0, 6).forEach(h => z.push(` - ${h.art}: ${h.stelle}`));\n if (r.stil_hinweise.length > 6) z.push(` ... weitere im Nachbesserungsbericht bzw. in der Execution`);\n }\n if (r.cache_hinweis) z.push(` Hinweis: ${r.cache_hinweis}`);\n });\n z.push('');\n}\nif (s.nachbessern.length) {\n z.push('Score zu niedrig, Nachbesserung liegt im Repo:');\n s.nachbessern.forEach(r => {\n z.push(` ${r.slug}: Score ${r.score} von ${r.score_ziel}`);\n z.push(` ${r.anzahl_pflicht} Pflichtbegriffe fehlen, ${r.anzahl_erweitert} Zusatzbegriffe`);\n if (r.wortzahl && r.wortzahl.differenz !== null) {\n z.push(` Wortzahl ${r.wortzahl.ist} von ${r.wortzahl.ziel} (${r.wortzahl.differenz > 0 ? '+' : ''}${r.wortzahl.differenz})`);\n }\n z.push(` Datei: ${r.pfad}`);\n if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`);\n if (r.stil_hinweise && r.stil_hinweise.length) {\n z.push(` STIL: ${r.stil_hinweise.length} Hinweis(e)`);\n r.stil_hinweise.slice(0, 6).forEach(h => z.push(` - ${h.art}: ${h.stelle}`));\n if (r.stil_hinweise.length > 6) z.push(` ... weitere im Nachbesserungsbericht bzw. in der Execution`);\n }\n });\n z.push('');\n}\nif (s.fehler.length) {\n z.push('Fehler:');\n s.fehler.forEach(r => z.push(` ${r.slug || '?'} [${r.stufe}]: ${r.meldung}`));\n z.push('');\n}\nif (s.uebersprungen.length) { z.push('Bereits veroeffentlicht, nicht angefasst:'); s.uebersprungen.forEach(r => z.push(` ${r.slug} (Beitrag ${r.post_id})`)); z.push(''); }\nz.push('Veroeffentlichte Beitraege werden nie ueberschrieben oder auf Entwurf gesetzt.');\n\nreturn [{ json: {\n betreff: `Skrift Ratgeber: ${s.anzahl_importiert} importiert`\n + (s.anzahl_nachbessern ? `, ${s.anzahl_nachbessern} zum Nachbessern` : '')\n + (s.anzahl_fehler ? `, ${s.anzahl_fehler} Fehler` : ''),\n text: z.join('\\n') }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -1394,7 +1469,7 @@ 2000, 80 ], - "id": "ea4f12d3-9039-4892-94a8-035d911aff47", + "id": "e7a6d1d8-6e87-4002-844e-e09bc9f0a76d", "name": "Meldung formulieren" }, { @@ -1412,7 +1487,7 @@ 2220, 80 ], - "id": "a895dc43-e8cf-4f82-af63-da713ae5a603", + "id": "ef7daca9-b2ac-492e-abc8-593c814b5866", "name": "Benachrichtigung", "disabled": true, "notes": "SMTP-Credential zuweisen, Absender eintragen, dann aktivieren." @@ -1679,6 +1754,35 @@ }, "Beitrag vorhanden?": { "main": [ + [ + { + "node": "Veroeffentlicht pruefen", + "type": "main", + "index": 0 + } + ] + ] + }, + "Veroeffentlicht pruefen": { + "main": [ + [ + { + "node": "Schon veroeffentlicht?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Schon veroeffentlicht?": { + "main": [ + [ + { + "node": "Uebersprungen merken", + "type": "main", + "index": 0 + } + ], [ { "node": "Kategorie suchen", @@ -1838,6 +1942,17 @@ ] ] }, + "Uebersprungen merken": { + "main": [ + [ + { + "node": "Pause Rate Limit", + "type": "main", + "index": 0 + } + ] + ] + }, "Pause Rate Limit": { "main": [ [