Bugfix: Tag-Duplikate, Post-Duplikate und Veröffentlichungsdatum

Fixes:
- Tag/Kategorie-Erstellung: Bessere Fehlerbehandlung für bereits existierende Tags
- Post-Duplikatsprüfung: Verbesserte Suche mit status='any' und case-insensitive Vergleich
- Veröffentlichungsdatum: datePublished aus Frontmatter wird als WordPress-Datum gesetzt
- Erweiterte Datumsextraktion aus verschiedenen Frontmatter-Strukturen

Neue Datei:
- USAGE_MODES.md: Übersicht der drei Verwendungsmodi
This commit is contained in:
Jörg Lohrer 2025-10-01 08:30:07 +02:00
parent 7a234be652
commit 9ba1aa7b10
4 changed files with 169 additions and 8 deletions

94
USAGE_MODES.md Normal file
View file

@ -0,0 +1,94 @@
# Verwendungs-Modi
## Modus 1: Einzelne URL (Empfohlen für Tests)
```bash
python workflow.py "https://example.com/artikel.md"
```
oder lokale Datei:
```bash
python workflow.py "content/beispiel-beitrag.md"
```
**Vorteile:**
- Schnellster Weg zum Testen
- Keine YAML-Konfiguration nötig
- Alle Metadaten aus Frontmatter
## Modus 2: YAML-Batch (Für kuratierte Listen)
Erstellen Sie `posts.yaml`:
```yaml
posts:
- url: "https://example.com/artikel1.md"
- url: "https://example.com/artikel2.md"
- file: "content/artikel3.md"
settings:
default_status: "draft"
```
Dann:
```bash
python workflow.py posts.yaml
```
**Vorteile:**
- Kontrollierte Liste von Beiträgen
- Metadaten können überschrieben werden
- Wiederverwendbar
## Modus 3: Forgejo-Repository (Für Bulk-Import)
```bash
python workflow.py --repo "https://codeberg.org/user/repo" main
```
**Vorteile:**
- Alle Markdown-Dateien eines Repos auf einmal
- Perfekt für bestehende Dokumentationen
- Automatische Erkennung aller .md-Dateien
## Kombinationen
### Test → Produktion Workflow
1. **Einzelne URL testen:**
```bash
python workflow.py "https://example.com/test-artikel.md"
```
2. **Bei Erfolg: YAML für mehrere erstellen**
3. **Bei Erfolg: Status auf "publish" setzen**
### Repository → Kuratierte Liste
1. **Repository scannen:**
```bash
python workflow.py --repo "https://codeberg.org/user/repo" main
```
2. **Prüfen welche Beiträge erstellt wurden**
3. **Gewünschte in YAML übertragen für Feinabstimmung**
## Empfohlener Workflow
1. ✅ Test mit Beispiel-Beitrag:
```bash
python workflow.py "content/beispiel-beitrag.md"
```
2. ✅ Test mit eigener URL:
```bash
python workflow.py "https://ihre-url.de/artikel.md"
```
3. ✅ Bei Erfolg: Batch oder Repo-Import
4. ✅ In WordPress überprüfen
5. ✅ Status auf "publish" setzen (in .yaml oder Frontmatter)

View file

@ -146,14 +146,19 @@ def extract_wordpress_metadata(frontmatter: Dict[str, Any],
metadata['status'] = 'draft'
# Datum extrahieren (falls vorhanden)
# Priorität: date > datePublished > (aus commonMetadata) > (aus staticSiteGenerator)
if 'date' in frontmatter:
metadata['date'] = frontmatter['date']
metadata['date'] = str(frontmatter['date'])
elif 'datePublished' in frontmatter:
metadata['date'] = frontmatter['datePublished']
metadata['date'] = str(frontmatter['datePublished'])
elif isinstance(frontmatter.get('#commonMetadata'), dict):
common = frontmatter['#commonMetadata']
if 'datePublished' in common:
metadata['date'] = common['datePublished']
metadata['date'] = str(common['datePublished'])
elif isinstance(frontmatter.get('#staticSiteGenerator'), dict):
static_gen = frontmatter['#staticSiteGenerator']
if 'datePublished' in static_gen:
metadata['date'] = str(static_gen['datePublished'])
return metadata

View file

@ -57,13 +57,31 @@ class WordPressAPI:
Post-ID wenn gefunden, sonst None
"""
try:
response = self._get('posts', params={'search': title, 'per_page': 10})
# Suche mit verschiedenen Parametern
response = self._get('posts', params={
'search': title,
'per_page': 100, # Erhöht für bessere Suche
'status': 'any' # Alle Status (draft, publish, etc.)
})
posts = response.json()
# Normalisiere Titel für Vergleich
title_lower = title.lower().strip()
# Exakte Übereinstimmung prüfen
for post in posts:
if post.get('title', {}).get('rendered', '') == title:
# Prüfe rendered Titel
rendered_title = post.get('title', {}).get('rendered', '').strip()
if rendered_title.lower() == title_lower:
print(f" → Beitrag gefunden (ID: {post['id']}, Status: {post['status']})")
return post['id']
# Prüfe auch raw Titel falls vorhanden
raw_title = post.get('title', {}).get('raw', '').strip()
if raw_title and raw_title.lower() == title_lower:
print(f" → Beitrag gefunden (ID: {post['id']}, Status: {post['status']})")
return post['id']
return None
except requests.exceptions.RequestException as e:
print(f"Fehler bei der Suche nach Beitrag: {e}")
@ -241,7 +259,21 @@ class WordPressAPI:
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}")
# Prüfe ob Fehler durch bereits existierende Kategorie
if e.response is not None and e.response.status_code == 400:
# Kategorie könnte durch Race Condition gerade erstellt worden sein
# Erneut suchen
categories = self.get_categories()
for cat in categories:
if cat['name'].lower() == name.lower():
return cat['id']
print(f"Fehler beim Erstellen der Kategorie '{name}': {e}")
if hasattr(e, 'response') and e.response is not None:
try:
error_data = e.response.json()
print(f"Details: {error_data}")
except:
print(f"Response: {e.response.text}")
return None
def get_tags(self) -> List[Dict[str, Any]]:
@ -265,7 +297,21 @@ class WordPressAPI:
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}")
# Prüfe ob Fehler durch bereits existierenden Tag
if e.response is not None and e.response.status_code == 400:
# Tag könnte durch Race Condition gerade erstellt worden sein
# Erneut suchen
tags = self.get_tags()
for tag in tags:
if tag['name'].lower() == name.lower():
return tag['id']
print(f"Fehler beim Erstellen des Tags '{name}': {e}")
if hasattr(e, 'response') and e.response is not None:
try:
error_data = e.response.json()
print(f"Details: {error_data}")
except:
print(f"Response: {e.response.text}")
return None

View file

@ -232,6 +232,21 @@ def process_post(wp_api: WordPressAPI, post_config: Dict[str, Any],
# Autor
author_name = metadata.get('author') or post_config.get('author') or default_author
# Veröffentlichungsdatum
# WordPress erwartet ISO 8601 Format: 2025-09-02T12:00:00
publish_date = metadata.get('date') or post_config.get('date')
# Zusätzliche WordPress-Felder vorbereiten
extra_fields = {}
if publish_date:
# Datum formatieren falls nötig
if isinstance(publish_date, str):
# Wenn nur Datum (YYYY-MM-DD), füge Uhrzeit hinzu
if len(publish_date) == 10: # Format: 2025-09-02
publish_date = f"{publish_date}T00:00:00"
extra_fields['date'] = publish_date
print(f"Veröffentlichungsdatum: {publish_date}")
# Beitrag erstellen
skip_duplicates = global_settings.get('skip_duplicates', True)
post_id = wp_api.create_post(
@ -242,7 +257,8 @@ def process_post(wp_api: WordPressAPI, post_config: Dict[str, Any],
categories=category_ids if category_ids else None,
tags=tag_ids if tag_ids else None,
excerpt=excerpt,
check_duplicate=skip_duplicates
check_duplicate=skip_duplicates,
**extra_fields # Datum und andere Felder
)
return post_id