diff --git a/build/briefe_holen.py b/build/briefe_holen.py new file mode 100644 index 0000000..3dd4ed4 --- /dev/null +++ b/build/briefe_holen.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +"""Holt briefe/brief-SLUG.json aus Gitea nach build/lokal/briefe/SLUG.json. Aufruf: python3 build/briefe_holen.py SLUG [...]""" +import base64, json, os, sys +exec(open(os.path.join(os.path.dirname(__file__), 'push_gitea.py'), encoding='utf-8').read().split('def sammle')[0]) +os.makedirs('build/lokal/briefe', exist_ok=True) +for s in sys.argv[1:]: + code, d = ruf('GET', f"{REPO}/contents/briefe/brief-{s}.json?ref=main") + if code != 200: + print('fehlt', s); continue + b = json.loads(base64.b64decode(d['content']).decode()) + json.dump(b, open(f'build/lokal/briefe/{s}.json', 'w', encoding='utf-8'), ensure_ascii=False, indent=2) + z = b.get('zusatz', {}) + print(f"== {s} | {b['query_id']} | Ziel {b['wortzahl_ziel']}") + print(' Pflicht:', ' | '.join(f"{x['begriff']}:{x['min']}-{x['max']}" for x in b['begriffe_pflicht'])) + print(' Titel:', ', '.join(z.get('begriffe_titel', []))) + print(' Desc:', ', '.join(z.get('begriffe_description', []))) + print(' H2:', ', '.join(z.get('begriffe_h2', [])[:25])) + erw = sorted(b['begriffe_erweitert'], key=lambda x: -(x['bei_wettbewerbern_pc'] or 0)) + print(' Erweitert:', ', '.join(x['begriff'] for x in erw[:45])) + print(' Fragen:', ' / '.join(f['frage'] for f in b['fragen'][:8])) diff --git a/build/gen_wf2.py b/build/gen_wf2.py index 179c116..6da8d6f 100644 --- a/build/gen_wf2.py +++ b/build/gen_wf2.py @@ -179,12 +179,13 @@ while ((m = re.exec(String(artikel.content_html || ''))) !== null) { } const shortcodes = (String(artikel.content_html || '').match(/\[skrift_preis[^\]]*\]/g) || []); -// NeuronWriter bewertet nur den uebergebenen Text. Kurzantwort und FAQ stehen +// NeuronWriter bewertet nur den uebergebenen Text. Titel (H1), Kurzantwort und FAQ stehen // aber auf der fertigen Seite, also gehoeren sie in die Bewertung. Der gespeicherte // Artikel bleibt unveraendert, das hier ist nur der Pruefkoerper. const faqText = (Array.isArray(artikel.faq) ? artikel.faq : []) .map(f => `

${f.frage || ''}

${f.antwort || ''}

`).join('\n'); const bewertungs_html = [ + artikel.titel ? `

${artikel.titel}

` : '', artikel.kurzantwort ? `

${artikel.kurzantwort}

` : '', artikel.content_html || '', faqText ? `

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

\n${faqText}` : '', diff --git a/build/nw_score.py b/build/nw_score.py index e1e2cf0..8ad24b7 100644 --- a/build/nw_score.py +++ b/build/nw_score.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Lokaler NeuronWriter-Score, identischer Pruefkoerper wie WF2 (Kurzantwort + Inhalt + FAQ). +"""Lokaler NeuronWriter-Score, identischer Pruefkoerper wie WF2 (H1 + Kurzantwort + Inhalt + FAQ). Aufruf: python3 build/nw_score.py artikel/artikel-SLUG.json [...]""" import json, os, sys, urllib.request @@ -15,7 +15,7 @@ E = env() for pfad in sys.argv[1:]: a = json.load(open(pfad, encoding='utf-8')) faq = '\n'.join(f"

{f.get('frage','')}

{f.get('antwort','')}

" for f in a.get('faq') or []) - html = '\n'.join([f"

{a['kurzantwort']}

" if a.get('kurzantwort') else '', a['content_html'], + html = '\n'.join([f"

{a['titel']}

", f"

{a['kurzantwort']}

" if a.get('kurzantwort') else '', a['content_html'], f"

{a.get('faq_ueberschrift') or 'Häufige Fragen'}

\n{faq}" if faq else '']) body = json.dumps({'query': a['query_id'], 'html': html, 'title': a.get('seo_titel') or a['titel'], 'description': a.get('seo_beschreibung', '')}).encode() diff --git a/workflows/pruefen-und-importieren.json b/workflows/pruefen-und-importieren.json index f96cbe4..a53be79 100644 --- a/workflows/pruefen-und-importieren.json +++ b/workflows/pruefen-und-importieren.json @@ -9,7 +9,7 @@ -460, 100 ], - "id": "e645bc35-12a4-4aed-9c7a-ecc74d00f453", + "id": "be3731b7-58d4-44e6-acc5-6272dbb3b681", "name": "Manuell starten" }, { @@ -25,9 +25,9 @@ -460, 280 ], - "id": "1d24eaed-3b4f-41e8-9016-4cc6923ab622", + "id": "0755c10f-e537-4a17-a15a-9e405f5947ce", "name": "Webhook Start", - "webhookId": "a54ca6b3-e645-465e-8969-fd2e242055ae" + "webhookId": "9866920d-2335-4c01-a289-c2b9aca47d8c" }, { "parameters": { @@ -35,115 +35,115 @@ "assignments": { "assignments": [ { - "id": "81fec9aa-b2e1-42a2-8eb3-87106e596bde", + "id": "2f0d5d8d-4f12-4a78-8fb4-f66aa4a0d01a", "name": "nw_base", "value": "https://app.neuronwriter.com/neuron-api/0.5/writer", "type": "string" }, { - "id": "ee7027dd-2ebd-4570-aaf5-85057bf01135", + "id": "d5bc9cac-5db5-40e3-bdad-2ceca39ac584", "name": "gitea_base", "value": "http://gitea:3000", "type": "string" }, { - "id": "03aee034-b048-4065-97c7-cf79f3983faa", + "id": "b91fd120-5168-4712-90a9-0b4049a47cdb", "name": "gitea_owner", "value": "lucas.orth", "type": "string" }, { - "id": "4f52fda8-7009-45c4-a639-47f2781745a9", + "id": "c757cc8c-f990-4e22-ab6a-7e7fda31e720", "name": "gitea_repo", "value": "Skrift-Auto-Article", "type": "string" }, { - "id": "c90e3261-c81c-4793-bc2f-dd8161939f08", + "id": "b0fdc07a-a7d4-4e78-8b8f-db50fb5d8a00", "name": "branch", "value": "main", "type": "string" }, { - "id": "6a4e762a-a262-4aae-b4a9-a746e1e163e6", + "id": "84922b0c-84de-4d50-8bda-f8562594ea00", "name": "artikel_ordner", "value": "artikel", "type": "string" }, { - "id": "f724b9be-2c72-4e94-a9b9-62a9f22baf50", + "id": "18d46746-ae2c-44c7-aab2-918133d4a29d", "name": "brief_ordner", "value": "briefe", "type": "string" }, { - "id": "742cbf71-76b2-4c86-ab81-8ddf644d4422", + "id": "24d34020-9424-47ea-8a2a-6ff1805c9424", "name": "nachbessern_ordner", "value": "nachbessern", "type": "string" }, { - "id": "4c5b9314-bc21-4474-8e48-056deecd14d8", + "id": "cc559866-a0b2-46be-b3cb-f27d2b9ff47f", "name": "status_pfad", "value": "status-importe.json", "type": "string" }, { - "id": "95d6f83b-aa71-4ebf-861e-ad6d2e9fa79b", + "id": "e5a30c70-dde6-4250-af13-1d49a8e4ab76", "name": "wp_base", "value": "https://skrift.de", "type": "string" }, { - "id": "105b0778-3d05-4b5f-9fed-99409c2aa44f", + "id": "af531179-ad15-40f9-bc3e-7ef6051653ba", "name": "score_ziel", "value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}", "type": "number" }, { - "id": "7f9d5e34-8319-407f-8c06-4acd2aab10f2", + "id": "794423e7-cd48-448d-94c6-787f66bb6af7", "name": "pause_zwischen_artikeln", "value": 5, "type": "number" }, { - "id": "0562defc-78e5-4bf5-a3be-caccff62640c", + "id": "d046a1fc-c6c2-4b19-8fc6-744ecaded14a", "name": "elementor_ausgabe", "value": false, "type": "boolean" }, { - "id": "d16d9864-9d93-438c-a7cf-ae9c9cdcaff0", + "id": "8c21a814-4f0f-4d47-afdd-d17efa005a98", "name": "bild_aktiv", "value": true, "type": "boolean" }, { - "id": "2c439140-a3ad-4f7d-8565-0810b3c27467", + "id": "9e35d60a-4922-41be-bf71-a1d63da06ab6", "name": "bild_modell", "value": "gpt-image-2", "type": "string" }, { - "id": "1ae43107-262a-4257-a89d-132f8dabfdd9", + "id": "1103be64-00cd-4948-9a9f-2bc65af5b6cd", "name": "bild_groesse", "value": "1200x1200", "type": "string" }, { - "id": "886ab8cf-1e76-4775-ba4f-727fe2389d81", + "id": "2da63505-15cc-43dc-9430-5091c8f4cc0c", "name": "bild_qualitaet", "value": "medium", "type": "string" }, { - "id": "e255dc56-5585-4c5d-831f-41913892e9f9", + "id": "459d8553-1df2-4141-b9c1-45c505ef2b77", "name": "faq_ueberschrift", "value": "Häufige Fragen", "type": "string" }, { - "id": "482a9f25-1180-422a-a03e-04e8d555b672", + "id": "bc459271-c4a2-408b-a64f-7945f943e785", "name": "benachrichtigung_an", "value": "privat@lucas-orth.de", "type": "string" @@ -158,7 +158,7 @@ -240, 190 ], - "id": "6ad49d4e-b542-499f-907a-2eb4a82e0cad", + "id": "59fcd646-0bc1-4ace-b073-dd3b634fc13a", "name": "Konfiguration" }, { @@ -183,7 +183,7 @@ -20, 190 ], - "id": "d049d98d-d7c6-4b65-a981-2b783050d08f", + "id": "cc17c686-84fc-4ebc-84d1-09d39dedb732", "name": "Artikelordner lesen", "credentials": { "httpHeaderAuth": { @@ -206,7 +206,7 @@ 200, 190 ], - "id": "fe3a6d0a-982d-41f1-86a1-74363a3fd16b", + "id": "25ffe9be-454d-4ce6-8ded-e1cbc29ae3ca", "name": "Artikeldateien filtern" }, { @@ -220,7 +220,7 @@ }, "conditions": [ { - "id": "875e5b9f-9c14-432f-abc9-ac4ede4f53b2", + "id": "05d539f6-ce93-45de-adf5-d2833aa28cd1", "leftValue": "={{ $json.leer === true }}", "rightValue": "", "operator": { @@ -241,7 +241,7 @@ 420, 190 ], - "id": "c83e34b8-5538-4aa2-893a-ab75c67d7bbf", + "id": "f5084550-fb1d-4a97-8dc0-e0edea4f78a2", "name": "Artikel vorhanden?" }, { @@ -252,7 +252,7 @@ 640, 40 ], - "id": "b971a2aa-7412-44b1-9ea7-192727cd8552", + "id": "302a85d0-4238-4cf8-83d6-f508850fb265", "name": "Nichts zu tun" }, { @@ -268,7 +268,7 @@ 640, 320 ], - "id": "17dad5b6-7d29-4200-9895-d9cfa89e3e65", + "id": "387aa2cd-4dfc-440c-8cdc-54c6bdb0f92f", "name": "Artikel nacheinander" }, { @@ -292,7 +292,7 @@ 900, 420 ], - "id": "70cf0ad6-149c-4930-8e32-6b469ef96dc4", + "id": "08d2b6c6-e75b-49ea-a303-5f299977eb68", "name": "Artikeldatei laden", "credentials": { "httpHeaderAuth": { @@ -316,7 +316,7 @@ 900, 640 ], - "id": "15e29181-177a-496e-aedc-1861f3ca021a", + "id": "b8dff49e-5773-4438-b2ea-806c0728667d", "name": "Fehler Artikeldatei", "onError": "continueRegularOutput" }, @@ -342,7 +342,7 @@ 1120, 420 ], - "id": "e0493f24-d401-47fc-917b-d1091ab17f1e", + "id": "34db62e0-4b60-4db1-94cc-4d4634e77048", "name": "Brief laden", "credentials": { "httpHeaderAuth": { @@ -354,7 +354,7 @@ { "parameters": { "mode": "runOnceForAllItems", - "jsCode": "// Stilpruefung: findet typische Muster maschinell wirkender Texte im Deutschen.\n// Wird von gen_wf2.py in den Code-Node \"Artikel pruefen\" eingebettet und ist\n// lokal mit node testbar. Liefert Hinweise, blockiert nichts.\nfunction stilpruefung(artikel) {\n const hinweise = [];\n const add = (art, stelle) => hinweise.push({ art, stelle: String(stelle).slice(0, 140) });\n const ohneTags = (h) => String(h || '').replace(/<[^>]+>/g, ' ').replace(/ /g, ' ').replace(/\\s+/g, ' ').trim();\n\n const html = String(artikel.content_html || '');\n const absaetze = [...html.matchAll(/

([\\s\\S]*?)<\\/p>/g)].map(m => ohneTags(m[1])).filter(Boolean);\n const volltext = [ohneTags(artikel.kurzantwort), ohneTags(html),\n ...(Array.isArray(artikel.faq) ? artikel.faq.map(f => ohneTags(f.antwort)) : [])].join(' ');\n\n // 1) Floskeln und Formeln, die in generierten Texten gehaeuft auftreten\n const formeln = [\n ['genau das', 'Formel'], ['genau diese', 'Formel'], ['genau daran', 'Formel'], ['genau so', 'Formel'],\n ['der grund ist', 'Formel'], ['nicht nur', 'Formel \"nicht nur, sondern auch\"'],\n ['entscheidend ist', 'Formel'], ['entscheidend für', 'Formel'], ['größten hebel', 'Formel'],\n ['den unterschied', 'Formel'], ['am ende des tages', 'Floskel'], ['unterm strich', 'Floskel'],\n ['kurz gesagt', 'Floskel'], ['mit anderen worten', 'Floskel'], ['die gute nachricht', 'Floskel'],\n ['spielt eine rolle', 'Floskel'], ['es lohnt sich', 'Floskel'], ['egal ob', 'Formel'],\n ['in diesem artikel', 'Meta-Satz'], ['wir zeigen ihnen', 'Meta-Satz'], ['fazit', 'Zusammenfassung'],\n ['tatsächlich', 'Fuellwort'], ['letztlich', 'Fuellwort'], ['schlicht', 'Fuellwort'], ['simpel', 'Fuellwort'],\n ['erfahrungsgemäß', 'unbelegte Behauptung'], ['in aller regel', 'unbelegte Behauptung'],\n ['studien zeigen', 'unbelegte Behauptung'], ['nachweislich', 'unbelegte Behauptung'],\n ['bekanntlich', 'Floskel'],\n ];\n const klein = volltext.toLowerCase();\n formeln.forEach(([f, art]) => {\n let i = klein.indexOf(f);\n while (i >= 0) {\n add(art + ': \"' + f + '\"', '…' + volltext.slice(Math.max(0, i - 40), i + f.length + 50) + '…');\n i = klein.indexOf(f, i + f.length);\n }\n });\n\n // 2) Aufzaehlungen im Fliesstext\n if (/\\berstens\\b[\\s\\S]*\\bzweitens\\b/i.test(volltext)) add('Aufzaehlung \"Erstens, zweitens\"', 'Im Fliesstext lieber eine echte Liste oder zwei Saetze');\n const dreier = volltext.match(/\\b\\w+(?:ig|lich|ell|bar|iv|sam)\\b, \\w+(?:ig|lich|ell|bar|iv|sam)\\b und \\w+(?:ig|lich|ell|bar|iv|sam)\\b/gi) || [];\n dreier.forEach(d => add('Dreierreihe aus Adjektiven', d));\n\n // 3) Rhetorische Frage im Absatz, direkt beantwortet\n absaetze.forEach(a => {\n const m = a.match(/[^.!?]{8,}\\?\\s+[A-ZÄÖÜ][^.!?]{3,}[.!]/);\n if (m) add('Rhetorische Frage mit Sofortantwort', m[0]);\n });\n\n // 4) Satzlaengen: gleichfoermiger Rhythmus\n const saetze = ohneTags(html).split(/(?<=[.!?])\\s+/).map(s => s.trim())\n .filter(s => s.split(' ').length >= 3);\n const laengen = saetze.map(s => s.split(' ').length);\n if (laengen.length >= 8) {\n const mittel = laengen.reduce((x, y) => x + y, 0) / laengen.length;\n const sd = Math.sqrt(laengen.reduce((x, y) => x + (y - mittel) ** 2, 0) / laengen.length);\n if (sd < 4.5) add('Gleichfoermige Satzlaengen', `Durchschnitt ${mittel.toFixed(1)} Woerter, Streuung nur ${sd.toFixed(1)}. Kurze und lange Saetze mischen.`);\n for (let i = 0; i + 4 <= laengen.length; i++) {\n const fenster = laengen.slice(i, i + 4);\n if (Math.max(...fenster) - Math.min(...fenster) <= 3) {\n add('Vier Saetze gleicher Laenge hintereinander', saetze[i]);\n i += 3;\n }\n }\n }\n\n // 5) Gleiche Satzanfaenge\n for (let i = 0; i + 3 <= saetze.length; i++) {\n const w = saetze.slice(i, i + 3).map(s => s.split(' ')[0].toLowerCase());\n if (w[0] === w[1] && w[1] === w[2] && w[0].length > 2) { add('Drei Saetze mit gleichem Anfang', saetze[i]); i += 2; }\n }\n const anfaenge = {};\n absaetze.forEach(a => { const k = a.split(' ').slice(0, 2).join(' ').toLowerCase(); anfaenge[k] = (anfaenge[k] || 0) + 1; });\n Object.entries(anfaenge).filter(([, n]) => n >= 3).forEach(([k, n]) => add('Absatzanfaenge wiederholen sich', `${n} Absaetze beginnen mit \"${k}\"`));\n\n // 6) Gleich lange Absaetze\n const al = absaetze.map(a => a.split(' ').length).filter(n => n >= 12);\n if (al.length >= 5) {\n const m = al.reduce((x, y) => x + y, 0) / al.length;\n const sd = Math.sqrt(al.reduce((x, y) => x + (y - m) ** 2, 0) / al.length);\n if (sd / m < 0.25) add('Absaetze fast gleich lang', `Durchschnitt ${m.toFixed(0)} Woerter, kaum Abweichung. Einen Absatz bewusst kurz halten.`);\n }\n\n // 7) Praxiswissen: ohne eigene Stichpunkte bleibt der Text generisch\n if (!Array.isArray(artikel.praxis) || artikel.praxis.length === 0) {\n add('Kein Praxiswissen hinterlegt', 'Feld \"praxis\" in artikel-index.json fuellen, dann den Artikel darauf aufbauen');\n }\n\n // 7b) Preise: immer inkl. MwSt., Formulierung \"inkl. 19 % MwSt.\"\n const alleTexte = [String(artikel.kurzantwort || ''), html,\n ...(Array.isArray(artikel.faq) ? artikel.faq.map(f => String(f.antwort || '')) : [])].join(' ');\n (alleTexte.match(/\\[skrift_preis[^\\]]*\\]/g) || []).forEach(s => {\n if (!/brutto=\"1\"/.test(s)) add('Preis ohne MwSt.', `${s} braucht brutto=\"1\"`);\n });\n const nb = ohneTags(alleTexte.replace(/\\[skrift_preis[^\\]]*\\]/g, 'PREIS')).match(/[^.]{0,40}\\b(netto|brutto|zzgl\\.?|zuzüglich)\\b[^.]{0,30}/gi) || [];\n nb.forEach(s => add('Preisangabe: \"netto\"/\"brutto\" statt \"inkl. 19 % MwSt.\"', s.trim()));\n\n // 8) Interne Links: primaer auf Business-Leistungsseiten\n const B = 'https://skrift.de/';\n const BUSINESS = ['handgeschriebene-businessbriefe', 'handgeschriebene-postkarten',\n 'unterschriftenservice', 'automatisierte-follow-ups'];\n const NEUTRAL = ['papier-schriftmuster', 'kontakt'];\n const PRIVAT = ['handschrift-fuer-privatpersonen', 'handgeschriebene-private-briefe',\n 'handgeschriebene-private-postkarten'];\n const slugVon = (u) => String(u || '').replace(/^https?:\\/\\/(www\\.)?skrift\\.de\\/?/, '').replace(/\\/.*$/, '');\n const links = [...html.matchAll(/]*href=\"([^\"]+)\"/g)].map(m => m[1]);\n const intern = links.filter(u => /^https?:\\/\\/(www\\.)?skrift\\.de/.test(u) || u.startsWith('/'));\n const slugs = intern.map(u => u.startsWith('/') ? u.replace(/^\\/|\\/.*$/g, '') : slugVon(u));\n\n const cta = slugVon(artikel.cta_ziel);\n if (!BUSINESS.includes(cta)) add('Links: CTA-Ziel ist keine Business-Leistungsseite', `cta_ziel \"${artikel.cta_ziel || 'leer'}\", erlaubt: ${BUSINESS.join(', ')}`);\n const ctaAnzahl = slugs.filter(s => s === cta).length;\n if (ctaAnzahl < 1) add('Links: CTA-Ziel nicht verlinkt', 'cta_ziel mindestens einmal im Fliesstext verlinken');\n const bus = slugs.filter(s => BUSINESS.includes(s));\n const priv = slugs.filter(s => PRIVAT.includes(s));\n if (bus.length < 2) add('Links: zu wenige Business-Links', `${bus.length} Link(s) auf Business-Seiten, mindestens 2`);\n if (priv.length) add('Links: Privatkunden-Link im Text', 'Privatkunden-Seiten nicht aus dem Artikeltext verlinken, das uebernimmt das Seitenlayout');\n // Bausteine, die das Seitenlayout bereits liefert\n if (/Auch für Privatpersonen/i.test(html)) add('Baustein doppelt: \"Auch für Privatpersonen.\"', 'Block aus dem Artikeltext entfernen, er steht im Seitenlayout');\n if (/]*>[^<]*(Muster anfordern|Handschriftmuster)/i.test(html)) add('Baustein doppelt: Muster-CTA', 'Abschnitt \"Kostenloses Handschriftmuster anfordern.\" entfernen, er steht im Seitenlayout');\n // Querverweise auf andere Ratgeberartikel\n const quer = [...html.matchAll(/\\[ratgeber_link\\s+slug=\"([^\"]+)\"\\]([\\s\\S]*?)\\[\\/ratgeber_link\\]/g)];\n if (quer.length < 2) add('Querverweise: zu wenige', `${quer.length} [ratgeber_link], mindestens 2 auf verwandte Ratgeberartikel`);\n quer.filter(q => q[1] === artikel.slug).forEach(() => add('Querverweis auf sich selbst', artikel.slug));\n quer.filter(q => !q[2].trim()).forEach(q => add('Querverweis ohne Ankertext', q[1]));\n (html.match(/href=\"https?:\\/\\/(www\\.)?skrift\\.de\\/(?!handgeschriebene-|unterschriftenservice|automatisierte-follow-ups|papier-schriftmuster|kontakt|handschrift-fuer)[a-z0-9-]+\\/?\"/g) || [])\n .forEach(h => add('Ratgeber-Link als harter Link', `${h} als [ratgeber_link slug=\"...\"] setzen, sonst droht ein toter Link`));\n\n const unbekannt = [...new Set(slugs.filter(s => s && !BUSINESS.includes(s) && !NEUTRAL.includes(s) && !PRIVAT.includes(s)))];\n unbekannt.forEach(s => add('Links: kein Linkziel fuer Ratgeber', `/${s}/ (z. B. Dankeseite, Rechtstexte)`));\n\n return hinweise;\n}\n\n\nconst meta = $('Artikel nacheinander').first().json;\nconst artRoh = $('Artikeldatei laden').first().json;\nconst briefAntwort = $input.first().json;\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 kb = K(briefAntwort);\n\nlet artikel;\ntry { artikel = JSON.parse(Buffer.from(artRoh.content, 'base64').toString('utf8')); }\ncatch (e) { throw new Error('artikel-' + meta.slug + '.json ist kein gueltiges JSON: ' + e.message); }\n\nconst pflicht = ['slug', 'titel', 'query_id', 'kategorie', 'content_html'];\nconst fehlt = pflicht.filter(f => !artikel[f]);\nif (fehlt.length) {\n throw new Error('artikel-' + meta.slug + '.json fehlen Pflichtfelder: ' + fehlt.join(', ')\n + '. Erwartetes Format siehe README Abschnitt \"Vertrag Cowork -> Workflow 2\".');\n}\nif (artikel.slug !== meta.slug) {\n throw new Error('Slug im Dateinamen (' + meta.slug + ') und im JSON (' + artikel.slug + ') stimmen nicht ueberein.');\n}\n\nlet brief = null;\nif (briefAntwort.statusCode === 200 && kb && kb.content) {\n try { brief = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8')); }\n catch (e) { brief = null; }\n}\n\n// Preise muessen als Shortcode stehen, nie ausgeschrieben.\nconst preisTreffer = [];\nconst re = /(\\d{1,3}(?:[.,]\\d{1,2})?)\\s*(?:€|EUR|Euro)/gi;\nlet m;\nwhile ((m = re.exec(String(artikel.content_html || ''))) !== null) {\n preisTreffer.push(m[0].trim());\n}\nconst shortcodes = (String(artikel.content_html || '').match(/\\[skrift_preis[^\\]]*\\]/g) || []);\n\n// NeuronWriter bewertet nur den uebergebenen Text. Kurzantwort und FAQ stehen\n// aber auf der fertigen Seite, also gehoeren sie in die Bewertung. Der gespeicherte\n// Artikel bleibt unveraendert, das hier ist nur der Pruefkoerper.\nconst faqText = (Array.isArray(artikel.faq) ? artikel.faq : [])\n .map(f => `

${f.frage || ''}

${f.antwort || ''}

`).join('\\n');\nconst bewertungs_html = [\n artikel.kurzantwort ? `

${artikel.kurzantwort}

` : '',\n artikel.content_html || '',\n faqText ? `

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

\\n${faqText}` : '',\n].filter(Boolean).join('\\n');\n\nconst praxis = Array.isArray(artikel.praxis) && artikel.praxis.length ? artikel.praxis\n : ((brief && brief.artikel && brief.artikel.praxis) || []);\n\nreturn [{ json: { slug: meta.slug, artikel, brief, brief_vorhanden: !!brief,\n bewertungs_html,\n stil_hinweise: stilpruefung({ ...artikel, praxis }),\n preis_warnung: preisTreffer.length\n ? `${preisTreffer.length} ausgeschriebene(r) Preis(e) im Text: ${[...new Set(preisTreffer)].slice(0,5).join(', ')}. `\n + 'Bitte durch [skrift_preis key=\"...\"] ersetzen.'\n : null,\n preis_shortcodes: shortcodes.length } }];\n" + "jsCode": "// Stilpruefung: findet typische Muster maschinell wirkender Texte im Deutschen.\n// Wird von gen_wf2.py in den Code-Node \"Artikel pruefen\" eingebettet und ist\n// lokal mit node testbar. Liefert Hinweise, blockiert nichts.\nfunction stilpruefung(artikel) {\n const hinweise = [];\n const add = (art, stelle) => hinweise.push({ art, stelle: String(stelle).slice(0, 140) });\n const ohneTags = (h) => String(h || '').replace(/<[^>]+>/g, ' ').replace(/ /g, ' ').replace(/\\s+/g, ' ').trim();\n\n const html = String(artikel.content_html || '');\n const absaetze = [...html.matchAll(/

([\\s\\S]*?)<\\/p>/g)].map(m => ohneTags(m[1])).filter(Boolean);\n const volltext = [ohneTags(artikel.kurzantwort), ohneTags(html),\n ...(Array.isArray(artikel.faq) ? artikel.faq.map(f => ohneTags(f.antwort)) : [])].join(' ');\n\n // 1) Floskeln und Formeln, die in generierten Texten gehaeuft auftreten\n const formeln = [\n ['genau das', 'Formel'], ['genau diese', 'Formel'], ['genau daran', 'Formel'], ['genau so', 'Formel'],\n ['der grund ist', 'Formel'], ['nicht nur', 'Formel \"nicht nur, sondern auch\"'],\n ['entscheidend ist', 'Formel'], ['entscheidend für', 'Formel'], ['größten hebel', 'Formel'],\n ['den unterschied', 'Formel'], ['am ende des tages', 'Floskel'], ['unterm strich', 'Floskel'],\n ['kurz gesagt', 'Floskel'], ['mit anderen worten', 'Floskel'], ['die gute nachricht', 'Floskel'],\n ['spielt eine rolle', 'Floskel'], ['es lohnt sich', 'Floskel'], ['egal ob', 'Formel'],\n ['in diesem artikel', 'Meta-Satz'], ['wir zeigen ihnen', 'Meta-Satz'], ['fazit', 'Zusammenfassung'],\n ['tatsächlich', 'Fuellwort'], ['letztlich', 'Fuellwort'], ['schlicht', 'Fuellwort'], ['simpel', 'Fuellwort'],\n ['erfahrungsgemäß', 'unbelegte Behauptung'], ['in aller regel', 'unbelegte Behauptung'],\n ['studien zeigen', 'unbelegte Behauptung'], ['nachweislich', 'unbelegte Behauptung'],\n ['bekanntlich', 'Floskel'],\n ];\n const klein = volltext.toLowerCase();\n formeln.forEach(([f, art]) => {\n let i = klein.indexOf(f);\n while (i >= 0) {\n add(art + ': \"' + f + '\"', '…' + volltext.slice(Math.max(0, i - 40), i + f.length + 50) + '…');\n i = klein.indexOf(f, i + f.length);\n }\n });\n\n // 2) Aufzaehlungen im Fliesstext\n if (/\\berstens\\b[\\s\\S]*\\bzweitens\\b/i.test(volltext)) add('Aufzaehlung \"Erstens, zweitens\"', 'Im Fliesstext lieber eine echte Liste oder zwei Saetze');\n const dreier = volltext.match(/\\b\\w+(?:ig|lich|ell|bar|iv|sam)\\b, \\w+(?:ig|lich|ell|bar|iv|sam)\\b und \\w+(?:ig|lich|ell|bar|iv|sam)\\b/gi) || [];\n dreier.forEach(d => add('Dreierreihe aus Adjektiven', d));\n\n // 3) Rhetorische Frage im Absatz, direkt beantwortet\n absaetze.forEach(a => {\n const m = a.match(/[^.!?]{8,}\\?\\s+[A-ZÄÖÜ][^.!?]{3,}[.!]/);\n if (m) add('Rhetorische Frage mit Sofortantwort', m[0]);\n });\n\n // 4) Satzlaengen: gleichfoermiger Rhythmus\n const saetze = ohneTags(html).split(/(?<=[.!?])\\s+/).map(s => s.trim())\n .filter(s => s.split(' ').length >= 3);\n const laengen = saetze.map(s => s.split(' ').length);\n if (laengen.length >= 8) {\n const mittel = laengen.reduce((x, y) => x + y, 0) / laengen.length;\n const sd = Math.sqrt(laengen.reduce((x, y) => x + (y - mittel) ** 2, 0) / laengen.length);\n if (sd < 4.5) add('Gleichfoermige Satzlaengen', `Durchschnitt ${mittel.toFixed(1)} Woerter, Streuung nur ${sd.toFixed(1)}. Kurze und lange Saetze mischen.`);\n for (let i = 0; i + 4 <= laengen.length; i++) {\n const fenster = laengen.slice(i, i + 4);\n if (Math.max(...fenster) - Math.min(...fenster) <= 3) {\n add('Vier Saetze gleicher Laenge hintereinander', saetze[i]);\n i += 3;\n }\n }\n }\n\n // 5) Gleiche Satzanfaenge\n for (let i = 0; i + 3 <= saetze.length; i++) {\n const w = saetze.slice(i, i + 3).map(s => s.split(' ')[0].toLowerCase());\n if (w[0] === w[1] && w[1] === w[2] && w[0].length > 2) { add('Drei Saetze mit gleichem Anfang', saetze[i]); i += 2; }\n }\n const anfaenge = {};\n absaetze.forEach(a => { const k = a.split(' ').slice(0, 2).join(' ').toLowerCase(); anfaenge[k] = (anfaenge[k] || 0) + 1; });\n Object.entries(anfaenge).filter(([, n]) => n >= 3).forEach(([k, n]) => add('Absatzanfaenge wiederholen sich', `${n} Absaetze beginnen mit \"${k}\"`));\n\n // 6) Gleich lange Absaetze\n const al = absaetze.map(a => a.split(' ').length).filter(n => n >= 12);\n if (al.length >= 5) {\n const m = al.reduce((x, y) => x + y, 0) / al.length;\n const sd = Math.sqrt(al.reduce((x, y) => x + (y - m) ** 2, 0) / al.length);\n if (sd / m < 0.25) add('Absaetze fast gleich lang', `Durchschnitt ${m.toFixed(0)} Woerter, kaum Abweichung. Einen Absatz bewusst kurz halten.`);\n }\n\n // 7) Praxiswissen: ohne eigene Stichpunkte bleibt der Text generisch\n if (!Array.isArray(artikel.praxis) || artikel.praxis.length === 0) {\n add('Kein Praxiswissen hinterlegt', 'Feld \"praxis\" in artikel-index.json fuellen, dann den Artikel darauf aufbauen');\n }\n\n // 7b) Preise: immer inkl. MwSt., Formulierung \"inkl. 19 % MwSt.\"\n const alleTexte = [String(artikel.kurzantwort || ''), html,\n ...(Array.isArray(artikel.faq) ? artikel.faq.map(f => String(f.antwort || '')) : [])].join(' ');\n (alleTexte.match(/\\[skrift_preis[^\\]]*\\]/g) || []).forEach(s => {\n if (!/brutto=\"1\"/.test(s)) add('Preis ohne MwSt.', `${s} braucht brutto=\"1\"`);\n });\n const nb = ohneTags(alleTexte.replace(/\\[skrift_preis[^\\]]*\\]/g, 'PREIS')).match(/[^.]{0,40}\\b(netto|brutto|zzgl\\.?|zuzüglich)\\b[^.]{0,30}/gi) || [];\n nb.forEach(s => add('Preisangabe: \"netto\"/\"brutto\" statt \"inkl. 19 % MwSt.\"', s.trim()));\n\n // 8) Interne Links: primaer auf Business-Leistungsseiten\n const B = 'https://skrift.de/';\n const BUSINESS = ['handgeschriebene-businessbriefe', 'handgeschriebene-postkarten',\n 'unterschriftenservice', 'automatisierte-follow-ups'];\n const NEUTRAL = ['papier-schriftmuster', 'kontakt'];\n const PRIVAT = ['handschrift-fuer-privatpersonen', 'handgeschriebene-private-briefe',\n 'handgeschriebene-private-postkarten'];\n const slugVon = (u) => String(u || '').replace(/^https?:\\/\\/(www\\.)?skrift\\.de\\/?/, '').replace(/\\/.*$/, '');\n const links = [...html.matchAll(/]*href=\"([^\"]+)\"/g)].map(m => m[1]);\n const intern = links.filter(u => /^https?:\\/\\/(www\\.)?skrift\\.de/.test(u) || u.startsWith('/'));\n const slugs = intern.map(u => u.startsWith('/') ? u.replace(/^\\/|\\/.*$/g, '') : slugVon(u));\n\n const cta = slugVon(artikel.cta_ziel);\n if (!BUSINESS.includes(cta)) add('Links: CTA-Ziel ist keine Business-Leistungsseite', `cta_ziel \"${artikel.cta_ziel || 'leer'}\", erlaubt: ${BUSINESS.join(', ')}`);\n const ctaAnzahl = slugs.filter(s => s === cta).length;\n if (ctaAnzahl < 1) add('Links: CTA-Ziel nicht verlinkt', 'cta_ziel mindestens einmal im Fliesstext verlinken');\n const bus = slugs.filter(s => BUSINESS.includes(s));\n const priv = slugs.filter(s => PRIVAT.includes(s));\n if (bus.length < 2) add('Links: zu wenige Business-Links', `${bus.length} Link(s) auf Business-Seiten, mindestens 2`);\n if (priv.length) add('Links: Privatkunden-Link im Text', 'Privatkunden-Seiten nicht aus dem Artikeltext verlinken, das uebernimmt das Seitenlayout');\n // Bausteine, die das Seitenlayout bereits liefert\n if (/Auch für Privatpersonen/i.test(html)) add('Baustein doppelt: \"Auch für Privatpersonen.\"', 'Block aus dem Artikeltext entfernen, er steht im Seitenlayout');\n if (/]*>[^<]*(Muster anfordern|Handschriftmuster)/i.test(html)) add('Baustein doppelt: Muster-CTA', 'Abschnitt \"Kostenloses Handschriftmuster anfordern.\" entfernen, er steht im Seitenlayout');\n // Querverweise auf andere Ratgeberartikel\n const quer = [...html.matchAll(/\\[ratgeber_link\\s+slug=\"([^\"]+)\"\\]([\\s\\S]*?)\\[\\/ratgeber_link\\]/g)];\n if (quer.length < 2) add('Querverweise: zu wenige', `${quer.length} [ratgeber_link], mindestens 2 auf verwandte Ratgeberartikel`);\n quer.filter(q => q[1] === artikel.slug).forEach(() => add('Querverweis auf sich selbst', artikel.slug));\n quer.filter(q => !q[2].trim()).forEach(q => add('Querverweis ohne Ankertext', q[1]));\n (html.match(/href=\"https?:\\/\\/(www\\.)?skrift\\.de\\/(?!handgeschriebene-|unterschriftenservice|automatisierte-follow-ups|papier-schriftmuster|kontakt|handschrift-fuer)[a-z0-9-]+\\/?\"/g) || [])\n .forEach(h => add('Ratgeber-Link als harter Link', `${h} als [ratgeber_link slug=\"...\"] setzen, sonst droht ein toter Link`));\n\n const unbekannt = [...new Set(slugs.filter(s => s && !BUSINESS.includes(s) && !NEUTRAL.includes(s) && !PRIVAT.includes(s)))];\n unbekannt.forEach(s => add('Links: kein Linkziel fuer Ratgeber', `/${s}/ (z. B. Dankeseite, Rechtstexte)`));\n\n return hinweise;\n}\n\n\nconst meta = $('Artikel nacheinander').first().json;\nconst artRoh = $('Artikeldatei laden').first().json;\nconst briefAntwort = $input.first().json;\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 kb = K(briefAntwort);\n\nlet artikel;\ntry { artikel = JSON.parse(Buffer.from(artRoh.content, 'base64').toString('utf8')); }\ncatch (e) { throw new Error('artikel-' + meta.slug + '.json ist kein gueltiges JSON: ' + e.message); }\n\nconst pflicht = ['slug', 'titel', 'query_id', 'kategorie', 'content_html'];\nconst fehlt = pflicht.filter(f => !artikel[f]);\nif (fehlt.length) {\n throw new Error('artikel-' + meta.slug + '.json fehlen Pflichtfelder: ' + fehlt.join(', ')\n + '. Erwartetes Format siehe README Abschnitt \"Vertrag Cowork -> Workflow 2\".');\n}\nif (artikel.slug !== meta.slug) {\n throw new Error('Slug im Dateinamen (' + meta.slug + ') und im JSON (' + artikel.slug + ') stimmen nicht ueberein.');\n}\n\nlet brief = null;\nif (briefAntwort.statusCode === 200 && kb && kb.content) {\n try { brief = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8')); }\n catch (e) { brief = null; }\n}\n\n// Preise muessen als Shortcode stehen, nie ausgeschrieben.\nconst preisTreffer = [];\nconst re = /(\\d{1,3}(?:[.,]\\d{1,2})?)\\s*(?:€|EUR|Euro)/gi;\nlet m;\nwhile ((m = re.exec(String(artikel.content_html || ''))) !== null) {\n preisTreffer.push(m[0].trim());\n}\nconst shortcodes = (String(artikel.content_html || '').match(/\\[skrift_preis[^\\]]*\\]/g) || []);\n\n// NeuronWriter bewertet nur den uebergebenen Text. Titel (H1), Kurzantwort und FAQ stehen\n// aber auf der fertigen Seite, also gehoeren sie in die Bewertung. Der gespeicherte\n// Artikel bleibt unveraendert, das hier ist nur der Pruefkoerper.\nconst faqText = (Array.isArray(artikel.faq) ? artikel.faq : [])\n .map(f => `

${f.frage || ''}

${f.antwort || ''}

`).join('\\n');\nconst bewertungs_html = [\n artikel.titel ? `

${artikel.titel}

` : '',\n artikel.kurzantwort ? `

${artikel.kurzantwort}

` : '',\n artikel.content_html || '',\n faqText ? `

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

\\n${faqText}` : '',\n].filter(Boolean).join('\\n');\n\nconst praxis = Array.isArray(artikel.praxis) && artikel.praxis.length ? artikel.praxis\n : ((brief && brief.artikel && brief.artikel.praxis) || []);\n\nreturn [{ json: { slug: meta.slug, artikel, brief, brief_vorhanden: !!brief,\n bewertungs_html,\n stil_hinweise: stilpruefung({ ...artikel, praxis }),\n preis_warnung: preisTreffer.length\n ? `${preisTreffer.length} ausgeschriebene(r) Preis(e) im Text: ${[...new Set(preisTreffer)].slice(0,5).join(', ')}. `\n + 'Bitte durch [skrift_preis key=\"...\"] ersetzen.'\n : null,\n preis_shortcodes: shortcodes.length } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -362,7 +362,7 @@ 1340, 420 ], - "id": "2a7b4374-a366-44f3-96b1-d33dc7378b2c", + "id": "f43fa611-de36-49c4-844b-7d127fbc5585", "name": "Artikel pruefen" }, { @@ -392,7 +392,7 @@ 1560, 420 ], - "id": "f163c0a3-181e-40c0-88a2-f2157042d415", + "id": "8baae2de-d269-4004-8a65-b56a3df52980", "name": "Score ermitteln", "credentials": { "httpHeaderAuth": { @@ -416,7 +416,7 @@ 1560, 640 ], - "id": "4455414d-1ed4-48a3-921b-d10cafe23afd", + "id": "2345ad56-70bf-4e9e-95e5-078f32873908", "name": "Fehler Score", "onError": "continueRegularOutput" }, @@ -431,7 +431,7 @@ 1780, 420 ], - "id": "0fe1ca56-4b69-4504-aa55-87766e82d1e6", + "id": "bb5f3c85-3ce9-4ef6-9472-5c5d1041119a", "name": "Score auswerten" }, { @@ -445,7 +445,7 @@ }, "conditions": [ { - "id": "62fbfd27-c661-454c-b433-b5977f969e52", + "id": "d6bb630a-83c0-4ef6-b58f-b3f04c7e0420", "leftValue": "={{ $json.bestanden }}", "rightValue": "", "operator": { @@ -466,7 +466,7 @@ 2000, 420 ], - "id": "ca4f34e1-f915-4fcf-91bd-6cbcc71abe77", + "id": "77d875da-9aa0-4e70-84e5-14f73ec2a275", "name": "Score erreicht?" }, { @@ -480,7 +480,7 @@ 2220, 640 ], - "id": "f598e1f9-a15a-4b34-a59d-5bdf15513402", + "id": "fead1ab1-34ed-476e-96b9-0fac9d7b6758", "name": "Fehlende Begriffe ermitteln" }, { @@ -505,7 +505,7 @@ 2440, 640 ], - "id": "2a20625a-a1b4-470a-a558-edf71e3985b8", + "id": "c8629336-a50d-4b58-9ae4-da4ebfcb22d0", "name": "Nachbesserung vorhanden?", "credentials": { "httpHeaderAuth": { @@ -525,7 +525,7 @@ 2660, 640 ], - "id": "4413cca3-3996-40ff-88bc-90deceac60c9", + "id": "946d4b02-8c02-43eb-b6c9-1347691487a5", "name": "Nachbesserung vorbereiten" }, { @@ -555,7 +555,7 @@ 2880, 640 ], - "id": "7c6e53c5-fb96-4c18-811b-c6764804b3f9", + "id": "aeff7f66-195f-4434-92d9-ec9ddea02495", "name": "Nachbesserung committen", "credentials": { "httpHeaderAuth": { @@ -579,7 +579,7 @@ 2880, 860 ], - "id": "24d265f2-7379-4dcc-af74-bd275c293ac1", + "id": "b868dae0-af73-442f-8962-f028ee5919ae", "name": "Fehler Nachbesserung", "onError": "continueRegularOutput" }, @@ -594,7 +594,7 @@ 3100, 640 ], - "id": "1bb8c4d3-91f7-4aa1-b0f3-ae176e5e4e3a", + "id": "cfb38044-2ef6-4a04-98d5-e3b100c1abe3", "name": "Nachbesserung gemerkt" }, { @@ -624,7 +624,7 @@ 2220, 240 ], - "id": "b1dd3437-1339-47a8-acf2-ac50a07e2fba", + "id": "87024225-d5c5-4f6e-8de0-2dc8c8779c18", "name": "Revision zu NeuronWriter", "credentials": { "httpHeaderAuth": { @@ -648,7 +648,7 @@ 2220, 60 ], - "id": "41308b76-0798-4d3b-892f-bfa9a0c2331b", + "id": "477ca965-5538-4f95-ad0a-4f303a628ddf", "name": "Fehler Revision", "onError": "continueRegularOutput" }, @@ -674,7 +674,7 @@ 2440, 240 ], - "id": "92090059-6fd5-492b-b3d4-0f381ff24aa6", + "id": "d66ed8ec-6c8c-4341-b470-5481b84ea3b9", "name": "Beitrag vorhanden?", "credentials": { "httpBasicAuth": { @@ -708,7 +708,7 @@ 2660, 240 ], - "id": "db39d451-3fae-476d-ac3f-e8a7a754c3af", + "id": "93bbd3d1-f4d2-4e3a-b725-b439c04a0743", "name": "Kategorie suchen", "credentials": { "httpBasicAuth": { @@ -731,7 +731,7 @@ 2880, 240 ], - "id": "4574946a-3f6c-4616-9b39-ecc412802a93", + "id": "784c106d-1d45-4de5-9840-8e3d331c0a11", "name": "Kategorie pruefen" }, { @@ -745,7 +745,7 @@ }, "conditions": [ { - "id": "2cc73f4f-f7a9-4639-8e76-8a36e28e55ec", + "id": "ae40aca1-0466-4e7f-97b5-032b36d76c33", "leftValue": "={{ $json.kategorie_fehlt }}", "rightValue": "", "operator": { @@ -766,7 +766,7 @@ 3100, 240 ], - "id": "54eda814-f0fa-4289-b85c-7d6bf1552894", + "id": "8fe4d39e-a88f-420c-a35a-27e3eb5fd3e1", "name": "Kategorie anlegen?" }, { @@ -796,7 +796,7 @@ 3320, 140 ], - "id": "97c45331-c51c-4500-9559-1531e5dd69fe", + "id": "be37bc13-b8a2-4a2c-ac3e-a253af1d5e05", "name": "Kategorie anlegen", "credentials": { "httpBasicAuth": { @@ -819,7 +819,7 @@ 3540, 240 ], - "id": "8cca486f-9191-4afb-9022-69a95657cf25", + "id": "64489c08-1877-409d-b680-5884846fffc5", "name": "Kategorie festlegen" }, { @@ -833,7 +833,7 @@ 3760, 240 ], - "id": "64401c03-a0e6-493d-be07-7054964fa3d7", + "id": "5e619ffa-f0b4-4802-b01a-9c9cc0f30ef6", "name": "WordPress-Body bauen" }, { @@ -863,7 +863,7 @@ 3980, 240 ], - "id": "652a752f-aaf2-4169-bf1f-5e5c246b0575", + "id": "f2896d54-c081-4f82-b4c4-fa23a9a3c0fa", "name": "Beitrag anlegen oder aktualisieren", "credentials": { "httpBasicAuth": { @@ -886,7 +886,7 @@ 4200, 240 ], - "id": "5c89708b-21a3-466b-a142-90ca5776cabb", + "id": "acab056e-56c5-4d45-adac-9ba7821cf345", "name": "Ergebnis pruefen" }, { @@ -917,7 +917,7 @@ 4420, 240 ], - "id": "617d82a6-7c93-42df-8aa3-5345ea5e5a11", + "id": "8fb3fedb-4f3e-46d8-a5e9-d7f0fa703a3a", "name": "Cache leeren", "credentials": { "httpBasicAuth": { @@ -937,7 +937,7 @@ 4640, 240 ], - "id": "e930894f-0a23-4b6f-8672-539fddded17a", + "id": "432fbfc3-c81c-4974-94f6-1ebe160d37e1", "name": "Importergebnis merken" }, { @@ -951,9 +951,9 @@ 4860, 420 ], - "id": "b52ef44c-0a99-428f-ac87-6f23ad851dcc", + "id": "df9453e6-48aa-4a1c-8dc4-842593e4f330", "name": "Pause Rate Limit", - "webhookId": "601a717a-a791-4885-9ae4-6211c4042840" + "webhookId": "c29d13d4-053b-4e05-bc34-a62ab21808ba" }, { "parameters": { @@ -966,7 +966,7 @@ }, "conditions": [ { - "id": "3479c179-c254-4e07-bbe3-139c2232cb41", + "id": "095329b9-6759-4e7e-a325-e012f3ec8825", "leftValue": "={{ $('Konfiguration').first().json.bild_aktiv === true && !Number($json.featured_media) }}", "rightValue": "", "operator": { @@ -987,7 +987,7 @@ 4860, 240 ], - "id": "ac9b1b62-9967-4bbf-b437-5b6d9d9c4f03", + "id": "65dbdf3c-cf02-4915-a03a-3f0fc0543356", "name": "Beitragsbild fehlt?" }, { @@ -1012,7 +1012,7 @@ 5080, 120 ], - "id": "fe7baabb-0017-4539-b367-78b55a7b59a4", + "id": "9d88551b-363e-4f2d-8ba2-ce5c7c702c8f", "name": "Bilddaten laden", "credentials": { "httpHeaderAuth": { @@ -1035,7 +1035,7 @@ 5300, 120 ], - "id": "7b14c694-e412-4a82-ac54-3dc038492daa", + "id": "835c8714-a9bf-4036-8f08-2830f0b29d94", "name": "Bildprompt bauen" }, { @@ -1049,7 +1049,7 @@ }, "conditions": [ { - "id": "9cc14322-cbf9-4d63-8032-75f3a326e208", + "id": "c703bc15-3ff7-4b0f-9b6e-9141611aa975", "leftValue": "={{ !!$json.bild_prompt }}", "rightValue": "", "operator": { @@ -1070,7 +1070,7 @@ 5520, 120 ], - "id": "ac5412be-fd49-4ae6-b727-54bb8a5e7b5b", + "id": "f1ab4c18-b561-4581-9b68-d8d3c9701651", "name": "Bildprompt vorhanden?" }, { @@ -1100,7 +1100,7 @@ 5740, 40 ], - "id": "f4a67217-eac5-4292-b48f-97dd852db890", + "id": "c406aa61-49c9-4561-857a-1c05ab21ed87", "name": "Bild erzeugen", "credentials": { "httpHeaderAuth": { @@ -1124,7 +1124,7 @@ 5740, 260 ], - "id": "d0507f71-febb-4770-8879-289cd5db8bde", + "id": "87181190-d3f6-4fe6-b927-0817afdea28f", "name": "Fehler Bild", "onError": "continueRegularOutput" }, @@ -1139,7 +1139,7 @@ 5960, 40 ], - "id": "0a26c40e-f669-447c-9873-b4658a0caa75", + "id": "8bf6e26d-cd73-47df-b97a-128aba60626d", "name": "Bild als Datei" }, { @@ -1174,7 +1174,7 @@ 6180, 40 ], - "id": "1f93892d-ca03-480f-80d4-d3e9136ba6be", + "id": "7569c6db-d2e5-457e-b0aa-11c8d51356f3", "name": "Bild in Mediathek", "credentials": { "httpBasicAuth": { @@ -1208,7 +1208,7 @@ 6400, 40 ], - "id": "df178293-f772-47db-bd70-5ebbbfecc99b", + "id": "7a9b4dd8-11b9-42fd-8852-17cf09d5e121", "name": "Beitragsbild setzen", "credentials": { "httpBasicAuth": { @@ -1231,7 +1231,7 @@ 6620, 40 ], - "id": "7acb87e1-6d08-4068-86e1-148e66e30a15", + "id": "99577ca6-750d-4afa-accc-f85eb3fde9dc", "name": "Bild gemerkt" }, { @@ -1245,7 +1245,7 @@ 900, 80 ], - "id": "de688426-8e55-4f3e-8fa0-6b6c536a645e", + "id": "19abdb52-8b48-4363-b8d8-0b651f6a088b", "name": "Ergebnisse sammeln" }, { @@ -1270,7 +1270,7 @@ 1120, 80 ], - "id": "c4d40ae3-0c2a-4c85-afb2-8e35e3b7a910", + "id": "288fd94a-d9e4-49aa-a672-726d0dd6be20", "name": "Statustabelle lesen", "credentials": { "httpHeaderAuth": { @@ -1290,7 +1290,7 @@ 1340, 80 ], - "id": "ef884157-2d8f-4108-8413-f8a44d30377c", + "id": "8c6ef018-eb58-4999-bfa0-b3375d23bb95", "name": "Statustabelle fortschreiben" }, { @@ -1304,7 +1304,7 @@ }, "conditions": [ { - "id": "88098264-cf81-4b80-8dd6-24e2b69ac9f6", + "id": "d80c40a3-8b68-4c25-96ba-ec948933abec", "leftValue": "={{ $json.ueberspringen }}", "rightValue": "", "operator": { @@ -1325,7 +1325,7 @@ 1560, 80 ], - "id": "680f0cda-2253-4542-a268-b5789d36e3ba", + "id": "6be07c39-1d90-4d70-a44c-449e3e6fc7b9", "name": "Gab es Importe?" }, { @@ -1355,7 +1355,7 @@ 1780, 160 ], - "id": "e3c52d54-c52c-4afb-ba79-4de6fcbe6668", + "id": "e7dcf291-a4ea-4dca-b213-839c7d3abf17", "name": "Statustabelle schreiben", "credentials": { "httpHeaderAuth": { @@ -1379,7 +1379,7 @@ 1780, 340 ], - "id": "fe8b8aca-18cb-4111-858f-61863c8b411f", + "id": "8677d1e3-66eb-4559-b330-f0eff8a23601", "name": "Fehler Statustabelle", "onError": "continueRegularOutput" }, @@ -1394,7 +1394,7 @@ 2000, 80 ], - "id": "557665ff-7190-485b-bbba-b001c50d04bf", + "id": "ea4f12d3-9039-4892-94a8-035d911aff47", "name": "Meldung formulieren" }, { @@ -1412,7 +1412,7 @@ 2220, 80 ], - "id": "0d7c548b-73ac-49a6-a2cd-0c4791507058", + "id": "a895dc43-e8cf-4f82-af63-da713ae5a603", "name": "Benachrichtigung", "disabled": true, "notes": "SMTP-Credential zuweisen, Absender eintragen, dann aktivieren."