Initial commit: WordPress News Import System

This commit is contained in:
Jörg Lohrer 2025-10-01 06:16:10 +02:00
commit 5f923d8ece
8 changed files with 948 additions and 0 deletions

4
.env.example Normal file
View file

@ -0,0 +1,4 @@
# WordPress API Credentials
WORDPRESS_URL=https://news.rpi-virtuell.de
WORDPRESS_USERNAME=your_username
WORDPRESS_APP_PASSWORD=UIVI 4Tdy oojL 9iZG g3X2 iAn5

28
.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Environment variables
.env
.env.local
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/
ENV/
.venv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
# OS
.DS_Store
Thumbs.db

281
README.md Normal file
View file

@ -0,0 +1,281 @@
# WordPress News Import
Automatisierter Workflow zum Erstellen von WordPress-Beiträgen aus Markdown-Dateien über die WordPress REST-API.
## Features
- ✅ **Duplikatsprüfung**: Verhindert das doppelte Erstellen von Beiträgen und Medien
- ✅ **Markdown zu HTML**: Automatische Konvertierung von Markdown-Inhalten
- ✅ **Medien-Upload**: Hochladen von Beitragsbildern mit Duplikatsprüfung
- ✅ **Kategorien & Tags**: Automatische Erstellung fehlender Kategorien und Tags
- ✅ **Flexible Quellen**: Unterstützt Markdown-URLs und lokale Dateien
- ✅ **YAML-Konfiguration**: Einfache Verwaltung mehrerer Beiträge
## Voraussetzungen
- Python 3.7 oder höher
- WordPress-Installation mit aktivierter REST-API
- WordPress Anwendungspasswort (Application Password)
## Installation
1. **Repository klonen oder herunterladen**
2. **Python-Abhängigkeiten installieren**
```bash
pip install -r requirements.txt
```
3. **Umgebungsvariablen konfigurieren**
Kopieren Sie `.env.example` zu `.env` und tragen Sie Ihre Credentials ein:
```bash
cp .env.example .env
```
Bearbeiten Sie `.env`:
```env
WORDPRESS_URL=https://news.rpi-virtuell.de
WORDPRESS_USERNAME=ihr_benutzername
WORDPRESS_APP_PASSWORD=UIVI 4Tdy oojL 9iZG g3X2 iAn5
```
## WordPress Anwendungspasswort erstellen
1. Melden Sie sich in WordPress an
2. Gehen Sie zu **Benutzer → Profil**
3. Scrollen Sie zu **Anwendungspasswörter**
4. Geben Sie einen Namen ein (z.B. "News Import")
5. Klicken Sie auf **Neues Anwendungspasswort hinzufügen**
6. Kopieren Sie das generierte Passwort in Ihre `.env`-Datei
## Verwendung
### 1. YAML-Konfiguration erstellen
Erstellen Sie eine `posts.yaml`-Datei mit Ihren Beiträgen:
```yaml
posts:
- title: "Mein erster Beitrag"
markdown_url: "https://raw.githubusercontent.com/user/repo/main/post.md"
status: "draft" # draft, publish, pending, private
categories:
- "News"
- "Tutorials"
tags:
- "WordPress"
- "API"
featured_image: "images/header.jpg"
excerpt: "Eine kurze Zusammenfassung"
- title: "Lokaler Beitrag"
markdown_file: "content/local-post.md"
status: "publish"
categories:
- "Updates"
settings:
default_status: "draft"
skip_duplicates: true
skip_duplicate_media: true
```
### 2. Workflow ausführen
```bash
python workflow.py posts.yaml
```
Oder ohne Angabe der Datei (verwendet `posts.yaml` als Standard):
```bash
python workflow.py
```
## Struktur
```
newsimport/
├── .env # Credentials (nicht in Git!)
├── .env.example # Beispiel-Konfiguration
├── .gitignore # Git-Ignorier-Liste
├── requirements.txt # Python-Abhängigkeiten
├── wordpress_api.py # WordPress REST-API Client
├── workflow.py # Haupt-Workflow Script
├── posts.yaml # Beitrags-Konfiguration
├── README.md # Diese Datei
├── content/ # Lokale Markdown-Dateien (optional)
│ └── *.md
└── images/ # Lokale Bilder (optional)
└── *.jpg/png
```
## API-Funktionen
### WordPress API Client (`wordpress_api.py`)
```python
from wordpress_api import WordPressAPI
# API initialisieren
wp = WordPressAPI(url, username, app_password)
# Beitrag erstellen (mit Duplikatsprüfung)
post_id = wp.create_post(
title="Titel",
content="<p>HTML-Inhalt</p>",
status="publish",
check_duplicate=True
)
# Medien hochladen (mit Duplikatsprüfung)
media_id = wp.upload_media(
file_path="bild.jpg",
title="Bild-Titel",
alt_text="Alt-Text",
check_duplicate=True
)
# Kategorie holen oder erstellen
cat_id = wp.get_or_create_category("News")
# Tag holen oder erstellen
tag_id = wp.get_or_create_tag("WordPress")
# Auf Duplikate prüfen
existing_post_id = wp.check_post_exists("Titel")
existing_media_id = wp.check_media_exists("bild.jpg")
```
## YAML-Konfiguration
### Beitrags-Felder
- `title` (erforderlich): Titel des Beitrags
- `markdown_url`: URL zur Markdown-Datei
- `markdown_file`: Pfad zu lokaler Markdown-Datei
- `content`: Direkter Markdown-Inhalt
- `status`: `draft`, `publish`, `pending`, `private`
- `categories`: Liste von Kategorie-Namen
- `tags`: Liste von Tag-Namen
- `featured_image`: Pfad oder URL zum Beitragsbild
- `excerpt`: Kurze Zusammenfassung
- `author`: Autor-Username
### Globale Einstellungen
```yaml
settings:
default_status: "draft" # Standard-Status für Beiträge
default_author: "admin" # Standard-Autor
skip_duplicates: true # Bestehende Beiträge überspringen
skip_duplicate_media: true # Bestehende Medien überspringen
markdown_extensions: # Markdown-Erweiterungen
- tables
- fenced_code
- footnotes
```
## Duplikatsprüfung
### Beiträge
Das System prüft vor dem Erstellen, ob ein Beitrag mit dem gleichen Titel bereits existiert. Falls ja, wird die bestehende Post-ID zurückgegeben und kein neuer Beitrag erstellt.
### Medien
Vor dem Upload wird geprüft, ob eine Datei mit dem gleichen Namen bereits existiert. Falls ja, wird die bestehende Media-ID verwendet.
## Beispiele
### Beispiel 1: Einfacher Beitrag von URL
```yaml
posts:
- title: "News Update"
markdown_url: "https://example.com/news.md"
status: "publish"
```
### Beispiel 2: Beitrag mit Kategorien, Tags und Bild
```yaml
posts:
- title: "Tutorial: WordPress REST-API"
markdown_url: "https://example.com/tutorial.md"
status: "draft"
categories:
- "Tutorials"
- "WordPress"
tags:
- "REST-API"
- "Entwicklung"
- "PHP"
featured_image: "https://example.com/images/header.jpg"
excerpt: "Lernen Sie die WordPress REST-API kennen"
```
### Beispiel 3: Lokale Dateien
```yaml
posts:
- title: "Lokaler Inhalt"
markdown_file: "content/article.md"
status: "publish"
featured_image: "images/local-image.jpg"
```
## Fehlerbehebung
### Authentifizierungsfehler
**Problem**: `401 Unauthorized`
**Lösung**:
- Überprüfen Sie Username und Anwendungspasswort in `.env`
- Stellen Sie sicher, dass das Anwendungspasswort korrekt ist (keine zusätzlichen Leerzeichen)
- Verifizieren Sie, dass die WordPress REST-API aktiviert ist
### Keine Verbindung zu WordPress
**Problem**: `Connection refused` oder Timeout
**Lösung**:
- Überprüfen Sie die `WORDPRESS_URL` in `.env`
- Stellen Sie sicher, dass WordPress erreichbar ist
- Prüfen Sie Firewall-Einstellungen
### Markdown wird nicht konvertiert
**Problem**: Markdown-Syntax erscheint im Beitrag
**Lösung**:
- Überprüfen Sie, ob `markdown` installiert ist: `pip install markdown`
- Prüfen Sie die Markdown-Syntax in der Quelldatei
### Import-Fehler bei Modulen
**Problem**: `ModuleNotFoundError: No module named 'requests'`
**Lösung**:
```bash
pip install -r requirements.txt
```
## Sicherheit
⚠️ **Wichtig**:
- Committen Sie **niemals** die `.env`-Datei mit echten Credentials in Git!
- Die `.gitignore` ist bereits so konfiguriert, dass `.env` ignoriert wird
- Verwenden Sie `.env.example` als Vorlage für andere Nutzer
## Lizenz
Dieses Projekt steht unter der MIT-Lizenz.
## Support
Bei Problemen oder Fragen erstellen Sie bitte ein Issue im Repository.
---
**Hinweis**: Dieses Tool wurde für `https://news.rpi-virtuell.de` entwickelt, funktioniert aber mit jeder WordPress-Installation, die die REST-API unterstützt.

View file

@ -0,0 +1,25 @@
# Beispiel-Beitrag
Dies ist ein Beispiel für einen lokalen Markdown-Beitrag.
## Einführung
Dieser Beitrag demonstriert die Verwendung von lokalem Markdown-Content für WordPress-Beiträge.
## Features
- **Markdown-Formatierung**: Vollständige Markdown-Unterstützung
- **Code-Blöcke**: Syntax-Highlighting für verschiedene Sprachen
- **Listen**: Geordnet und ungeordnet
- **Links und Bilder**: Vollständige Unterstützung
## Code-Beispiel
```python
def hello_world():
print("Hello, WordPress!")
```
## Zusammenfassung
Mit diesem System können Sie einfach Markdown-Inhalte in WordPress-Beiträge umwandeln.

50
posts.yaml Normal file
View file

@ -0,0 +1,50 @@
# Beispiel-Konfiguration für WordPress-Beiträge
# Diese YAML-Datei definiert Beiträge, die aus Markdown-Dateien erstellt werden sollen
posts:
- title: "Einführung in die WordPress REST-API"
markdown_url: "https://raw.githubusercontent.com/example/repo/main/docs/wordpress-api.md"
status: "draft" # draft, publish, pending, private
categories:
- "Tutorials"
- "WordPress"
tags:
- "REST-API"
- "Entwicklung"
featured_image: "images/wordpress-api-header.jpg"
author: "admin"
excerpt: "Ein umfassender Leitfaden zur Verwendung der WordPress REST-API"
- title: "Best Practices für WordPress-Plugins"
markdown_url: "https://raw.githubusercontent.com/example/repo/main/docs/plugin-best-practices.md"
status: "draft"
categories:
- "WordPress"
- "Best Practices"
tags:
- "Plugins"
- "Entwicklung"
- "Sicherheit"
- title: "Lokale Markdown-Datei verwenden"
markdown_file: "content/local-post.md" # Lokale Datei statt URL
status: "publish"
categories:
- "News"
tags:
- "Update"
featured_image: "images/news-header.png"
# Globale Einstellungen (optional)
settings:
default_status: "draft"
default_author: "admin"
# Wenn true, werden vorhandene Beiträge übersprungen
skip_duplicates: true
# Wenn true, werden vorhandene Medien übersprungen
skip_duplicate_media: true
# Markdown zu HTML Konvertierung
markdown_extensions:
- tables
- fenced_code
- footnotes

4
requirements.txt Normal file
View file

@ -0,0 +1,4 @@
requests>=2.31.0
python-dotenv>=1.0.0
PyYAML>=6.0.1
markdown>=3.5.0

278
wordpress_api.py Normal file
View file

@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""
WordPress API Helper
Bietet Funktionen zum Erstellen von WordPress-Beiträgen und Hochladen von Medien
über die WordPress REST-API mit Duplikatsprüfung.
"""
import os
import requests
import hashlib
from typing import Optional, Dict, Any, List
from urllib.parse import urljoin
import mimetypes
class WordPressAPI:
"""WordPress REST-API Client mit Duplikatsprüfung"""
def __init__(self, url: str, username: str, app_password: str):
"""
Initialisiert den WordPress API Client
Args:
url: WordPress URL (z.B. https://news.rpi-virtuell.de)
username: WordPress Benutzername
app_password: WordPress Anwendungspasswort
"""
self.url = url.rstrip('/')
self.api_base = urljoin(self.url, '/wp-json/wp/v2/')
self.auth = (username, app_password.replace(' ', ''))
self.session = requests.Session()
self.session.auth = self.auth
def _get(self, endpoint: str, params: Optional[Dict] = None) -> requests.Response:
"""GET-Request an WordPress API"""
url = urljoin(self.api_base, endpoint)
response = self.session.get(url, params=params)
response.raise_for_status()
return response
def _post(self, endpoint: str, data: Optional[Dict] = None,
files: Optional[Dict] = None, headers: Optional[Dict] = None) -> requests.Response:
"""POST-Request an WordPress API"""
url = urljoin(self.api_base, endpoint)
response = self.session.post(url, json=data, files=files, headers=headers)
response.raise_for_status()
return response
def check_post_exists(self, title: str) -> Optional[int]:
"""
Prüft, ob ein Beitrag mit dem Titel bereits existiert
Args:
title: Titel des Beitrags
Returns:
Post-ID wenn gefunden, sonst None
"""
try:
response = self._get('posts', params={'search': title, 'per_page': 10})
posts = response.json()
# Exakte Übereinstimmung prüfen
for post in posts:
if post.get('title', {}).get('rendered', '') == title:
return post['id']
return None
except requests.exceptions.RequestException as e:
print(f"Fehler bei der Suche nach Beitrag: {e}")
return None
def check_media_exists(self, filename: str, file_hash: Optional[str] = None) -> Optional[int]:
"""
Prüft, ob eine Mediendatei bereits existiert
Args:
filename: Dateiname
file_hash: Optional MD5-Hash der Datei für präzisere Prüfung
Returns:
Media-ID wenn gefunden, sonst None
"""
try:
# Suche nach Dateiname
response = self._get('media', params={'search': filename, 'per_page': 10})
media_items = response.json()
for item in media_items:
source_url = item.get('source_url', '')
if filename in source_url:
# Wenn Hash gegeben, zusätzlich prüfen
if file_hash:
# Hash kann nicht direkt verglichen werden,
# daher nur Dateiname-Check
return item['id']
return item['id']
return None
except requests.exceptions.RequestException as e:
print(f"Fehler bei der Suche nach Medien: {e}")
return None
def upload_media(self, file_path: str, title: Optional[str] = None,
alt_text: Optional[str] = None,
check_duplicate: bool = True) -> Optional[int]:
"""
Lädt eine Mediendatei zu WordPress hoch
Args:
file_path: Pfad zur Datei
title: Titel der Mediendatei (optional)
alt_text: Alt-Text für Bilder (optional)
check_duplicate: Prüfung auf Duplikate (Standard: True)
Returns:
Media-ID der hochgeladenen Datei, oder None bei Fehler
"""
if not os.path.exists(file_path):
print(f"Datei nicht gefunden: {file_path}")
return None
filename = os.path.basename(file_path)
# Duplikatsprüfung
if check_duplicate:
existing_id = self.check_media_exists(filename)
if existing_id:
print(f"Medien-Datei '{filename}' existiert bereits (ID: {existing_id})")
return existing_id
# MIME-Type ermitteln
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type:
mime_type = 'application/octet-stream'
# Datei hochladen
try:
with open(file_path, 'rb') as f:
files = {
'file': (filename, f, mime_type)
}
headers = {
'Content-Disposition': f'attachment; filename="{filename}"'
}
if title:
headers['Content-Title'] = title
if alt_text:
headers['Content-Alt-Text'] = alt_text
url = urljoin(self.api_base, 'media')
response = self.session.post(url, files=files, headers=headers)
response.raise_for_status()
media_data = response.json()
media_id = media_data['id']
print(f"Medien-Datei '{filename}' hochgeladen (ID: {media_id})")
return media_id
except requests.exceptions.RequestException as e:
print(f"Fehler beim Hochladen der Medien-Datei: {e}")
return None
def create_post(self, title: str, content: str,
status: str = 'draft',
featured_media: Optional[int] = None,
categories: Optional[List[int]] = None,
tags: Optional[List[int]] = None,
check_duplicate: bool = True,
**kwargs) -> Optional[int]:
"""
Erstellt einen neuen WordPress-Beitrag
Args:
title: Titel des Beitrags
content: Inhalt des Beitrags (HTML)
status: Status (draft, publish, etc.)
featured_media: ID des Beitragsbilds
categories: Liste der Kategorie-IDs
tags: Liste der Tag-IDs
check_duplicate: Prüfung auf Duplikate (Standard: True)
**kwargs: Weitere WordPress-Post-Felder
Returns:
Post-ID des erstellten Beitrags, oder None bei Fehler
"""
# Duplikatsprüfung
if check_duplicate:
existing_id = self.check_post_exists(title)
if existing_id:
print(f"Beitrag '{title}' existiert bereits (ID: {existing_id})")
return existing_id
# Post-Daten zusammenstellen
post_data = {
'title': title,
'content': content,
'status': status,
**kwargs
}
if featured_media:
post_data['featured_media'] = featured_media
if categories:
post_data['categories'] = categories
if tags:
post_data['tags'] = tags
# Beitrag erstellen
try:
response = self._post('posts', data=post_data)
post = response.json()
post_id = post['id']
print(f"Beitrag '{title}' erstellt (ID: {post_id}, Status: {status})")
return post_id
except requests.exceptions.RequestException as e:
print(f"Fehler beim Erstellen des Beitrags: {e}")
if hasattr(e.response, 'text'):
print(f"Details: {e.response.text}")
return None
def get_categories(self) -> List[Dict[str, Any]]:
"""Holt alle verfügbaren Kategorien"""
try:
response = self._get('categories', params={'per_page': 100})
return response.json()
except requests.exceptions.RequestException as e:
print(f"Fehler beim Abrufen der Kategorien: {e}")
return []
def get_or_create_category(self, name: str) -> Optional[int]:
"""Holt oder erstellt eine Kategorie"""
categories = self.get_categories()
for cat in categories:
if cat['name'].lower() == name.lower():
return cat['id']
# Kategorie erstellen
try:
response = self._post('categories', data={'name': name})
return response.json()['id']
except requests.exceptions.RequestException as e:
print(f"Fehler beim Erstellen der Kategorie: {e}")
return None
def get_tags(self) -> List[Dict[str, Any]]:
"""Holt alle verfügbaren Tags"""
try:
response = self._get('tags', params={'per_page': 100})
return response.json()
except requests.exceptions.RequestException as e:
print(f"Fehler beim Abrufen der Tags: {e}")
return []
def get_or_create_tag(self, name: str) -> Optional[int]:
"""Holt oder erstellt einen Tag"""
tags = self.get_tags()
for tag in tags:
if tag['name'].lower() == name.lower():
return tag['id']
# Tag erstellen
try:
response = self._post('tags', data={'name': name})
return response.json()['id']
except requests.exceptions.RequestException as e:
print(f"Fehler beim Erstellen des Tags: {e}")
return None
def calculate_file_hash(file_path: str) -> str:
"""Berechnet MD5-Hash einer Datei"""
hash_md5 = hashlib.md5()
with open(file_path, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()

278
workflow.py Normal file
View file

@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""
WordPress Import Workflow
Liest Markdown-Dateien aus URLs oder lokalen Dateien und erstellt WordPress-Beiträge
basierend auf einer YAML-Konfigurationsdatei.
"""
import os
import sys
import yaml
import requests
import markdown
from pathlib import Path
from dotenv import load_dotenv
from typing import Dict, Any, List, Optional
from wordpress_api import WordPressAPI
# Lade Umgebungsvariablen
load_dotenv()
def download_markdown(url: str) -> Optional[str]:
"""
Lädt Markdown-Inhalt von einer URL herunter
Args:
url: URL zur Markdown-Datei
Returns:
Markdown-Inhalt als String oder None bei Fehler
"""
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Fehler beim Herunterladen von {url}: {e}")
return None
def read_local_markdown(file_path: str) -> Optional[str]:
"""
Liest Markdown-Inhalt aus einer lokalen Datei
Args:
file_path: Pfad zur lokalen Markdown-Datei
Returns:
Markdown-Inhalt als String oder None bei Fehler
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
except IOError as e:
print(f"Fehler beim Lesen von {file_path}: {e}")
return None
def markdown_to_html(markdown_text: str, extensions: Optional[List[str]] = None) -> str:
"""
Konvertiert Markdown zu HTML
Args:
markdown_text: Markdown-Text
extensions: Liste der Markdown-Erweiterungen
Returns:
HTML-String
"""
if extensions is None:
extensions = ['extra', 'codehilite', 'toc']
return markdown.markdown(markdown_text, extensions=extensions)
def process_featured_image(wp_api: WordPressAPI, image_path: str,
check_duplicate: bool = True) -> Optional[int]:
"""
Verarbeitet und lädt ein Beitragsbild hoch
Args:
wp_api: WordPress API Client
image_path: Pfad zum Bild (lokal oder URL)
check_duplicate: Prüfung auf Duplikate
Returns:
Media-ID oder None
"""
# Prüfe ob URL oder lokaler Pfad
if image_path.startswith('http://') or image_path.startswith('https://'):
# Download Bild
try:
response = requests.get(image_path, timeout=30)
response.raise_for_status()
# Temporäre Datei erstellen
filename = os.path.basename(image_path.split('?')[0])
temp_path = f"/tmp/{filename}"
with open(temp_path, 'wb') as f:
f.write(response.content)
media_id = wp_api.upload_media(temp_path, check_duplicate=check_duplicate)
# Temporäre Datei löschen
os.remove(temp_path)
return media_id
except Exception as e:
print(f"Fehler beim Verarbeiten des Bilds von URL: {e}")
return None
else:
# Lokale Datei
if os.path.exists(image_path):
return wp_api.upload_media(image_path, check_duplicate=check_duplicate)
else:
print(f"Bilddatei nicht gefunden: {image_path}")
return None
def process_post(wp_api: WordPressAPI, post_config: Dict[str, Any],
global_settings: Dict[str, Any]) -> Optional[int]:
"""
Verarbeitet einen einzelnen Beitrag aus der Konfiguration
Args:
wp_api: WordPress API Client
post_config: Beitrags-Konfiguration
global_settings: Globale Einstellungen
Returns:
Post-ID oder None
"""
title = post_config.get('title')
if not title:
print("Fehler: Titel fehlt in der Beitragskonfiguration")
return None
print(f"\n{'='*60}")
print(f"Verarbeite Beitrag: {title}")
print(f"{'='*60}")
# Markdown-Inhalt abrufen
markdown_content = None
if 'markdown_url' in post_config:
print(f"Lade Markdown von URL: {post_config['markdown_url']}")
markdown_content = download_markdown(post_config['markdown_url'])
elif 'markdown_file' in post_config:
print(f"Lese lokale Markdown-Datei: {post_config['markdown_file']}")
markdown_content = read_local_markdown(post_config['markdown_file'])
elif 'content' in post_config:
markdown_content = post_config['content']
if not markdown_content:
print(f"Fehler: Kein Inhalt für Beitrag '{title}'")
return None
# Markdown zu HTML konvertieren
extensions = global_settings.get('markdown_extensions', ['extra', 'codehilite', 'toc'])
html_content = markdown_to_html(markdown_content, extensions)
# Kategorien verarbeiten
category_ids = []
if 'categories' in post_config:
for cat_name in post_config['categories']:
cat_id = wp_api.get_or_create_category(cat_name)
if cat_id:
category_ids.append(cat_id)
# Tags verarbeiten
tag_ids = []
if 'tags' in post_config:
for tag_name in post_config['tags']:
tag_id = wp_api.get_or_create_tag(tag_name)
if tag_id:
tag_ids.append(tag_id)
# Beitragsbild verarbeiten
featured_media_id = None
if 'featured_image' in post_config:
skip_duplicate_media = global_settings.get('skip_duplicate_media', True)
featured_media_id = process_featured_image(
wp_api,
post_config['featured_image'],
check_duplicate=skip_duplicate_media
)
# Status
status = post_config.get('status', global_settings.get('default_status', 'draft'))
# Excerpt
excerpt = post_config.get('excerpt', '')
# Beitrag erstellen
skip_duplicates = global_settings.get('skip_duplicates', True)
post_id = wp_api.create_post(
title=title,
content=html_content,
status=status,
featured_media=featured_media_id,
categories=category_ids if category_ids else None,
tags=tag_ids if tag_ids else None,
excerpt=excerpt,
check_duplicate=skip_duplicates
)
return post_id
def main():
"""Hauptfunktion des Workflows"""
# Konfigurationsdatei laden
config_file = sys.argv[1] if len(sys.argv) > 1 else 'posts.yaml'
if not os.path.exists(config_file):
print(f"Fehler: Konfigurationsdatei '{config_file}' nicht gefunden")
print("Verwendung: python workflow.py [config.yaml]")
sys.exit(1)
print(f"Lade Konfiguration aus: {config_file}")
with open(config_file, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# WordPress-Credentials aus Umgebungsvariablen
wp_url = os.getenv('WORDPRESS_URL')
wp_username = os.getenv('WORDPRESS_USERNAME')
wp_password = os.getenv('WORDPRESS_APP_PASSWORD')
if not all([wp_url, wp_username, wp_password]):
print("Fehler: WordPress-Credentials fehlen in .env-Datei")
print("Benötigt: WORDPRESS_URL, WORDPRESS_USERNAME, WORDPRESS_APP_PASSWORD")
sys.exit(1)
print(f"\nVerbinde mit WordPress: {wp_url}")
# WordPress API initialisieren
wp_api = WordPressAPI(wp_url, wp_username, wp_password)
# Globale Einstellungen
global_settings = config.get('settings', {})
# Beiträge verarbeiten
posts = config.get('posts', [])
if not posts:
print("Warnung: Keine Beiträge in der Konfiguration gefunden")
return
print(f"\nVerarbeite {len(posts)} Beitrag/Beiträge...\n")
success_count = 0
error_count = 0
for post_config in posts:
try:
post_id = process_post(wp_api, post_config, global_settings)
if post_id:
success_count += 1
else:
error_count += 1
except Exception as e:
print(f"Fehler bei der Verarbeitung: {e}")
error_count += 1
# Zusammenfassung
print(f"\n{'='*60}")
print(f"ZUSAMMENFASSUNG")
print(f"{'='*60}")
print(f"Erfolgreich: {success_count}")
print(f"Fehler: {error_count}")
print(f"Gesamt: {len(posts)}")
print(f"{'='*60}\n")
if __name__ == '__main__':
main()