MD2WordPress/workflow.py

278 lines
8.3 KiB
Python

#!/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()