278 lines
8.3 KiB
Python
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()
|