Beitragsbild per OpenAI gpt-image-2 in Workflow 2

This commit is contained in:
2026-09-22 10:10:27 +02:00
parent 4da76a4bce
commit e6f76f9429
9 changed files with 707 additions and 80 deletions

View File

@@ -22,3 +22,4 @@ CACHE_PURGE_TOKEN=
SCORE_ZIELWERT=72 SCORE_ZIELWERT=72
NW_ENGINE=google.de NW_ENGINE=google.de
NW_LANGUAGE=German NW_LANGUAGE=German
OPENAI_API_KEY=

View File

@@ -270,6 +270,7 @@ sie beim Schreiben direkt vorliegen.
- Beitrag anlegen oder aktualisieren, immer `status: draft` - Beitrag anlegen oder aktualisieren, immer `status: draft`
- Meta-Felder und SEOPress-Felder im selben Request - Meta-Felder und SEOPress-Felder im selben Request
- Cache leeren (siehe Hinweis unten) - Cache leeren (siehe Hinweis unten)
- Beitragsbild erzeugen, falls noch keins gesetzt ist (siehe unten)
- Zeile in `status-importe.json` - Zeile in `status-importe.json`
6. Benachrichtigung mit Vorschaulinks 6. Benachrichtigung mit Vorschaulinks
@@ -366,6 +367,31 @@ genauso zaehlt.
--- ---
### Beitragsbild
Hat ein importierter Beitrag noch kein Beitragsbild, erzeugt Workflow 2 eins:
1. `bild_farbe` und `bildmotiv` aus `artikel-index.json` lesen
2. Prompt nach `ratgeber/bildstil.md` bauen, die Farbe zusaetzlich mit Namen,
weil das Modell reine Hexwerte etwas zu kraeftig umsetzt
3. `POST https://api.openai.com/v1/images/generations` mit `gpt-image-2`,
Groesse `1536x864` (16:9 direkt, kein Zuschnitt noetig), `output_format: webp`,
`quality: medium`. Antwort in `data[0].b64_json`, Dauer rund 25 Sekunden.
4. Als WebP in die Mediathek laden, Alt-Text "Illustration zum Artikel: <Titel>"
5. Als `featured_media` setzen und gegenpruefen
Ist schon ein Beitragsbild gesetzt, passiert nichts, auch nicht beim erneuten
Import. **Neu erzeugen:** Beitragsbild im Beitrag entfernen, Workflow 2 erneut
starten. Motiv anpassen geht ueber `bildmotiv` in `artikel-index.json`.
Scheitert OpenAI (Guthaben, Verifizierung, abgelehnter Prompt), wird der Artikel
trotzdem importiert und die Ursache gemeldet. Das Hochladen und Setzen in
WordPress laeuft dagegen ohne Continue On Fail.
Schalter und Werte im Node **Konfiguration**: `bild_aktiv`, `bild_modell`,
`bild_groesse`, `bild_qualitaet`. Credential: `OpenAI Bilder`
(HTTP Header Auth, `Authorization: Bearer <key>`).
## 4. Fehlerverhalten ## 4. Fehlerverhalten
Jeder HTTP-Node hat einen eigenen Fehlerausgang mit einer Meldung, die die Jeder HTTP-Node hat einen eigenen Fehlerausgang mit einer Meldung, die die

View File

@@ -6,6 +6,7 @@ CRED_NW = {"httpHeaderAuth": {"id": "U23n2O980GKWocWq", "name": "NeuronWriter
CRED_GITEA = {"httpHeaderAuth": {"id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article"}} CRED_GITEA = {"httpHeaderAuth": {"id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article"}}
_js = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "stilpruefung.js"), encoding="utf-8").read() _js = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "stilpruefung.js"), encoding="utf-8").read()
STIL_JS = _js.split("if (typeof module")[0] + "\n" STIL_JS = _js.split("if (typeof module")[0] + "\n"
CRED_OPENAI = {"httpHeaderAuth": {"id": "03NmFB2C9IFRXIu8", "name": "OpenAI Bilder"}}
CRED_WP = {"httpBasicAuth": {"id": "WIN05TZgM0K5VOGy", "name": "WP skrift.de Redakteur"}} CRED_WP = {"httpBasicAuth": {"id": "WIN05TZgM0K5VOGy", "name": "WP skrift.de Redakteur"}}
nodes, conns = [], {} nodes, conns = [], {}
@@ -71,6 +72,10 @@ KONF = node("Konfiguration", "n8n-nodes-base.set", 3.4, [-240, 190], {
{"id": nid(), "name": "score_ziel", "value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}", "type": "number"}, {"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"}, {"id": nid(), "name": "pause_zwischen_artikeln", "value": 5, "type": "number"},
{"id": nid(), "name": "elementor_ausgabe", "value": False, "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"},
{"id": nid(), "name": "bild_groesse", "value": "1536x864", "type": "string"},
{"id": nid(), "name": "bild_qualitaet", "value": "medium", "type": "string"},
{"id": nid(), "name": "faq_ueberschrift", "value": "Häufige Fragen", "type": "string"}, {"id": nid(), "name": "faq_ueberschrift", "value": "Häufige Fragen", "type": "string"},
{"id": nid(), "name": "benachrichtigung_an", "value": "privat@lucas-orth.de", "type": "string"}, {"id": nid(), "name": "benachrichtigung_an", "value": "privat@lucas-orth.de", "type": "string"},
]}, "options": {}}) ]}, "options": {}})
@@ -561,7 +566,7 @@ const faq_ok = Array.isArray(m.faq) ? m.faq.length : -1;
const konf = $('Konfiguration').first().json; const konf = $('Konfiguration').first().json;
return [{ json: { return [{ json: {
slug: v.slug, ergebnis: 'importiert', score: v.score, score_ziel: v.score_ziel, slug: v.slug, ergebnis: 'importiert', score: v.score, score_ziel: v.score_ziel,
post_id: p.id, aktion: v.vorhanden ? 'aktualisiert' : 'neu angelegt', post_id: p.id, featured_media: p.featured_media || 0, aktion: v.vorhanden ? 'aktualisiert' : 'neu angelegt',
alter_status: v.alter_status, status: p.status, alter_status: v.alter_status, status: p.status,
kategorie: v.kategorie_name, kategorie_id: v.kategorie_id, kategorie: v.kategorie_name, kategorie_id: v.kategorie_id,
vorschau_url: `${konf.wp_base}/?p=${p.id}&preview=true`, vorschau_url: `${konf.wp_base}/?p=${p.id}&preview=true`,
@@ -604,10 +609,138 @@ link(CACHE, CACHE_NOTIZ)
PAUSE = node("Pause Rate Limit", "n8n-nodes-base.wait", 1.1, [4860, 420], PAUSE = node("Pause Rate Limit", "n8n-nodes-base.wait", 1.1, [4860, 420],
{"amount": "={{ $('Konfiguration').first().json.pause_zwischen_artikeln }}", "unit": "seconds"}, {"amount": "={{ $('Konfiguration').first().json.pause_zwischen_artikeln }}", "unit": "seconds"},
webhookId=str(uuid.uuid4())) webhookId=str(uuid.uuid4()))
for q in [CACHE_NOTIZ, NACH_OK, NACH_FEHLER, FEHLER_ART, FEHLER_EVAL]: for q in [NACH_OK, NACH_FEHLER, FEHLER_ART, FEHLER_EVAL]:
link(q, PAUSE) link(q, PAUSE)
link(PAUSE, LOOP) link(PAUSE, LOOP)
# ================================================================ BEITRAGSBILD
BILD_IF = iff("Beitragsbild fehlt?", [4860, 240],
"={{ $('Konfiguration').first().json.bild_aktiv === true && !Number($json.featured_media) }}",
"boolean", "true")
link(CACHE_NOTIZ, BILD_IF)
link(BILD_IF, "Pause Rate Limit", out=1)
BILD_DATEN = http("Bilddaten laden", [5080, 120], "GET",
"={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/artikel-index.json?ref={{ $('Konfiguration').first().json.branch }}",
CRED_GITEA, error_out=False, retry=True,
extra={"options": {"response": {"response": {"neverError": True, "fullResponse": True}}, "timeout": 60000}})
link(BILD_IF, BILD_DATEN, out=0)
BILD_PROMPT = code("Bildprompt bauen", [5300, 120], r"""
// Prompt nach ratgeber/bildstil.md, Farbe und Motiv aus artikel-index.json.
const r = $('Importergebnis merken').first().json;
const antwort = $input.first().json;
const K = (x) => (x && x.body !== undefined) ? x.body : ((x || {}).data);
const konf = $('Konfiguration').first().json;
let zeile = null;
const kb = K(antwort);
if (antwort.statusCode === 200 && kb && kb.content) {
try {
const tab = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8'));
zeile = (Array.isArray(tab) ? tab : []).find(z => z.slug === r.slug) || null;
} catch (e) { zeile = null; }
}
if (!zeile || !zeile.bild_farbe || !zeile.bildmotiv) {
return [{ json: { ...r, bild_prompt: null,
bild_hinweis: 'Kein bild_farbe oder bildmotiv in artikel-index.json, Bild uebersprungen.' } }];
}
const FARBNAME = { '#7B96C2': 'muted light ink blue', '#B6997F': 'muted sepia brown',
'#8C9A82': 'muted sage green', '#C47B5E': 'muted terracotta red' };
const farbe = String(zeile.bild_farbe).toUpperCase();
const name = FARBNAME[farbe] ? ` (${FARBNAME[farbe]}, not more saturated)` : '';
const leer = /price tag/i.test(zeile.bildmotiv) ? ', the price tag is completely empty' : '';
const prompt = [
'Hand-drawn doodle spot illustration, flat editorial icon style.',
`Solid flat background color ${farbe}${name}, completely uniform, no texture, no gradient, no shadow, no vignette.`,
'In the center one simple object, drawn with thick uneven black marker outlines (#1A1A1A), slightly wobbly hand-drawn lines like a felt-tip pen, rounded line ends.',
'Clean closed outlines, no stray marks, no hatching and no small dashes inside the shapes.',
'Object fills are flat off-white cream (#F9F7F5), no shading, no highlights.',
`Motif: ${zeile.bildmotiv}${leer}. Shown straight from the front, not tilted.`,
'Minimal and iconic, lots of empty space, the object takes about 35 percent of the image height.',
'No text, no letters, no numbers, no currency symbols, no logo, no border, no frame, no rounded corners, no people.',
].join(' ');
return [{ json: { ...r, bild_prompt: prompt, bild_titel: zeile.titel || r.slug,
openai_body: JSON.stringify({ model: konf.bild_modell, prompt, size: konf.bild_groesse,
n: 1, output_format: 'webp', quality: konf.bild_qualitaet }) } }];
""")
link(BILD_DATEN, BILD_PROMPT)
PROMPT_IF = iff("Bildprompt vorhanden?", [5520, 120], "={{ !!$json.bild_prompt }}", "boolean", "true")
link(BILD_PROMPT, PROMPT_IF)
link(PROMPT_IF, "Pause Rate Limit", out=1)
# OpenAI darf scheitern, der Artikel ist bereits importiert. Deshalb Fehlerausgang.
BILD_GEN = http("Bild erzeugen", [5740, 40], "POST",
"https://api.openai.com/v1/images/generations", CRED_OPENAI,
body="={{ $json.openai_body }}", retry=True,
extra={"options": {"response": {"response": {"neverError": False}}, "timeout": 180000}})
link(PROMPT_IF, BILD_GEN, out=0)
BILD_FEHLER = code("Fehler Bild", [5740, 260], """
const r = $('Bildprompt bauen').first().json;
const e = $input.first().json;
return [{ json: { ...r, openai_body: undefined,
bild_hinweis: 'Bild nicht erzeugt, Artikel ist trotzdem importiert. Haeufige Ursachen: '
+ 'Guthaben leer, Organisation fuer Bildmodelle nicht verifiziert, Prompt abgelehnt. Antwort: '
+ JSON.stringify(e).slice(0, 300) } }];
""", onError="continueRegularOutput")
link(BILD_GEN, BILD_FEHLER, out=1)
link(BILD_FEHLER, "Pause Rate Limit")
BILD_BIN = code("Bild als Datei", [5960, 40], """
const r = $('Bildprompt bauen').first().json;
const a = $input.first().json;
const b64 = a && a.data && a.data[0] && a.data[0].b64_json;
if (!b64) throw new Error('OpenAI lieferte kein data[0].b64_json. Antwort: ' + JSON.stringify(a).slice(0, 300));
const puffer = Buffer.from(b64, 'base64');
const datei = await this.helpers.prepareBinaryData(puffer, `${r.slug}.webp`, 'image/webp');
const { openai_body, ...rest } = r;
return [{ json: { ...rest, bild_bytes: puffer.length }, binary: { bild: datei } }];
""")
link(BILD_GEN, BILD_BIN, out=0)
# Schreiben nach WordPress: kein Continue On Fail.
node("Bild in Mediathek", "n8n-nodes-base.httpRequest", 4.3, [6180, 40], {
"method": "POST",
"url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/media?alt_text={{ encodeURIComponent('Illustration zum Artikel: ' + $json.bild_titel) }}&title={{ encodeURIComponent($json.bild_titel) }}",
"authentication": "genericCredentialType", "genericAuthType": "httpBasicAuth",
"sendHeaders": True,
"headerParameters": {"parameters": [
{"name": "Content-Disposition", "value": "=attachment; filename=\"{{ $json.slug }}.webp\""},
{"name": "Content-Type", "value": "image/webp"}]},
"sendBody": True, "contentType": "binaryData", "inputDataFieldName": "bild",
"options": {"timeout": 120000}},
credentials=CRED_WP, retryOnFail=True, maxTries=3, waitBetweenTries=3000)
link(BILD_BIN, "Bild in Mediathek")
node("Beitragsbild setzen", "n8n-nodes-base.httpRequest", 4.3, [6400, 40], {
"method": "POST",
"url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/posts/{{ $('Bildprompt bauen').first().json.post_id }}",
"authentication": "genericCredentialType", "genericAuthType": "httpBasicAuth",
"sendBody": True, "specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ featured_media: $json.id }) }}",
"sendHeaders": True, "specifyHeaders": "json", "jsonHeaders": '={"Content-Type":"application/json"}',
"options": {"timeout": 60000}},
credentials=CRED_WP, retryOnFail=True, maxTries=3, waitBetweenTries=3000)
link("Bild in Mediathek", "Beitragsbild setzen")
BILD_OK = code("Bild gemerkt", [6620, 40], """
const r = $('Bild als Datei').first().json;
const m = $('Bild in Mediathek').first().json;
const p = $input.first().json;
if (Number(p.featured_media) !== Number(m.id)) {
throw new Error('Beitragsbild wurde nicht gesetzt. Erwartet ' + m.id + ', WordPress meldet ' + p.featured_media);
}
return [{ json: { ...r, bild: 'neu erzeugt', media_id: m.id, bild_url: m.source_url,
featured_media: m.id, bild_kb: Math.round(r.bild_bytes / 1024) } }];
""")
link("Beitragsbild setzen", BILD_OK)
link(BILD_OK, "Pause Rate Limit")
# ---------------------------------------------------------------- Statustabelle # ---------------------------------------------------------------- Statustabelle
SAMMELN = code("Ergebnisse sammeln", [900, 80], r""" SAMMELN = code("Ergebnisse sammeln", [900, 80], r"""
const alle = $input.all().map(i => i.json); const alle = $input.all().map(i => i.json);
@@ -694,6 +827,9 @@ if (s.importiert.length) {
z.push(` Vorschau: ${r.vorschau_url}`); z.push(` Vorschau: ${r.vorschau_url}`);
z.push(` Bearbeiten: ${r.bearbeiten_url}`); z.push(` Bearbeiten: ${r.bearbeiten_url}`);
if (r.elementor) z.push(` Layout: ${r.elementor}`); if (r.elementor) z.push(` Layout: ${r.elementor}`);
if (r.bild === 'neu erzeugt') z.push(` Bild: neu erzeugt (${r.bild_kb} KB), ${r.bild_url}`);
else if (r.featured_media) z.push(` Bild: bereits vorhanden, nicht neu erzeugt`);
if (r.bild_hinweis) z.push(` Bild: ${r.bild_hinweis}`);
if (r.meta_warnung) z.push(` ACHTUNG: ${r.meta_warnung}`); if (r.meta_warnung) z.push(` ACHTUNG: ${r.meta_warnung}`);
if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`); if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`);
if (r.stil_hinweise && r.stil_hinweise.length) { if (r.stil_hinweise && r.stil_hinweise.length) {

4
build/workflow-ids.json Normal file
View File

@@ -0,0 +1,4 @@
{
"brief-vorbereiten": "9Ym8xpJwWw2OaUCv",
"pruefen-und-importieren": "OtwhR0yPz3W9Snui"
}

View File

@@ -0,0 +1,26 @@
{
"created": 1790064128,
"background": "opaque",
"output_format": "webp",
"quality": "medium",
"size": "1536x864",
"usage": {
"input_tokens": 220,
"input_tokens_details": {
"image_tokens": 0,
"text_tokens": 220
},
"output_tokens": 1078,
"output_tokens_details": {
"image_tokens": 1078,
"text_tokens": 0
},
"total_tokens": 1298
},
"data": [
{
"b64_json": "UklGRjbOCQBXRUJQVlA4TKV4CQAv/8XXAM1IbhtJkCQoq3f1yBv/Dc6o7Lm1...",
"generation_id": "7295a6c6-dcaf-4e78-8ba9-ed4513a89731"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

View File

@@ -9,7 +9,7 @@
-460, -460,
100 100
], ],
"id": "9d7b1805-8faf-4e0b-90d0-faedab546436", "id": "b9523f68-6d9f-4591-92c1-315c8d17e036",
"name": "Manuell starten" "name": "Manuell starten"
}, },
{ {
@@ -25,9 +25,9 @@
-460, -460,
280 280
], ],
"id": "ceaa8ccf-b4a1-4a0d-a173-b9ed63d44833", "id": "e145f97c-7c42-4ac0-bbfa-6791cdf1dc75",
"name": "Webhook Start", "name": "Webhook Start",
"webhookId": "ddaf87b7-a52e-44ad-8fc9-b0560671eab3" "webhookId": "51409459-2753-4b72-9442-44eb304e3970"
}, },
{ {
"parameters": { "parameters": {
@@ -35,91 +35,115 @@
"assignments": { "assignments": {
"assignments": [ "assignments": [
{ {
"id": "c37716f4-15ed-4a09-83f1-7841ff2a4efb", "id": "41e1ade4-2852-4dd1-a6ea-2bb3f9303615",
"name": "nw_base", "name": "nw_base",
"value": "https://app.neuronwriter.com/neuron-api/0.5/writer", "value": "https://app.neuronwriter.com/neuron-api/0.5/writer",
"type": "string" "type": "string"
}, },
{ {
"id": "a30eeffc-30a1-42fa-bb43-4662e503872b", "id": "919bffea-074d-4e88-b70a-811a1eac022c",
"name": "gitea_base", "name": "gitea_base",
"value": "http://gitea:3000", "value": "http://gitea:3000",
"type": "string" "type": "string"
}, },
{ {
"id": "db835d6c-31f9-4ac1-95ff-096bd685aa5e", "id": "0b37ba5d-6fca-4366-93f0-91f449cbac1d",
"name": "gitea_owner", "name": "gitea_owner",
"value": "lucas.orth", "value": "lucas.orth",
"type": "string" "type": "string"
}, },
{ {
"id": "d3f9c73d-bcf1-4ed7-8047-f9eb3968f403", "id": "1bb08030-e5c0-43d9-956a-dc7eaa123b1c",
"name": "gitea_repo", "name": "gitea_repo",
"value": "Skrift-Auto-Article", "value": "Skrift-Auto-Article",
"type": "string" "type": "string"
}, },
{ {
"id": "c6a32b0a-032f-498e-aea8-04587961668b", "id": "f025bd60-6036-4d86-ad10-de6e9797b4aa",
"name": "branch", "name": "branch",
"value": "main", "value": "main",
"type": "string" "type": "string"
}, },
{ {
"id": "c7e60ffd-132b-43b4-b3e3-7fd0f66b6546", "id": "70fe52cd-e118-428f-98d3-b6fe820415b4",
"name": "artikel_ordner", "name": "artikel_ordner",
"value": "artikel", "value": "artikel",
"type": "string" "type": "string"
}, },
{ {
"id": "d379f9fc-d1ba-4ac5-8494-1af6068cefca", "id": "dca9c723-2b38-4eb5-8a97-11e5261768af",
"name": "brief_ordner", "name": "brief_ordner",
"value": "briefe", "value": "briefe",
"type": "string" "type": "string"
}, },
{ {
"id": "46d52c78-8a51-4c5b-b893-ed79a53753a8", "id": "d1761018-b988-49c6-a8d2-b2daea919c48",
"name": "nachbessern_ordner", "name": "nachbessern_ordner",
"value": "nachbessern", "value": "nachbessern",
"type": "string" "type": "string"
}, },
{ {
"id": "ff44870f-d811-402a-afaf-61053ca2bb83", "id": "7ca8cd59-7043-4a0c-8045-ee87d6386c90",
"name": "status_pfad", "name": "status_pfad",
"value": "status-importe.json", "value": "status-importe.json",
"type": "string" "type": "string"
}, },
{ {
"id": "ea4a7974-642d-49df-b419-7cb2007893d6", "id": "88d49605-c288-4715-aa51-6263c7a173a3",
"name": "wp_base", "name": "wp_base",
"value": "https://skrift.de", "value": "https://skrift.de",
"type": "string" "type": "string"
}, },
{ {
"id": "354bf17a-52d5-4483-9cd6-68721a9c2a23", "id": "de3caff4-aa67-441f-b26c-16bdfed44eb9",
"name": "score_ziel", "name": "score_ziel",
"value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}", "value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}",
"type": "number" "type": "number"
}, },
{ {
"id": "388a5e43-5ab7-42f2-b3d2-438f72ff58e6", "id": "8047bfd6-776e-462f-887e-fbee0826d29d",
"name": "pause_zwischen_artikeln", "name": "pause_zwischen_artikeln",
"value": 5, "value": 5,
"type": "number" "type": "number"
}, },
{ {
"id": "9b2fa315-8213-4609-a2e5-be0936e24f20", "id": "6debc7f0-3db7-4f35-9f91-5126abefb520",
"name": "elementor_ausgabe", "name": "elementor_ausgabe",
"value": false, "value": false,
"type": "boolean" "type": "boolean"
}, },
{ {
"id": "ffca6f3b-e391-4ca7-adb8-49017472dc7b", "id": "3fd3df05-82f9-4b9f-9a2b-fbe70429b7d6",
"name": "bild_aktiv",
"value": true,
"type": "boolean"
},
{
"id": "378253b1-1ddf-4fbb-8d05-852cd3aed978",
"name": "bild_modell",
"value": "gpt-image-2",
"type": "string"
},
{
"id": "ef82071d-192f-4a81-a4f1-544a038a295f",
"name": "bild_groesse",
"value": "1536x864",
"type": "string"
},
{
"id": "cc33aca1-730e-45a2-9ec7-273755c8c47d",
"name": "bild_qualitaet",
"value": "medium",
"type": "string"
},
{
"id": "116daf1c-c81a-4ef6-8c5d-0c0c193c224c",
"name": "faq_ueberschrift", "name": "faq_ueberschrift",
"value": "Häufige Fragen", "value": "Häufige Fragen",
"type": "string" "type": "string"
}, },
{ {
"id": "2e8ae0e2-c6ea-4495-9fd5-1ad24c2b5dd8", "id": "0cbace80-605d-4dbc-8868-c63a6234fbf4",
"name": "benachrichtigung_an", "name": "benachrichtigung_an",
"value": "privat@lucas-orth.de", "value": "privat@lucas-orth.de",
"type": "string" "type": "string"
@@ -134,7 +158,7 @@
-240, -240,
190 190
], ],
"id": "d6943dd4-a080-471e-8d8d-281d2b49c91d", "id": "964a22b7-73b6-4318-8700-f0079d07a9d0",
"name": "Konfiguration" "name": "Konfiguration"
}, },
{ {
@@ -159,7 +183,7 @@
-20, -20,
190 190
], ],
"id": "21951ce8-26f3-4473-8051-4e232c228e89", "id": "6a9ab914-96ca-4fbe-95c8-625796575b2a",
"name": "Artikelordner lesen", "name": "Artikelordner lesen",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -182,7 +206,7 @@
200, 200,
190 190
], ],
"id": "79588bca-c88b-4f26-8344-1f1aaabbf7b7", "id": "b9f93c84-cfe5-41a1-a55d-341ef9adbd8e",
"name": "Artikeldateien filtern" "name": "Artikeldateien filtern"
}, },
{ {
@@ -196,7 +220,7 @@
}, },
"conditions": [ "conditions": [
{ {
"id": "a9e1753d-2159-4f99-aa0e-b9acaaf0b413", "id": "8d02e08e-0638-4437-92e0-6ac6abfffc56",
"leftValue": "={{ $json.leer === true }}", "leftValue": "={{ $json.leer === true }}",
"rightValue": "", "rightValue": "",
"operator": { "operator": {
@@ -217,7 +241,7 @@
420, 420,
190 190
], ],
"id": "7f5cd106-1cbe-4fd2-9a15-3d31ceef74c0", "id": "e0885743-c0cd-49d3-a821-164616cad729",
"name": "Artikel vorhanden?" "name": "Artikel vorhanden?"
}, },
{ {
@@ -228,7 +252,7 @@
640, 640,
40 40
], ],
"id": "66ea5319-b958-4cde-924a-ff101321bb3e", "id": "42e5ca07-fe2d-4f3d-bc8a-e8c6641a8d54",
"name": "Nichts zu tun" "name": "Nichts zu tun"
}, },
{ {
@@ -244,7 +268,7 @@
640, 640,
320 320
], ],
"id": "59e68a48-9e3a-499c-91a9-2d32ba8a6420", "id": "969401ee-c28c-4489-b8d9-c024bd9c2b5e",
"name": "Artikel nacheinander" "name": "Artikel nacheinander"
}, },
{ {
@@ -268,7 +292,7 @@
900, 900,
420 420
], ],
"id": "a3787725-913a-4b49-8177-b40a29322c78", "id": "566c2381-7296-4b46-b022-564a78fb6792",
"name": "Artikeldatei laden", "name": "Artikeldatei laden",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -292,7 +316,7 @@
900, 900,
640 640
], ],
"id": "25cb559a-6047-4766-bebd-f090d36d44a2", "id": "8a543e9c-c7b3-4ee4-a07c-37d13cf83f2f",
"name": "Fehler Artikeldatei", "name": "Fehler Artikeldatei",
"onError": "continueRegularOutput" "onError": "continueRegularOutput"
}, },
@@ -318,7 +342,7 @@
1120, 1120,
420 420
], ],
"id": "d3ea9547-61df-4d13-9dd4-6ffde02be40b", "id": "bb339e46-44b5-4213-9160-e8ceda5e7a89",
"name": "Brief laden", "name": "Brief laden",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -338,7 +362,7 @@
1340, 1340,
420 420
], ],
"id": "01b083a8-acdd-4964-80fc-26770a1dcecb", "id": "c4db72b1-1e0c-4c63-8dd6-18ce5ed9288f",
"name": "Artikel pruefen" "name": "Artikel pruefen"
}, },
{ {
@@ -368,7 +392,7 @@
1560, 1560,
420 420
], ],
"id": "939d7498-7c06-49a7-b96b-8ed0095edf94", "id": "22ffdabc-ac80-4570-bee2-337463b9e11f",
"name": "Score ermitteln", "name": "Score ermitteln",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -392,7 +416,7 @@
1560, 1560,
640 640
], ],
"id": "e3135c57-8738-4d82-914c-8813d3916dcc", "id": "06e19a47-f72e-49af-9275-09383308e218",
"name": "Fehler Score", "name": "Fehler Score",
"onError": "continueRegularOutput" "onError": "continueRegularOutput"
}, },
@@ -407,7 +431,7 @@
1780, 1780,
420 420
], ],
"id": "b1ea4d99-d731-4468-8525-b1af09e0acc3", "id": "29350571-0c58-4cbb-946c-d413c0d3cac3",
"name": "Score auswerten" "name": "Score auswerten"
}, },
{ {
@@ -421,7 +445,7 @@
}, },
"conditions": [ "conditions": [
{ {
"id": "851f792a-479c-4d4b-8801-15010f03be39", "id": "f05d0df3-ca20-4543-968b-5c2cb789774f",
"leftValue": "={{ $json.bestanden }}", "leftValue": "={{ $json.bestanden }}",
"rightValue": "", "rightValue": "",
"operator": { "operator": {
@@ -442,7 +466,7 @@
2000, 2000,
420 420
], ],
"id": "7187c432-a84a-4a61-b653-a864626f9c0b", "id": "8f793efc-bb8b-4994-a02a-9a4726fbcea8",
"name": "Score erreicht?" "name": "Score erreicht?"
}, },
{ {
@@ -456,7 +480,7 @@
2220, 2220,
640 640
], ],
"id": "202c13fa-f4eb-4f85-a1a9-97f953ac99dc", "id": "2ca96cd1-a927-4fb3-85ae-8012437609df",
"name": "Fehlende Begriffe ermitteln" "name": "Fehlende Begriffe ermitteln"
}, },
{ {
@@ -481,7 +505,7 @@
2440, 2440,
640 640
], ],
"id": "8cbf40d1-3067-461f-a394-796fb71981d0", "id": "4e8517b2-8f12-4bc0-b19b-386da85a6d3a",
"name": "Nachbesserung vorhanden?", "name": "Nachbesserung vorhanden?",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -501,7 +525,7 @@
2660, 2660,
640 640
], ],
"id": "b20fd046-39d8-48bf-941d-0866963437d0", "id": "3d8631ae-5f48-4a1b-a4c1-ebb27856dbb4",
"name": "Nachbesserung vorbereiten" "name": "Nachbesserung vorbereiten"
}, },
{ {
@@ -531,7 +555,7 @@
2880, 2880,
640 640
], ],
"id": "0197b51e-4b00-4fd9-acb0-86a5193ed692", "id": "36244e4c-bd55-4777-b0c0-93ab9c17d14c",
"name": "Nachbesserung committen", "name": "Nachbesserung committen",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -555,7 +579,7 @@
2880, 2880,
860 860
], ],
"id": "9c770168-67a5-4ec5-9498-1037533d808d", "id": "dc67d35e-4a83-428e-83be-7c4196161806",
"name": "Fehler Nachbesserung", "name": "Fehler Nachbesserung",
"onError": "continueRegularOutput" "onError": "continueRegularOutput"
}, },
@@ -570,7 +594,7 @@
3100, 3100,
640 640
], ],
"id": "0a987ebf-e478-48ce-b721-598e2759019a", "id": "eaf7de10-8b7a-4020-a5e9-5b58af6b95e8",
"name": "Nachbesserung gemerkt" "name": "Nachbesserung gemerkt"
}, },
{ {
@@ -600,7 +624,7 @@
2220, 2220,
240 240
], ],
"id": "4ee193df-d21a-4a4b-a78e-ce967ce6fabf", "id": "ed8e731a-4142-46ac-830e-cddcd44a9987",
"name": "Revision zu NeuronWriter", "name": "Revision zu NeuronWriter",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -624,7 +648,7 @@
2220, 2220,
60 60
], ],
"id": "9fa33651-10b2-4068-a5db-0c1705a394a6", "id": "9ed00d79-a091-406f-afa9-e6aca9d773ac",
"name": "Fehler Revision", "name": "Fehler Revision",
"onError": "continueRegularOutput" "onError": "continueRegularOutput"
}, },
@@ -650,7 +674,7 @@
2440, 2440,
240 240
], ],
"id": "bd2f2ec2-0ce6-4491-86cf-342aeecbbeed", "id": "641f8e00-0809-41bb-a43a-2ab3077236cc",
"name": "Beitrag vorhanden?", "name": "Beitrag vorhanden?",
"credentials": { "credentials": {
"httpBasicAuth": { "httpBasicAuth": {
@@ -684,7 +708,7 @@
2660, 2660,
240 240
], ],
"id": "caa0d4e8-3bca-4fd1-90e4-824837ca8c01", "id": "34ba9cb0-fab5-4344-bc9c-699239d01c99",
"name": "Kategorie suchen", "name": "Kategorie suchen",
"credentials": { "credentials": {
"httpBasicAuth": { "httpBasicAuth": {
@@ -707,7 +731,7 @@
2880, 2880,
240 240
], ],
"id": "cd297bed-663d-47f3-ae1d-7442e7251e69", "id": "e900cca9-39fe-4806-9588-91d1de3ba9e5",
"name": "Kategorie pruefen" "name": "Kategorie pruefen"
}, },
{ {
@@ -721,7 +745,7 @@
}, },
"conditions": [ "conditions": [
{ {
"id": "f05870e6-38ac-4e5e-8b77-c75437e2d520", "id": "59dc46d5-6d0d-4796-bf86-95d715ea05d8",
"leftValue": "={{ $json.kategorie_fehlt }}", "leftValue": "={{ $json.kategorie_fehlt }}",
"rightValue": "", "rightValue": "",
"operator": { "operator": {
@@ -742,7 +766,7 @@
3100, 3100,
240 240
], ],
"id": "01a425ce-96d8-4a77-b797-946bc628ad2f", "id": "0c0a8bc7-cbac-4e70-a7f6-e94c38ffa837",
"name": "Kategorie anlegen?" "name": "Kategorie anlegen?"
}, },
{ {
@@ -772,7 +796,7 @@
3320, 3320,
140 140
], ],
"id": "ed15de50-93a9-4413-8729-3aa6c57cac6a", "id": "b64baa11-054d-4830-a56c-20c931ce3655",
"name": "Kategorie anlegen", "name": "Kategorie anlegen",
"credentials": { "credentials": {
"httpBasicAuth": { "httpBasicAuth": {
@@ -795,7 +819,7 @@
3540, 3540,
240 240
], ],
"id": "1dad3b28-189b-430a-ac32-c1724c816f16", "id": "cfb9aeda-8ebe-4895-a463-6f621d22020e",
"name": "Kategorie festlegen" "name": "Kategorie festlegen"
}, },
{ {
@@ -809,7 +833,7 @@
3760, 3760,
240 240
], ],
"id": "b64f3c2d-f4a3-4e61-abd0-4b2b5fa42661", "id": "fa7f4b40-e2fd-4e02-832c-2ff0da8527b3",
"name": "WordPress-Body bauen" "name": "WordPress-Body bauen"
}, },
{ {
@@ -839,7 +863,7 @@
3980, 3980,
240 240
], ],
"id": "d1d7fd80-caf7-46cf-9ded-9aa3b97b9a15", "id": "c22b7697-ac02-45eb-aa2c-7fe87aeb46ff",
"name": "Beitrag anlegen oder aktualisieren", "name": "Beitrag anlegen oder aktualisieren",
"credentials": { "credentials": {
"httpBasicAuth": { "httpBasicAuth": {
@@ -854,7 +878,7 @@
{ {
"parameters": { "parameters": {
"mode": "runOnceForAllItems", "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, 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 !== '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"
}, },
"type": "n8n-nodes-base.code", "type": "n8n-nodes-base.code",
"typeVersion": 2, "typeVersion": 2,
@@ -862,7 +886,7 @@
4200, 4200,
240 240
], ],
"id": "a16f780a-5f7e-4581-b0f5-7f55464e87d1", "id": "9d81b408-d848-480d-99d7-cf09bf728877",
"name": "Ergebnis pruefen" "name": "Ergebnis pruefen"
}, },
{ {
@@ -893,7 +917,7 @@
4420, 4420,
240 240
], ],
"id": "f1b48e11-b8c6-4842-913c-5a8a43b042d5", "id": "f4ad279d-9ee5-4d0a-9e82-151450889d92",
"name": "Cache leeren", "name": "Cache leeren",
"credentials": { "credentials": {
"httpBasicAuth": { "httpBasicAuth": {
@@ -913,7 +937,7 @@
4640, 4640,
240 240
], ],
"id": "6708b72c-7577-4bb5-b824-652648fff48c", "id": "18f0e4ac-8ea3-450f-8d49-df200d7ddaaf",
"name": "Importergebnis merken" "name": "Importergebnis merken"
}, },
{ {
@@ -927,9 +951,288 @@
4860, 4860,
420 420
], ],
"id": "2297ffc3-9c1a-48a2-ba7a-20f394af02f0", "id": "9613876f-73d4-4baa-99ad-31d8a2714e4c",
"name": "Pause Rate Limit", "name": "Pause Rate Limit",
"webhookId": "cf34062f-fc9c-49de-b651-9a8d020b9d57" "webhookId": "d8b56a3f-dffc-4f1e-94a6-a3864185fa09"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "433d059a-e005-4644-9f14-64cfe735e4b6",
"leftValue": "={{ $('Konfiguration').first().json.bild_aktiv === true && !Number($json.featured_media) }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
4860,
240
],
"id": "61319cb6-8092-4daa-a837-fb7008994b87",
"name": "Beitragsbild fehlt?"
},
{
"parameters": {
"method": "GET",
"url": "={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/artikel-index.json?ref={{ $('Konfiguration').first().json.branch }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {
"response": {
"response": {
"neverError": true,
"fullResponse": true
}
},
"timeout": 60000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
5080,
120
],
"id": "6c2b9f0c-e425-4f79-a0c8-de5b56da7ebf",
"name": "Bilddaten laden",
"credentials": {
"httpHeaderAuth": {
"id": "VOD4JM5zwpSC8wAr",
"name": "Gitea Skrift Auto Article"
}
},
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 3000
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "\n// Prompt nach ratgeber/bildstil.md, Farbe und Motiv aus artikel-index.json.\nconst r = $('Importergebnis merken').first().json;\nconst antwort = $input.first().json;\nconst K = (x) => (x && x.body !== undefined) ? x.body : ((x || {}).data);\nconst konf = $('Konfiguration').first().json;\n\nlet zeile = null;\nconst kb = K(antwort);\nif (antwort.statusCode === 200 && kb && kb.content) {\n try {\n const tab = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8'));\n zeile = (Array.isArray(tab) ? tab : []).find(z => z.slug === r.slug) || null;\n } catch (e) { zeile = null; }\n}\nif (!zeile || !zeile.bild_farbe || !zeile.bildmotiv) {\n return [{ json: { ...r, bild_prompt: null,\n bild_hinweis: 'Kein bild_farbe oder bildmotiv in artikel-index.json, Bild uebersprungen.' } }];\n}\n\nconst FARBNAME = { '#7B96C2': 'muted light ink blue', '#B6997F': 'muted sepia brown',\n '#8C9A82': 'muted sage green', '#C47B5E': 'muted terracotta red' };\nconst farbe = String(zeile.bild_farbe).toUpperCase();\nconst name = FARBNAME[farbe] ? ` (${FARBNAME[farbe]}, not more saturated)` : '';\nconst leer = /price tag/i.test(zeile.bildmotiv) ? ', the price tag is completely empty' : '';\n\nconst prompt = [\n 'Hand-drawn doodle spot illustration, flat editorial icon style.',\n `Solid flat background color ${farbe}${name}, completely uniform, no texture, no gradient, no shadow, no vignette.`,\n 'In the center one simple object, drawn with thick uneven black marker outlines (#1A1A1A), slightly wobbly hand-drawn lines like a felt-tip pen, rounded line ends.',\n 'Clean closed outlines, no stray marks, no hatching and no small dashes inside the shapes.',\n 'Object fills are flat off-white cream (#F9F7F5), no shading, no highlights.',\n `Motif: ${zeile.bildmotiv}${leer}. Shown straight from the front, not tilted.`,\n 'Minimal and iconic, lots of empty space, the object takes about 35 percent of the image height.',\n 'No text, no letters, no numbers, no currency symbols, no logo, no border, no frame, no rounded corners, no people.',\n].join(' ');\n\nreturn [{ json: { ...r, bild_prompt: prompt, bild_titel: zeile.titel || r.slug,\n openai_body: JSON.stringify({ model: konf.bild_modell, prompt, size: konf.bild_groesse,\n n: 1, output_format: 'webp', quality: konf.bild_qualitaet }) } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5300,
120
],
"id": "f602842b-c4ed-4d15-8e85-72ce9d329fa3",
"name": "Bildprompt bauen"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "a9afefa8-8157-43f5-91b1-f7446c5364bd",
"leftValue": "={{ !!$json.bild_prompt }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
5520,
120
],
"id": "14894cfa-8e68-4edb-9d17-f2671780aa74",
"name": "Bildprompt vorhanden?"
},
{
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/images/generations",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {
"response": {
"response": {
"neverError": false
}
},
"timeout": 180000
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.openai_body }}",
"sendHeaders": true,
"specifyHeaders": "json",
"jsonHeaders": "={\"Content-Type\":\"application/json\"}"
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
5740,
40
],
"id": "31ab60fc-6031-439a-8402-74dd6d0d9437",
"name": "Bild erzeugen",
"credentials": {
"httpHeaderAuth": {
"id": "03NmFB2C9IFRXIu8",
"name": "OpenAI Bilder"
}
},
"onError": "continueErrorOutput",
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 3000
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "\nconst r = $('Bildprompt bauen').first().json;\nconst e = $input.first().json;\nreturn [{ json: { ...r, openai_body: undefined,\n bild_hinweis: 'Bild nicht erzeugt, Artikel ist trotzdem importiert. Haeufige Ursachen: '\n + 'Guthaben leer, Organisation fuer Bildmodelle nicht verifiziert, Prompt abgelehnt. Antwort: '\n + JSON.stringify(e).slice(0, 300) } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5740,
260
],
"id": "6efa2c23-95e2-4a94-873b-b509c10ab395",
"name": "Fehler Bild",
"onError": "continueRegularOutput"
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "\nconst r = $('Bildprompt bauen').first().json;\nconst a = $input.first().json;\nconst b64 = a && a.data && a.data[0] && a.data[0].b64_json;\nif (!b64) throw new Error('OpenAI lieferte kein data[0].b64_json. Antwort: ' + JSON.stringify(a).slice(0, 300));\nconst puffer = Buffer.from(b64, 'base64');\nconst datei = await this.helpers.prepareBinaryData(puffer, `${r.slug}.webp`, 'image/webp');\nconst { openai_body, ...rest } = r;\nreturn [{ json: { ...rest, bild_bytes: puffer.length }, binary: { bild: datei } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5960,
40
],
"id": "491b23b7-4679-429d-9745-47292b0154ca",
"name": "Bild als Datei"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/media?alt_text={{ encodeURIComponent('Illustration zum Artikel: ' + $json.bild_titel) }}&title={{ encodeURIComponent($json.bild_titel) }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Disposition",
"value": "=attachment; filename=\"{{ $json.slug }}.webp\""
},
{
"name": "Content-Type",
"value": "image/webp"
}
]
},
"sendBody": true,
"contentType": "binaryData",
"inputDataFieldName": "bild",
"options": {
"timeout": 120000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
6180,
40
],
"id": "b5e1265a-6b87-4c3f-840c-6cca67628550",
"name": "Bild in Mediathek",
"credentials": {
"httpBasicAuth": {
"id": "WIN05TZgM0K5VOGy",
"name": "WP skrift.de Redakteur"
}
},
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 3000
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/posts/{{ $('Bildprompt bauen').first().json.post_id }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ featured_media: $json.id }) }}",
"sendHeaders": true,
"specifyHeaders": "json",
"jsonHeaders": "={\"Content-Type\":\"application/json\"}",
"options": {
"timeout": 60000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
6400,
40
],
"id": "21f00fcc-1449-4cb7-96b2-6cf35a4d8056",
"name": "Beitragsbild setzen",
"credentials": {
"httpBasicAuth": {
"id": "WIN05TZgM0K5VOGy",
"name": "WP skrift.de Redakteur"
}
},
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 3000
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "\nconst r = $('Bild als Datei').first().json;\nconst m = $('Bild in Mediathek').first().json;\nconst p = $input.first().json;\nif (Number(p.featured_media) !== Number(m.id)) {\n throw new Error('Beitragsbild wurde nicht gesetzt. Erwartet ' + m.id + ', WordPress meldet ' + p.featured_media);\n}\nreturn [{ json: { ...r, bild: 'neu erzeugt', media_id: m.id, bild_url: m.source_url,\n featured_media: m.id, bild_kb: Math.round(r.bild_bytes / 1024) } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
6620,
40
],
"id": "78f4059e-6003-43b0-80dc-fad15546eff3",
"name": "Bild gemerkt"
}, },
{ {
"parameters": { "parameters": {
@@ -942,7 +1245,7 @@
900, 900,
80 80
], ],
"id": "056c1cf1-f7ba-48dc-a1c2-3d22d70e4550", "id": "7241567b-ebff-4ddf-a4e3-f40ef3787b07",
"name": "Ergebnisse sammeln" "name": "Ergebnisse sammeln"
}, },
{ {
@@ -967,7 +1270,7 @@
1120, 1120,
80 80
], ],
"id": "88815af5-2481-4518-adf6-d50ec1cb28c8", "id": "1b6c1a04-caf9-4bb8-ace9-b050fe4710bb",
"name": "Statustabelle lesen", "name": "Statustabelle lesen",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -987,7 +1290,7 @@
1340, 1340,
80 80
], ],
"id": "c094f8dd-9756-47b6-876b-057507091af2", "id": "5aecb5c3-e0d3-45df-b33b-37c1d13c8c08",
"name": "Statustabelle fortschreiben" "name": "Statustabelle fortschreiben"
}, },
{ {
@@ -1001,7 +1304,7 @@
}, },
"conditions": [ "conditions": [
{ {
"id": "51ddce38-e3ef-46d4-8922-b51f79ff011a", "id": "82bf79b1-edca-437a-88cd-913d7f3fd9c4",
"leftValue": "={{ $json.ueberspringen }}", "leftValue": "={{ $json.ueberspringen }}",
"rightValue": "", "rightValue": "",
"operator": { "operator": {
@@ -1022,7 +1325,7 @@
1560, 1560,
80 80
], ],
"id": "480901ea-3057-402a-8828-c449d4127118", "id": "049968c9-10ac-4616-b768-832074799f58",
"name": "Gab es Importe?" "name": "Gab es Importe?"
}, },
{ {
@@ -1052,7 +1355,7 @@
1780, 1780,
160 160
], ],
"id": "461196bc-73d5-49a9-be96-9da3866479dc", "id": "e1193d3f-fed0-4f31-a90c-c333a5a2bdd5",
"name": "Statustabelle schreiben", "name": "Statustabelle schreiben",
"credentials": { "credentials": {
"httpHeaderAuth": { "httpHeaderAuth": {
@@ -1076,14 +1379,14 @@
1780, 1780,
340 340
], ],
"id": "a754dbe7-60b0-43c2-80bd-ff88df4eac50", "id": "6df2408f-6d6c-4a04-8641-3a4a738268c2",
"name": "Fehler Statustabelle", "name": "Fehler Statustabelle",
"onError": "continueRegularOutput" "onError": "continueRegularOutput"
}, },
{ {
"parameters": { "parameters": {
"mode": "runOnceForAllItems", "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.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} | 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"
}, },
"type": "n8n-nodes-base.code", "type": "n8n-nodes-base.code",
"typeVersion": 2, "typeVersion": 2,
@@ -1091,7 +1394,7 @@
2000, 2000,
80 80
], ],
"id": "86c65a05-ec2b-49fd-9134-0538d0be1065", "id": "3384fccd-d896-4c44-9c89-f91946e5d986",
"name": "Meldung formulieren" "name": "Meldung formulieren"
}, },
{ {
@@ -1109,7 +1412,7 @@
2220, 2220,
80 80
], ],
"id": "0a421a3c-7f3a-4cc0-b3ca-edd79e6a4584", "id": "d2da0c0a-1998-405c-b585-7c8d7bbc834c",
"name": "Benachrichtigung", "name": "Benachrichtigung",
"disabled": true, "disabled": true,
"notes": "SMTP-Credential zuweisen, Absender eintragen, dann aktivieren." "notes": "SMTP-Credential zuweisen, Absender eintragen, dann aktivieren."
@@ -1491,17 +1794,6 @@
] ]
] ]
}, },
"Importergebnis merken": {
"main": [
[
{
"node": "Pause Rate Limit",
"type": "main",
"index": 0
}
]
]
},
"Nachbesserung gemerkt": { "Nachbesserung gemerkt": {
"main": [ "main": [
[ [
@@ -1557,6 +1849,148 @@
] ]
] ]
}, },
"Importergebnis merken": {
"main": [
[
{
"node": "Beitragsbild fehlt?",
"type": "main",
"index": 0
}
]
]
},
"Beitragsbild fehlt?": {
"main": [
[
{
"node": "Bilddaten laden",
"type": "main",
"index": 0
}
],
[
{
"node": "Pause Rate Limit",
"type": "main",
"index": 0
}
]
]
},
"Bilddaten laden": {
"main": [
[
{
"node": "Bildprompt bauen",
"type": "main",
"index": 0
}
]
]
},
"Bildprompt bauen": {
"main": [
[
{
"node": "Bildprompt vorhanden?",
"type": "main",
"index": 0
}
]
]
},
"Bildprompt vorhanden?": {
"main": [
[
{
"node": "Bild erzeugen",
"type": "main",
"index": 0
}
],
[
{
"node": "Pause Rate Limit",
"type": "main",
"index": 0
}
]
]
},
"Bild erzeugen": {
"main": [
[
{
"node": "Bild als Datei",
"type": "main",
"index": 0
}
],
[
{
"node": "Fehler Bild",
"type": "main",
"index": 0
}
]
]
},
"Fehler Bild": {
"main": [
[
{
"node": "Pause Rate Limit",
"type": "main",
"index": 0
}
]
]
},
"Bild als Datei": {
"main": [
[
{
"node": "Bild in Mediathek",
"type": "main",
"index": 0
}
]
]
},
"Bild in Mediathek": {
"main": [
[
{
"node": "Beitragsbild setzen",
"type": "main",
"index": 0
}
]
]
},
"Beitragsbild setzen": {
"main": [
[
{
"node": "Bild gemerkt",
"type": "main",
"index": 0
}
]
]
},
"Bild gemerkt": {
"main": [
[
{
"node": "Pause Rate Limit",
"type": "main",
"index": 0
}
]
]
},
"Ergebnisse sammeln": { "Ergebnisse sammeln": {
"main": [ "main": [
[ [