Alle 30 Artikel, WF2 veroeffentlicht direkt und schuetzt veroeffentlichte Beitraege
This commit is contained in:
12
build/artikel_schreiben.py
Normal file
12
build/artikel_schreiben.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Schreibt einen Artikel aus einer Python-Datei mit dict A und prueft ihn. Aufruf: python3 build/artikel_schreiben.py datei.py"""
|
||||
import json, subprocess, sys
|
||||
ns = {}
|
||||
exec(open(sys.argv[1], encoding='utf-8').read(), ns)
|
||||
a = ns['A']
|
||||
a.setdefault('stand_datum', '2026-09-22')
|
||||
pfad = f"artikel/artikel-{a['slug']}.json"
|
||||
json.dump(a, open(pfad, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
|
||||
print('SEO', len(a['seo_titel']), len(a['seo_beschreibung']))
|
||||
subprocess.run(['python3', 'build/pruef_lokal.py', pfad])
|
||||
subprocess.run(['python3', 'build/nw_score.py', pfad])
|
||||
@@ -71,6 +71,10 @@ KONF = node("Konfiguration", "n8n-nodes-base.set", 3.4, [-240, 190], {
|
||||
{"id": nid(), "name": "wp_base", "value": "https://skrift.de", "type": "string"},
|
||||
{"id": nid(), "name": "score_ziel", "value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}", "type": "number"},
|
||||
{"id": nid(), "name": "pause_zwischen_artikeln", "value": 5, "type": "number"},
|
||||
# Zielstatus fuer neue Beitraege und fuer Entwuerfe: publish (Vorgabe seit 22.09.2026) oder draft
|
||||
{"id": nid(), "name": "ziel_status", "value": "={{ $json.body && ['draft','publish'].includes($json.body.status) ? $json.body.status : 'publish' }}", "type": "string"},
|
||||
# Veroeffentlichte Beitraege werden nie angefasst, ausser ausdruecklich per Webhook erlaubt
|
||||
{"id": nid(), "name": "veroeffentlichte_ueberschreiben", "value": "={{ !!($json.body && $json.body.veroeffentlichte_ueberschreiben === true) }}", "type": "boolean"},
|
||||
{"id": nid(), "name": "elementor_ausgabe", "value": False, "type": "boolean"},
|
||||
{"id": nid(), "name": "bild_aktiv", "value": True, "type": "boolean"},
|
||||
{"id": nid(), "name": "bild_modell", "value": "gpt-image-2", "type": "string"},
|
||||
@@ -383,7 +387,30 @@ KAT_SUCHE = http("Kategorie suchen", [2660, 240], "GET",
|
||||
"={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/categories?search={{ encodeURIComponent($('Score auswerten').first().json.artikel.kategorie) }}&per_page=100",
|
||||
CRED_WP, auth_typ="httpBasicAuth", error_out=False, retry=True,
|
||||
extra={"options": {"response": {"response": {"neverError": True, "fullResponse": True}}, "timeout": 60000}})
|
||||
link(DUP, KAT_SUCHE)
|
||||
|
||||
# Schutz: Ein bereits veroeffentlichter Beitrag wird weder ueberschrieben noch auf Entwurf gesetzt.
|
||||
SCHUTZ = code("Veroeffentlicht pruefen", [2440, 440], r"""
|
||||
const s = $('Score auswerten').first().json;
|
||||
const dup = $input.first().json;
|
||||
const K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);
|
||||
const kd = K(dup);
|
||||
const treffer = (dup.statusCode === 200 && Array.isArray(kd)) ? kd.find(p => p.slug === s.slug) : null;
|
||||
const konf = $('Konfiguration').first().json;
|
||||
const geschuetzt = !!treffer && treffer.status !== 'draft' && konf.veroeffentlichte_ueberschreiben !== true;
|
||||
return [{ json: { slug: s.slug, score: s.score, geschuetzt,
|
||||
post_id: treffer ? treffer.id : null, alter_status: treffer ? treffer.status : null } }];
|
||||
""")
|
||||
link(DUP, SCHUTZ)
|
||||
SCHUTZ_IF = iff("Schon veroeffentlicht?", [2660, 440], "={{ $json.geschuetzt }}", "boolean", "true")
|
||||
link(SCHUTZ, SCHUTZ_IF)
|
||||
SCHUTZ_NOTIZ = code("Uebersprungen merken", [2880, 540], """
|
||||
const v = $input.first().json;
|
||||
return [{ json: { slug: v.slug, ergebnis: 'uebersprungen', score: v.score, post_id: v.post_id,
|
||||
status: v.alter_status,
|
||||
meldung: 'Beitrag ' + v.post_id + ' ist bereits ' + v.alter_status + ' und bleibt unveraendert.' } }];
|
||||
""")
|
||||
link(SCHUTZ_IF, SCHUTZ_NOTIZ, out=0)
|
||||
link(SCHUTZ_IF, KAT_SUCHE, out=1)
|
||||
|
||||
KAT_PRUEF = code("Kategorie pruefen", [2880, 240], """
|
||||
const s = $('Score auswerten').first().json;
|
||||
@@ -429,6 +456,7 @@ WP_VOR = code("WordPress-Body bauen", [3760, 240], r"""
|
||||
const s = $input.first().json;
|
||||
const dup = $('Beitrag vorhanden?').first().json;
|
||||
const a = s.artikel;
|
||||
const zielStatus = $('Konfiguration').first().json.ziel_status === 'draft' ? 'draft' : 'publish';
|
||||
|
||||
// Duplikatspruefung: exakter Slug-Treffer entscheidet ueber anlegen oder aktualisieren.
|
||||
// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.
|
||||
@@ -522,7 +550,7 @@ const body = {
|
||||
title: a.titel,
|
||||
slug: s.slug,
|
||||
content: a.content_html,
|
||||
status: 'draft', // harte Vorgabe: nie automatisch veroeffentlichen
|
||||
status: zielStatus, // publish seit 22.09.2026, per Webhook {"status":"draft"} umschaltbar
|
||||
categories: [s.kategorie_id],
|
||||
meta,
|
||||
};
|
||||
@@ -536,6 +564,7 @@ return [{ json: {
|
||||
wp_url: treffer
|
||||
? `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts/${treffer.id}`
|
||||
: `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts`,
|
||||
ziel_status: zielStatus,
|
||||
wp_body: JSON.stringify(body),
|
||||
}}];
|
||||
""")
|
||||
@@ -555,9 +584,9 @@ if (!p.id) {
|
||||
throw new Error('WordPress lieferte keine Beitrags-ID zurueck. Antwort: '
|
||||
+ JSON.stringify(p).slice(0, 400));
|
||||
}
|
||||
if (p.status !== 'draft') {
|
||||
if (p.status !== v.ziel_status) {
|
||||
throw new Error('Beitrag ' + p.id + ' hat Status "' + p.status
|
||||
+ '" statt "draft". Der Workflow darf nie veroeffentlichen. Bitte pruefen.');
|
||||
+ '" statt "' + v.ziel_status + '". Bitte Rechte des Benutzers pruefen.');
|
||||
}
|
||||
|
||||
// Gegenprobe: sind die Meta-Felder wirklich angekommen?
|
||||
@@ -612,7 +641,7 @@ link(CACHE, CACHE_NOTIZ)
|
||||
PAUSE = node("Pause Rate Limit", "n8n-nodes-base.wait", 1.1, [4860, 420],
|
||||
{"amount": "={{ $('Konfiguration').first().json.pause_zwischen_artikeln }}", "unit": "seconds"},
|
||||
webhookId=str(uuid.uuid4()))
|
||||
for q in [NACH_OK, NACH_FEHLER, FEHLER_ART, FEHLER_EVAL]:
|
||||
for q in [NACH_OK, NACH_FEHLER, FEHLER_ART, FEHLER_EVAL, SCHUTZ_NOTIZ]:
|
||||
link(q, PAUSE)
|
||||
link(PAUSE, LOOP)
|
||||
|
||||
@@ -750,6 +779,7 @@ const alle = $input.all().map(i => i.json);
|
||||
const importiert = alle.filter(r => r.ergebnis === 'importiert');
|
||||
const nachbessern = alle.filter(r => r.ergebnis === 'nachbessern');
|
||||
const fehler = alle.filter(r => r.ergebnis === 'fehler');
|
||||
const uebersprungen = alle.filter(r => r.ergebnis === 'uebersprungen');
|
||||
|
||||
const zeilen = importiert.map(r => ({
|
||||
slug: r.slug, score: r.score, datum: new Date().toISOString(),
|
||||
@@ -757,11 +787,11 @@ const zeilen = importiert.map(r => ({
|
||||
aktion: r.aktion, kategorie: r.kategorie,
|
||||
}));
|
||||
|
||||
return [{ json: { alle, importiert, nachbessern, fehler,
|
||||
return [{ json: { alle, importiert, nachbessern, fehler, uebersprungen,
|
||||
neue_zeilen: zeilen,
|
||||
anzahl_importiert: importiert.length,
|
||||
anzahl_nachbessern: nachbessern.length,
|
||||
anzahl_fehler: fehler.length }}];
|
||||
anzahl_fehler: fehler.length, anzahl_uebersprungen: uebersprungen.length }}];
|
||||
""")
|
||||
link(LOOP, SAMMELN, out=0)
|
||||
|
||||
@@ -820,13 +850,13 @@ link(ST_SCHREIB, ST_FEHLER, out=1)
|
||||
MAIL = code("Meldung formulieren", [2000, 80], r"""
|
||||
const s = $('Ergebnisse sammeln').first().json;
|
||||
const z = [];
|
||||
z.push(`Importiert: ${s.anzahl_importiert} | Nachbessern: ${s.anzahl_nachbessern} | Fehler: ${s.anzahl_fehler}`);
|
||||
z.push(`Importiert: ${s.anzahl_importiert} | Unveraendert (veroeffentlicht): ${s.anzahl_uebersprungen} | Nachbessern: ${s.anzahl_nachbessern} | Fehler: ${s.anzahl_fehler}`);
|
||||
z.push('');
|
||||
|
||||
if (s.importiert.length) {
|
||||
z.push('Als Entwurf in WordPress:');
|
||||
z.push('In WordPress (Status je Beitrag):');
|
||||
s.importiert.forEach(r => {
|
||||
z.push(` ${r.slug} (Score ${r.score}, ${r.aktion})`);
|
||||
z.push(` ${r.slug} (Score ${r.score}, ${r.aktion}, ${r.status})`);
|
||||
z.push(` Vorschau: ${r.vorschau_url}`);
|
||||
z.push(` Bearbeiten: ${r.bearbeiten_url}`);
|
||||
if (r.elementor) z.push(` Layout: ${r.elementor}`);
|
||||
@@ -867,10 +897,11 @@ if (s.fehler.length) {
|
||||
s.fehler.forEach(r => z.push(` ${r.slug || '?'} [${r.stufe}]: ${r.meldung}`));
|
||||
z.push('');
|
||||
}
|
||||
z.push('Kein Beitrag wurde veroeffentlicht. Alle Importe stehen auf Entwurf.');
|
||||
if (s.uebersprungen.length) { z.push('Bereits veroeffentlicht, nicht angefasst:'); s.uebersprungen.forEach(r => z.push(` ${r.slug} (Beitrag ${r.post_id})`)); z.push(''); }
|
||||
z.push('Veroeffentlichte Beitraege werden nie ueberschrieben oder auf Entwurf gesetzt.');
|
||||
|
||||
return [{ json: {
|
||||
betreff: `Skrift Ratgeber: ${s.anzahl_importiert} Entwurf/Entwuerfe`
|
||||
betreff: `Skrift Ratgeber: ${s.anzahl_importiert} importiert`
|
||||
+ (s.anzahl_nachbessern ? `, ${s.anzahl_nachbessern} zum Nachbessern` : '')
|
||||
+ (s.anzahl_fehler ? `, ${s.anzahl_fehler} Fehler` : ''),
|
||||
text: z.join('\n') }}];
|
||||
|
||||
@@ -28,7 +28,7 @@ if re.search('[–—]', c + a['kurzantwort'] + faqt): probleme.append("Gedanken
|
||||
if re.search(r'\bich\b', plain, re.I): probleme.append("Ich-Form")
|
||||
for s in re.findall(r'\[ratgeber_link slug="([^"]+)"', c):
|
||||
if s not in slugs: probleme.append(f"unbekannter Slug {s}")
|
||||
if a['slug'] in ('kaltakquise-per-brief',) and 'Keine Rechtsberatung: Dieser Beitrag gibt den Stand unserer Praxiserfahrung wieder' not in plain:
|
||||
if a['slug'] in ('kaltakquise-per-brief', 'serienbriefe-unterschreiben', 'dsgvo-briefwerbung') and 'Keine Rechtsberatung: Dieser Beitrag gibt den Stand unserer Praxiserfahrung wieder' not in plain:
|
||||
probleme.append("Disclaimer fehlt")
|
||||
if os.path.exists(bp):
|
||||
b = json.load(open(bp, encoding='utf-8'))
|
||||
|
||||
19
build/wf2_deploy.py
Normal file
19
build/wf2_deploy.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Spielt workflows/pruefen-und-importieren.json in n8n ein, Credentials und Webhook-IDs bleiben erhalten."""
|
||||
import json, os, urllib.request
|
||||
w = json.load(open('workflows/pruefen-und-importieren.json', encoding='utf-8'))
|
||||
wid_ = json.load(open('build/workflow-ids.json'))['pruefen-und-importieren']
|
||||
H = {'X-N8N-API-KEY': os.environ['N8N_API_KEY'], 'Content-Type': 'application/json'}
|
||||
base = os.environ['N8N_BASE_URL'].rstrip('/') + '/api/v1/workflows/' + wid_
|
||||
alt = json.load(urllib.request.urlopen(urllib.request.Request(base, headers=H)))
|
||||
cred = {n['name']: n.get('credentials') for n in alt['nodes'] if n.get('credentials')}
|
||||
hook = {n['name']: n.get('webhookId') for n in alt['nodes'] if n.get('webhookId')}
|
||||
for n in w['nodes']:
|
||||
if n['name'] in cred: n['credentials'] = cred[n['name']]
|
||||
if n['name'] in hook: n['webhookId'] = hook[n['name']]
|
||||
erlaubt = ('executionOrder', 'saveManualExecutions', 'callerPolicy', 'errorWorkflow', 'timezone',
|
||||
'saveDataErrorExecution', 'saveDataSuccessExecution', 'saveExecutionProgress', 'executionTimeout')
|
||||
body = {'name': alt['name'], 'nodes': w['nodes'], 'connections': w['connections'],
|
||||
'settings': {k: v for k, v in (alt.get('settings') or {}).items() if k in erlaubt}}
|
||||
d = json.load(urllib.request.urlopen(urllib.request.Request(base, data=json.dumps(body).encode(), headers=H, method='PUT')))
|
||||
print('aktualisiert', d['id'], 'aktiv' if d['active'] else 'inaktiv', len(d['nodes']), 'Nodes')
|
||||
Reference in New Issue
Block a user