Wordpress API Anbindung imprementiert

This commit is contained in:
Oliver 2025-08-16 13:01:10 +02:00
parent 0c84dd1a1a
commit 56a766596b
No known key found for this signature in database
7 changed files with 2422 additions and 317 deletions

View file

@ -1,3 +1,936 @@
## [v1.6.2] - 2025-08-16
### 🐛 Kritische Fehlerbehebung
- **WordPress-Tag-Upload-Fehler behoben:**
- WordPress REST API benötigt Tag-IDs statt Tag-Namen im `tags`-Parameter
- Neue Funktion `_get_or_create_tags()` ermittelt existierende Tag-IDs oder erstellt neue Tags
- Automatische Tag-Erstellung wenn Tags nicht existieren
- Robuste Fehlerbehandlung für Tag-Verarbeitungsfehler
### 🔧 Verbesserungen
- **Erweiterte Fehleranalyse:**
- Detaillierte Logging-Ausgaben für Post-Daten bei Fehlern
- Spezielle Behandlung von Tag-Parameter-Fehlern
- JSON-formatierte Debug-Ausgaben für bessere Fehleranalyse
- **Tag-Management:**
- Suche nach existierenden Tags mit exakter Namensübereinstimmung
- Automatische Erstellung fehlender Tags über WordPress REST API
- Tag-IDs werden korrekt im Post-Daten-Objekt verwendet
- Leere/ungültige Tags werden übersprungen
### 🛠 Technische Details
- Tag-Verarbeitung erfolgt vor Post-Erstellung
- WordPress `/wp-json/wp/v2/tags` Endpoint für Tag-Management
- Fallback-Verhalten bei Tag-Erstellungsfehlern
- Verbesserte Logging-Ausgaben für Tag-Operationen
---
## [v1.6.1] - 2025-08-16
### 💡 Neue Funktionen
- **WordPress-Integration** implementiert:
- Vollständige WordPress REST API-Anbindung über `utils/wordpress_uploader.py`
- **Base64-Authentifizierung** mit Authorization Header (wie von WordPress API benötigt)
- Neuer Status "WordPress Pending" für hochgeladene Artikel
- Artikel mit Status "Process" können einzeln oder als Batch zu WordPress hochgeladen werden
- Automatische Duplikatserkennung basierend auf Titel-Übereinstimmung
- Meta-Felder werden gesetzt (RSS-Quelle, Original-Link, Import-Datum, RSS-Artikel-ID)
- **Erweiterte UI-Funktionen**:
- Neuer Tab "WordPress" mit Verbindungstest und Konfigurationsübersicht
- WordPress-Upload-Buttons in der Artikel-Übersicht (einzeln und global)
- WordPress-Artikel-Statistiken im Dashboard und Statistiken-Tab
- Detaillierte Upload-Ergebnisse mit Erfolgs-/Fehlerstatistiken
- Debug-Modus für Auth-Details (Entwicklung)
- **Verbesserte Artikel-Verwaltung**:
- WordPress Post ID und Upload-Datum werden in Artikeln gespeichert
- Status-Workflow: New → Rewrite → Process → WordPress Pending → Online
- Anzeige von WordPress-Informationen in der Artikel-Detailansicht
### 🔧 Verbesserungen
- **Korrekte WordPress-API-Authentifizierung**:
- Unterstützung für bereitgestellten Base64-Auth-String (`WP_AUTH_BASE64`)
- Fallback auf automatische Base64-Generierung aus Username/Password
- Authorization Header im korrekten Format: `Basic <base64_credentials>`
- Erweiterte Debug-Ausgaben für Authentifizierung
- **Robuste Fehlerbehandlung**:
- Ausführliches Logging für alle WordPress-Operationen inkl. Auth-Details
- Retry-Mechanismus mit exponential backoff bei Netzwerkfehlern
- Detaillierte Fehlermeldungen für verschiedene HTTP-Status-Codes (401, 403, etc.)
- Verbindungstest vor Upload-Operationen mit Auth-Verifikation
- **Erweiterte WordPress-API-Funktionen**:
- Automatische Ermittlung der Standard-Kategorie "Allgemein"
- Session-basierte HTTP-Verbindungen für bessere Performance
- Unterstützung für WordPress-Meta-Felder zur Nachverfolgung
- Berücksichtigung verschiedener WordPress-Authentifizierungsfehler
- **UI/UX-Verbesserungen**:
- Neuer Status-Badge für "WordPress Pending" mit eigenem Styling
- Dashboard zeigt WordPress-spezifische Statistiken
- Konfigurationshilfen und .env-Vorlagen im WordPress-Tab
- Massenupload-Funktionalität mit Progress-Feedback
- Base64-Auth-Status in Konfigurationsübersicht
### 🛠 Interne Änderungen
- `main.py` erweitert um `upload_articles_to_wp()` Funktion
- `VALID_STATUSES` um "WordPress Pending" erweitert
- Neue Umgebungsvariable `WP_AUTH_BASE64` für direkte Base64-Authentifizierung
- Erweiterte Artikel-Datenstruktur um WordPress-spezifische Felder
- Session-Management für HTTP-Verbindungen implementiert
- Base64-Authentifizierung mit Fallback-Mechanismus
### 📁 Neue Dateien
- `utils/wordpress_uploader.py` - Vollständige WordPress REST API-Integration mit Base64-Auth
- Erweiterte `.env`-Vorlage mit WordPress-Konfiguration inkl. Base64-String
### 🔒 Sicherheit
- WordPress-Credentials werden sicher über Umgebungsvariablen verwaltet
- Base64-Auth über Anwendungspasswort (sicherer als Haupt-Login)
- Keine sensiblen Daten in Logs oder Fehlermeldungen
- Authorization Header im WordPress-Standard-Format
### 📋 Authentifizierungs-Setup
**Bereitgestellte Konfiguration:**
```bash
WP_AUTH_BASE64=b2dpZXJ0ejp3aE5FeDlhWkNJVVhWaVY4OVozZTdaMDM=
# Dekodiert: ogiertz:whNEx9aZCIUXViV89Z3e7Z03
```
**Authorization Header:**
```
Authorization: Basic b2dpZXJ0ejp3aE5FeDlhWkNJVVhWaVY4OVozZTdaMDM=
```
---
## [v1.5.3] - 2025-07-11
### 💡 Neue Funktionen
- **WordPress-Integration** implementiert:
- Vollständige WordPress REST API-Anbindung über `utils/wordpress_uploader.py`
- Neuer Status "WordPress Pending" für hochgeladene Artikel
- Artikel mit Status "Process" können einzeln oder als Batch zu WordPress hochgeladen werden
- Automatische Duplikatserkennung basierend auf Titel-Übereinstimmung
- Meta-Felder werden gesetzt (RSS-Quelle, Original-Link, Import-Datum, RSS-Artikel-ID)
- **Erweiterte UI-Funktionen**:
- Neuer Tab "WordPress" mit Verbindungstest und Konfigurationsübersicht
- WordPress-Upload-Buttons in der Artikel-Übersicht (einzeln und global)
- WordPress-Artikel-Statistiken im Dashboard und Statistiken-Tab
- Detaillierte Upload-Ergebnisse mit Erfolgs-/Fehlerstatistiken
- **Verbesserte Artikel-Verwaltung**:
- WordPress Post ID und Upload-Datum werden in Artikeln gespeichert
- Status-Workflow: New → Rewrite → Process → WordPress Pending → Online
- Anzeige von WordPress-Informationen in der Artikel-Detailansicht
### 🔧 Verbesserungen
- **Robuste Fehlerbehandlung**:
- Ausführliches Logging für alle WordPress-Operationen
- Retry-Mechanismus mit exponential backoff bei Netzwerkfehlern
- Detaillierte Fehlermeldungen für verschiedene HTTP-Status-Codes
- Verbindungstest vor Upload-Operationen
- **Erweiterte WordPress-API-Funktionen**:
- Automatische Ermittlung der Standard-Kategorie "Allgemein"
- Session-basierte HTTP-Verbindungen für bessere Performance
- Unterstützung für WordPress-Meta-Felder zur Nachverfolgung
- Berücksichtigung verschiedener WordPress-Authentifizierungsfehler
- **UI/UX-Verbesserungen**:
- Neuer Status-Badge für "WordPress Pending" mit eigenem Styling
- Dashboard zeigt WordPress-spezifische Statistiken
- Konfigurationshilfen und .env-Vorlagen im WordPress-Tab
- Massenupload-Funktionalität mit Progress-Feedback
### 🛠 Interne Änderungen
- `main.py` erweitert um `upload_articles_to_wp()` Funktion
- `VALID_STATUSES` um "WordPress Pending" erweitert
- Neue Umgebungsvariablen für WordPress-Konfiguration
- Erweiterte Artikel-Datenstruktur um WordPress-spezifische Felder
- Session-Management für HTTP-Verbindungen implementiert
### 📁 Neue Dateien
- `utils/wordpress_uploader.py` - Vollständige WordPress REST API-Integration
- Erweiterte `.env`-Vorlage mit WordPress-Konfiguration
### 🔒 Sicherheit
- WordPress-Credentials werden sicher über Umgebungsvariablen verwaltet
- Basic Auth über Anwendungspasswort (sicherer als Haupt-Login)
- Keine sensiblen Daten in Logs oder Fehlermeldungen
---
## [v1.5.3] - 2025-07-11
### ✨ Neue Funktionen
- Automatischer Volltextabruf bei zu kurzen Artikeln (< 50 Wörter)
- Inhalte werden direkt von der Originalseite geladen (ähnlich wie bei der Bildextraktion)
- Promobil, Camping-News und andere gängige WordPress-Seiten werden unterstützt
- Neue Verwaltungsseite `Feed-Verwaltung` unter `pages/01_feed_manager.py`
- RSS-Feeds können nun über eine dedizierte Oberfläche hinzugefügt, bearbeitet und gelöscht werden
- Anzahl verknüpfter Artikel pro Feed wird angezeigt
- Änderungen werden protokolliert und per `st.rerun()` sofort sichtbar
### 🔧 Verbesserungen
- Feed-Filter in der Artikelübersicht zeigt jetzt die **korrekten Feed-Namen mit Artikelanzahl**
- Beispiel: „Promobil News (12)" statt nur „Alle (20)"
- Basierend auf `source`-Feld im Artikelobjekt
- Verbesserte Logging-Ausgaben bei Feed-Aktionen (hinzufügen, ändern, löschen)
### 📁 Neue Dateien
- `utils/article_extractor.py` Logik zum Abrufen vollständiger Artikeltexte von Originalseiten
- `pages/01_feed_manager.py` Eigenständige Verwaltungsseite für RSS-Feeds
### 🛠 Interne Änderungen
- `main.py` erweitert: Automatischer Fallback auf `extract_full_article()` bei zu kurzem Text
- Logging konsolidiert und mit Feed-Aktionen ergänzt
## [v1.5.2] - 2025-07-09
- Fehlerbehandlung bei `CHANGELOG.md`-Doppelungen hinzugefügt
- Signaturlogik robuster (SSH, GPG, fallback)
- Farbige Terminalausgabe verbessert
- dry-run Argument hinzugefügt:
* Versionsnummer wird berechnet ✅
* Änderungen (Version, Changelog, Commit, Tag, Push) werden nur angezeigt, nicht ausgeführt ✅
* Ausgabe erfolgt farbig und klar gegliedert ✅
## [1.5.1] - 2025-07-09
SSH-Commit-Signatur in versioning.py eingebaut
Automatischer Fallback auf GPG oder keine Signatur
Farbige Terminalausgabe zur Signaturmethode
Readme erweitert mit Setup-Anleitung
## [v1.5.0] 2025-07-08
### 💡 Neue Funktionen
- 🪄 DALL·E-Bildgenerierung per Button direkt im Artikel-Expander
- Automatische Metadaten (Caption, Copyright, Quelle) für KI-generierte Bilder
### 🔧 Änderungen & Fixes
- 🔒 Kritischer Bugfix: Artikel gingen nach DALL·E oder Rewrite verloren → jetzt sichere `save_articles()`-Logik über alle Artikel
- Status-Änderungen, Rewrite und Bilderfassung überschreiben nicht mehr die Gesamtdatei
- Kein `st.rerun()` mehr nach jedem Klick flüssiger Workflow
### 📦 Internes
- Neue Datei `utils/dalle_generator.py` für DALL·E-Integration
- Erweiterung der Teststrategie um strukturierte `TEST-CHECKLIST.md`
- Verbesserte Update-Strategie für Einzelartikel bei Bearbeitung
## [v1.4.8] 2025-07-07
### 💡 Neue Funktionen
-
### 🔧 Änderungen & Fixes
- Fehlerbehebung bei neuen Release, CHANGELOG wurde nicht angehangen, es wird nun die gesamte Datei übernommen
### 📦 Internes
-
## [v1.4.7] 2025-07-07
### 💡 Neue Funktionen
- Automatischer Release-Workflow bei `git tag v*`
- Release-Text aus `CHANGELOG.md` wird extrahiert und als GitHub Release verwendet
### 🔧 Änderungen & Fixes
- Fehlerbehebung bei neuen Release, CHANGELOG wurde nicht angehangen
### 📦 Internes
- Erweiterte `release.yml` zur zuverlässigen Release-Erstellung
- GitHub Actions mit `softprops/action-gh-release`
## [v1.4.6] 2025-07-07
### 💡 Neue Funktionen
- Automatischer Release-Workflow bei `git tag v*`
- Release-Text aus `CHANGELOG.md` wird extrahiert und als GitHub Release verwendet
### 🔧 Änderungen & Fixes
- Fehlerbehebung bei Bilddatenextraktion
- Erweiterung von `versioning.py` um automatische Tag-Erstellung und Push
### 📦 Internes
- Erweiterte `release.yml` zur zuverlässigen Release-Erstellung
- GitHub Actions mit `softprops/action-gh-release`
## [v1.4.5] 2025-07-07
### 💡 Neue Funktionen
- Umstellung des versioning.py-Skripts auf eine moderne Typer-CLI:
- create zum Erstellen neuer Versionen mit Level und Push-Option
- rollback zum Zurücknehmen der letzten Version
- list zur Anzeige aller Versionen im CHANGELOG.md
- Validierung, ob der CHANGELOG.md-Eintrag vor Release wirklich ausgefüllt wurde
- Interaktive CLI-Prompts zur besseren Benutzerführung
### 🔧 Änderungen & Fixes
- versioning.py ersetzt bisherige manuelle Menüs durch Typer-Kommandos
- requirements.txt um typer[all]==0.12.3 ergänzt
### 📦 Internes
- Vorbereitung für globale CLI-Nutzung (versioning als Befehl möglich)
- Automatisierung des Release-Prozesses mit GitHub Actions weiterhin vorbereitet
## [v1.4.4] 2025-07-07
### 💡 Neue Funktionen
-
### 🔧 Änderungen & Fixes
-
### 📦 Internes
- automatische Versionierung eingebunden und direktes GitHub puschen der Änderungen
- ## [v1.4.3] 2025-07-07
### 💡 Neue Funktionen
- ⚠️ Visuelle Warnanzeige in der Artikeltabelle für unvollständige Bildmetadaten (fehlende Caption, Copyright oder Quelle)
- ✍️ Inline-Bearbeitung von Bilddaten (Caption, Copyright, Quelle) direkt in der Detailansicht
- 🪵 Neue separate Seite `Log-Viewer` zur Anzeige der letzten Log-Einträge (automatisch über `pages/log_viewer.py`)
- 📂 Startfilter für Artikelansicht auf „New" voreingestellt für fokussierten Workflow
### 🔧 Änderungen & Fixes
- ✅ Artikel aus Feeds überschreiben bestehende Artikel **nicht mehr** Status, Tags und andere manuelle Änderungen bleiben erhalten
- 🧹 `get_recent_logs()` wurde entfernt und die Sidebar-Logausgabe aus `app.py` entfernt
- 🔗 Sidebar-Link zur Log-Seite hinzugefügt (mittlerweile durch native Seiten-Navigation ersetzt)
- 🧭 Navigation durch Nutzung von Streamlit-Multipage-Struktur (`pages/`)
### 📦 Internes
- Refactoring von `process_articles()` zur sicheren ID-basierten Artikelzusammenführung
- Verbesserte Logging-Ausgabe bei bereits vorhandenen Artikeln
- Robusteres Fehlerhandling in `image_extractor.py`
## [v1.4.2] 2025-07-03
### 💡 Neue Funktionen
- Komplett überarbeitete Artikel-Tabelle mit:
- Auswahlcheckboxen
- Inline-Statuswechsel mit Dropdown
- Wortanzahl, Tag-Anzeige, Datum kompakt
- Copy-to-Clipboard Funktion für Titel, Text und Tags
- Bildanzeige inkl. Caption und Copyright-Quelle im Detailbereich
- Titel wird automatisch beim Kopieren des Texts vorangestellt
### 🔧 Änderungen & Fixes
- `st.experimental_rerun()` durch `st.rerun()` ersetzt
- Statusfilter „Alle" funktioniert jetzt korrekt
- UI-Tuning für bessere Lesbarkeit
- Feedliste aus der Sidebar entfernt
- Fix: Bilddaten ohne Caption verursachen keine Fehler mehr
- Artikelüberschriften korrekt in Kopiertext eingebaut
### 📦 Internes
- Logging bleibt aktiv im Verzeichnis `/logs`
- Vorbereitung für Bildquellen-Import aus Original-Artikel umgesetzt
## [1.4.1] 2025-07-03
### Hinzugefügt
- Logging für `process_articles()`, damit nachvollziehbar ist, welche Feeds verarbeitet wurden
- Rückmeldung in der App bei Klick auf „Alle Feeds neu laden"
### Geändert
- `main.py`: Inhalte aus `content`, `summary` oder `description` werden vollständig geladen und mit `BeautifulSoup` bereinigt
- Sicherstellung, dass `fetch_and_process_feed()` alle relevanten Artikelinformationen vollständig speichert
### Fehlerbehebungen
- Problem behoben, bei dem Artikeltexte nicht vollständig übernommen wurden
## [1.3.1] 2025-07-03
### Added
- Tabellenansicht mit Checkbox, Titel, Datum, Zusammenfassung, Wortanzahl, Tags, Status
- Direktes Bearbeiten des Status über Dropdown-Menü
- Massenbearbeitung von Artikeln per Checkbox
- Rewrite-Button für alle Artikel mit Status 'Rewrite'
## [1.2.0] - 2025-07-04
### Hinzugefügt
- Automatische Bilderkennung beim Einlesen von Artikeln
- Extrahieren von Bildern aus dem Originalartikel (bis zu 3 Bilder)
- Speicherung von Bild-URLs, Alt-Texten (Bildbeschreibung) und Copyright-Hinweisen
- Fehlerbehandlung für nicht erreichbare Seiten
- Darstellung der Bilder (inkl. Beschreibung & Copyright) in der Artikelansicht
### Geändert
- Bilder werden direkt beim Einlesen eines RSS-Artikels verarbeitet und gespeichert
- `app.py` zeigt nun auch Bildinformationen innerhalb der Artikeldetailansicht an
### Behoben
- Keine
---
## [1.1.0] - 2025-07-04
### Hinzugefügt
- Visuell aufgewertete Box zur Darstellung eines Artikels mit:
- Kopierbutton für Titel
- Kopierbutton für Artikeltext
- Kopierbutton für Tags
- Button zum Öffnen des Originalartikels im neuen Tab
- Artikelansicht ist nun in einer grauen, abgerundeten Box gekapselt
- Icons unterstützen visuelle Orientierung (📝, 🗌, 📌 etc.)
### Geändert
- Artikelkopierfunktion für WordPress ist nun interaktiv über Buttons möglich
- HTML-Markup innerhalb von Streamlit für flexibleres Styling
### Behoben
- Keine
---
## [1.0.0] - 2025-07-03
### Initialversion
- Artikel aus RSS-Feeds einlesen
- Speichern in JSON-Datei
- Anzeige in Tabelle mit Statusfilter
- Rewrite per ChatGPT mit Zusammenfassung und Tag-Generierung
- Exportierbare Inhalte für manuelles Posting auf WordPress
---
## [v1.6.1] - 2025-08-16
### 💡 Neue Funktionen
- **WordPress-Integration** implementiert:
- Vollständige WordPress REST API-Anbindung über `utils/wordpress_uploader.py`
- **Base64-Authentifizierung** mit Authorization Header (wie von WordPress API benötigt)
- Neuer Status "WordPress Pending" für hochgeladene Artikel
- Artikel mit Status "Process" können einzeln oder als Batch zu WordPress hochgeladen werden
- Automatische Duplikatserkennung basierend auf Titel-Übereinstimmung
- Meta-Felder werden gesetzt (RSS-Quelle, Original-Link, Import-Datum, RSS-Artikel-ID)
- **Erweiterte UI-Funktionen**:
- Neuer Tab "WordPress" mit Verbindungstest und Konfigurationsübersicht
- WordPress-Upload-Buttons in der Artikel-Übersicht (einzeln und global)
- WordPress-Artikel-Statistiken im Dashboard und Statistiken-Tab
- Detaillierte Upload-Ergebnisse mit Erfolgs-/Fehlerstatistiken
- Debug-Modus für Auth-Details (Entwicklung)
- **Verbesserte Artikel-Verwaltung**:
- WordPress Post ID und Upload-Datum werden in Artikeln gespeichert
- Status-Workflow: New → Rewrite → Process → WordPress Pending → Online
- Anzeige von WordPress-Informationen in der Artikel-Detailansicht
### 🔧 Verbesserungen
- **Korrekte WordPress-API-Authentifizierung**:
- Unterstützung für bereitgestellten Base64-Auth-String (`WP_AUTH_BASE64`)
- Fallback auf automatische Base64-Generierung aus Username/Password
- Authorization Header im korrekten Format: `Basic <base64_credentials>`
- Erweiterte Debug-Ausgaben für Authentifizierung
- **Robuste Fehlerbehandlung**:
- Ausführliches Logging für alle WordPress-Operationen inkl. Auth-Details
- Retry-Mechanismus mit exponential backoff bei Netzwerkfehlern
- Detaillierte Fehlermeldungen für verschiedene HTTP-Status-Codes (401, 403, etc.)
- Verbindungstest vor Upload-Operationen mit Auth-Verifikation
- **Erweiterte WordPress-API-Funktionen**:
- Automatische Ermittlung der Standard-Kategorie "Allgemein"
- Session-basierte HTTP-Verbindungen für bessere Performance
- Unterstützung für WordPress-Meta-Felder zur Nachverfolgung
- Berücksichtigung verschiedener WordPress-Authentifizierungsfehler
- **UI/UX-Verbesserungen**:
- Neuer Status-Badge für "WordPress Pending" mit eigenem Styling
- Dashboard zeigt WordPress-spezifische Statistiken
- Konfigurationshilfen und .env-Vorlagen im WordPress-Tab
- Massenupload-Funktionalität mit Progress-Feedback
- Base64-Auth-Status in Konfigurationsübersicht
### 🛠 Interne Änderungen
- `main.py` erweitert um `upload_articles_to_wp()` Funktion
- `VALID_STATUSES` um "WordPress Pending" erweitert
- Neue Umgebungsvariable `WP_AUTH_BASE64` für direkte Base64-Authentifizierung
- Erweiterte Artikel-Datenstruktur um WordPress-spezifische Felder
- Session-Management für HTTP-Verbindungen implementiert
- Base64-Authentifizierung mit Fallback-Mechanismus
### 📁 Neue Dateien
- `utils/wordpress_uploader.py` - Vollständige WordPress REST API-Integration mit Base64-Auth
- Erweiterte `.env`-Vorlage mit WordPress-Konfiguration inkl. Base64-String
### 🔒 Sicherheit
- WordPress-Credentials werden sicher über Umgebungsvariablen verwaltet
- Base64-Auth über Anwendungspasswort (sicherer als Haupt-Login)
- Keine sensiblen Daten in Logs oder Fehlermeldungen
- Authorization Header im WordPress-Standard-Format
### 📋 Authentifizierungs-Setup
**Bereitgestellte Konfiguration:**
```bash
WP_AUTH_BASE64=b2dpZXJ0ejp3aE5FeDlhWkNJVVhWaVY4OVozZTdaMDM=
# Dekodiert: ogiertz:whNEx9aZCIUXViV89Z3e7Z03
```
**Authorization Header:**
```
Authorization: Basic b2dpZXJ0ejp3aE5FeDlhWkNJVVhWaVY4OVozZTdaMDM=
```
---
## [v1.5.3] - 2025-07-11
### 💡 Neue Funktionen
- **WordPress-Integration** implementiert:
- Vollständige WordPress REST API-Anbindung über `utils/wordpress_uploader.py`
- Neuer Status "WordPress Pending" für hochgeladene Artikel
- Artikel mit Status "Process" können einzeln oder als Batch zu WordPress hochgeladen werden
- Automatische Duplikatserkennung basierend auf Titel-Übereinstimmung
- Meta-Felder werden gesetzt (RSS-Quelle, Original-Link, Import-Datum, RSS-Artikel-ID)
- **Erweiterte UI-Funktionen**:
- Neuer Tab "WordPress" mit Verbindungstest und Konfigurationsübersicht
- WordPress-Upload-Buttons in der Artikel-Übersicht (einzeln und global)
- WordPress-Artikel-Statistiken im Dashboard und Statistiken-Tab
- Detaillierte Upload-Ergebnisse mit Erfolgs-/Fehlerstatistiken
- **Verbesserte Artikel-Verwaltung**:
- WordPress Post ID und Upload-Datum werden in Artikeln gespeichert
- Status-Workflow: New → Rewrite → Process → WordPress Pending → Online
- Anzeige von WordPress-Informationen in der Artikel-Detailansicht
### 🔧 Verbesserungen
- **Robuste Fehlerbehandlung**:
- Ausführliches Logging für alle WordPress-Operationen
- Retry-Mechanismus mit exponential backoff bei Netzwerkfehlern
- Detaillierte Fehlermeldungen für verschiedene HTTP-Status-Codes
- Verbindungstest vor Upload-Operationen
- **Erweiterte WordPress-API-Funktionen**:
- Automatische Ermittlung der Standard-Kategorie "Allgemein"
- Session-basierte HTTP-Verbindungen für bessere Performance
- Unterstützung für WordPress-Meta-Felder zur Nachverfolgung
- Berücksichtigung verschiedener WordPress-Authentifizierungsfehler
- **UI/UX-Verbesserungen**:
- Neuer Status-Badge für "WordPress Pending" mit eigenem Styling
- Dashboard zeigt WordPress-spezifische Statistiken
- Konfigurationshilfen und .env-Vorlagen im WordPress-Tab
- Massenupload-Funktionalität mit Progress-Feedback
### 🛠 Interne Änderungen
- `main.py` erweitert um `upload_articles_to_wp()` Funktion
- `VALID_STATUSES` um "WordPress Pending" erweitert
- Neue Umgebungsvariablen für WordPress-Konfiguration
- Erweiterte Artikel-Datenstruktur um WordPress-spezifische Felder
- Session-Management für HTTP-Verbindungen implementiert
### 📁 Neue Dateien
- `utils/wordpress_uploader.py` - Vollständige WordPress REST API-Integration
- Erweiterte `.env`-Vorlage mit WordPress-Konfiguration
### 🔒 Sicherheit
- WordPress-Credentials werden sicher über Umgebungsvariablen verwaltet
- Basic Auth über Anwendungspasswort (sicherer als Haupt-Login)
- Keine sensiblen Daten in Logs oder Fehlermeldungen
---
## [v1.5.3] - 2025-07-11
### ✨ Neue Funktionen
- Automatischer Volltextabruf bei zu kurzen Artikeln (< 50 Wörter)
- Inhalte werden direkt von der Originalseite geladen (ähnlich wie bei der Bildextraktion)
- Promobil, Camping-News und andere gängige WordPress-Seiten werden unterstützt
- Neue Verwaltungsseite `Feed-Verwaltung` unter `pages/01_feed_manager.py`
- RSS-Feeds können nun über eine dedizierte Oberfläche hinzugefügt, bearbeitet und gelöscht werden
- Anzahl verknüpfter Artikel pro Feed wird angezeigt
- Änderungen werden protokolliert und per `st.rerun()` sofort sichtbar
### 🔧 Verbesserungen
- Feed-Filter in der Artikelübersicht zeigt jetzt die **korrekten Feed-Namen mit Artikelanzahl**
- Beispiel: „Promobil News (12)" statt nur „Alle (20)"
- Basierend auf `source`-Feld im Artikelobjekt
- Verbesserte Logging-Ausgaben bei Feed-Aktionen (hinzufügen, ändern, löschen)
### 📁 Neue Dateien
- `utils/article_extractor.py` Logik zum Abrufen vollständiger Artikeltexte von Originalseiten
- `pages/01_feed_manager.py` Eigenständige Verwaltungsseite für RSS-Feeds
### 🛠 Interne Änderungen
- `main.py` erweitert: Automatischer Fallback auf `extract_full_article()` bei zu kurzem Text
- Logging konsolidiert und mit Feed-Aktionen ergänzt
## [v1.5.2] - 2025-07-09
- Fehlerbehandlung bei `CHANGELOG.md`-Doppelungen hinzugefügt
- Signaturlogik robuster (SSH, GPG, fallback)
- Farbige Terminalausgabe verbessert
- dry-run Argument hinzugefügt:
* Versionsnummer wird berechnet ✅
* Änderungen (Version, Changelog, Commit, Tag, Push) werden nur angezeigt, nicht ausgeführt ✅
* Ausgabe erfolgt farbig und klar gegliedert ✅
## [1.5.1] - 2025-07-09
SSH-Commit-Signatur in versioning.py eingebaut
Automatischer Fallback auf GPG oder keine Signatur
Farbige Terminalausgabe zur Signaturmethode
Readme erweitert mit Setup-Anleitung
## [v1.5.0] 2025-07-08
### 💡 Neue Funktionen
- 🪄 DALL·E-Bildgenerierung per Button direkt im Artikel-Expander
- Automatische Metadaten (Caption, Copyright, Quelle) für KI-generierte Bilder
### 🔧 Änderungen & Fixes
- 🔒 Kritischer Bugfix: Artikel gingen nach DALL·E oder Rewrite verloren → jetzt sichere `save_articles()`-Logik über alle Artikel
- Status-Änderungen, Rewrite und Bilderfassung überschreiben nicht mehr die Gesamtdatei
- Kein `st.rerun()` mehr nach jedem Klick flüssiger Workflow
### 📦 Internes
- Neue Datei `utils/dalle_generator.py` für DALL·E-Integration
- Erweiterung der Teststrategie um strukturierte `TEST-CHECKLIST.md`
- Verbesserte Update-Strategie für Einzelartikel bei Bearbeitung
## [v1.4.8] 2025-07-07
### 💡 Neue Funktionen
-
### 🔧 Änderungen & Fixes
- Fehlerbehebung bei neuen Release, CHANGELOG wurde nicht angehangen, es wird nun die gesamte Datei übernommen
### 📦 Internes
-
## [v1.4.7] 2025-07-07
### 💡 Neue Funktionen
- Automatischer Release-Workflow bei `git tag v*`
- Release-Text aus `CHANGELOG.md` wird extrahiert und als GitHub Release verwendet
### 🔧 Änderungen & Fixes
- Fehlerbehebung bei neuen Release, CHANGELOG wurde nicht angehangen
### 📦 Internes
- Erweiterte `release.yml` zur zuverlässigen Release-Erstellung
- GitHub Actions mit `softprops/action-gh-release`
## [v1.4.6] 2025-07-07
### 💡 Neue Funktionen
- Automatischer Release-Workflow bei `git tag v*`
- Release-Text aus `CHANGELOG.md` wird extrahiert und als GitHub Release verwendet
### 🔧 Änderungen & Fixes
- Fehlerbehebung bei Bilddatenextraktion
- Erweiterung von `versioning.py` um automatische Tag-Erstellung und Push
### 📦 Internes
- Erweiterte `release.yml` zur zuverlässigen Release-Erstellung
- GitHub Actions mit `softprops/action-gh-release`
## [v1.4.5] 2025-07-07
### 💡 Neue Funktionen
- Umstellung des versioning.py-Skripts auf eine moderne Typer-CLI:
- create zum Erstellen neuer Versionen mit Level und Push-Option
- rollback zum Zurücknehmen der letzten Version
- list zur Anzeige aller Versionen im CHANGELOG.md
- Validierung, ob der CHANGELOG.md-Eintrag vor Release wirklich ausgefüllt wurde
- Interaktive CLI-Prompts zur besseren Benutzerführung
### 🔧 Änderungen & Fixes
- versioning.py ersetzt bisherige manuelle Menüs durch Typer-Kommandos
- requirements.txt um typer[all]==0.12.3 ergänzt
### 📦 Internes
- Vorbereitung für globale CLI-Nutzung (versioning als Befehl möglich)
- Automatisierung des Release-Prozesses mit GitHub Actions weiterhin vorbereitet
## [v1.4.4] 2025-07-07
### 💡 Neue Funktionen
-
### 🔧 Änderungen & Fixes
-
### 📦 Internes
- automatische Versionierung eingebunden und direktes GitHub puschen der Änderungen
- ## [v1.4.3] 2025-07-07
### 💡 Neue Funktionen
- ⚠️ Visuelle Warnanzeige in der Artikeltabelle für unvollständige Bildmetadaten (fehlende Caption, Copyright oder Quelle)
- ✍️ Inline-Bearbeitung von Bilddaten (Caption, Copyright, Quelle) direkt in der Detailansicht
- 🪵 Neue separate Seite `Log-Viewer` zur Anzeige der letzten Log-Einträge (automatisch über `pages/log_viewer.py`)
- 📂 Startfilter für Artikelansicht auf „New" voreingestellt für fokussierten Workflow
### 🔧 Änderungen & Fixes
- ✅ Artikel aus Feeds überschreiben bestehende Artikel **nicht mehr** Status, Tags und andere manuelle Änderungen bleiben erhalten
- 🧹 `get_recent_logs()` wurde entfernt und die Sidebar-Logausgabe aus `app.py` entfernt
- 🔗 Sidebar-Link zur Log-Seite hinzugefügt (mittlerweile durch native Seiten-Navigation ersetzt)
- 🧭 Navigation durch Nutzung von Streamlit-Multipage-Struktur (`pages/`)
### 📦 Internes
- Refactoring von `process_articles()` zur sicheren ID-basierten Artikelzusammenführung
- Verbesserte Logging-Ausgabe bei bereits vorhandenen Artikeln
- Robusteres Fehlerhandling in `image_extractor.py`
## [v1.4.2] 2025-07-03
### 💡 Neue Funktionen
- Komplett überarbeitete Artikel-Tabelle mit:
- Auswahlcheckboxen
- Inline-Statuswechsel mit Dropdown
- Wortanzahl, Tag-Anzeige, Datum kompakt
- Copy-to-Clipboard Funktion für Titel, Text und Tags
- Bildanzeige inkl. Caption und Copyright-Quelle im Detailbereich
- Titel wird automatisch beim Kopieren des Texts vorangestellt
### 🔧 Änderungen & Fixes
- `st.experimental_rerun()` durch `st.rerun()` ersetzt
- Statusfilter „Alle" funktioniert jetzt korrekt
- UI-Tuning für bessere Lesbarkeit
- Feedliste aus der Sidebar entfernt
- Fix: Bilddaten ohne Caption verursachen keine Fehler mehr
- Artikelüberschriften korrekt in Kopiertext eingebaut
### 📦 Internes
- Logging bleibt aktiv im Verzeichnis `/logs`
- Vorbereitung für Bildquellen-Import aus Original-Artikel umgesetzt
## [1.4.1] 2025-07-03
### Hinzugefügt
- Logging für `process_articles()`, damit nachvollziehbar ist, welche Feeds verarbeitet wurden
- Rückmeldung in der App bei Klick auf „Alle Feeds neu laden"
### Geändert
- `main.py`: Inhalte aus `content`, `summary` oder `description` werden vollständig geladen und mit `BeautifulSoup` bereinigt
- Sicherstellung, dass `fetch_and_process_feed()` alle relevanten Artikelinformationen vollständig speichert
### Fehlerbehebungen
- Problem behoben, bei dem Artikeltexte nicht vollständig übernommen wurden
## [1.3.1] 2025-07-03
### Added
- Tabellenansicht mit Checkbox, Titel, Datum, Zusammenfassung, Wortanzahl, Tags, Status
- Direktes Bearbeiten des Status über Dropdown-Menü
- Massenbearbeitung von Artikeln per Checkbox
- Rewrite-Button für alle Artikel mit Status 'Rewrite'
## [1.2.0] - 2025-07-04
### Hinzugefügt
- Automatische Bilderkennung beim Einlesen von Artikeln
- Extrahieren von Bildern aus dem Originalartikel (bis zu 3 Bilder)
- Speicherung von Bild-URLs, Alt-Texten (Bildbeschreibung) und Copyright-Hinweisen
- Fehlerbehandlung für nicht erreichbare Seiten
- Darstellung der Bilder (inkl. Beschreibung & Copyright) in der Artikelansicht
### Geändert
- Bilder werden direkt beim Einlesen eines RSS-Artikels verarbeitet und gespeichert
- `app.py` zeigt nun auch Bildinformationen innerhalb der Artikeldetailansicht an
### Behoben
- Keine
---
## [1.1.0] - 2025-07-04
### Hinzugefügt
- Visuell aufgewertete Box zur Darstellung eines Artikels mit:
- Kopierbutton für Titel
- Kopierbutton für Artikeltext
- Kopierbutton für Tags
- Button zum Öffnen des Originalartikels im neuen Tab
- Artikelansicht ist nun in einer grauen, abgerundeten Box gekapselt
- Icons unterstützen visuelle Orientierung (📝, 🗌, 📌 etc.)
### Geändert
- Artikelkopierfunktion für WordPress ist nun interaktiv über Buttons möglich
- HTML-Markup innerhalb von Streamlit für flexibleres Styling
### Behoben
- Keine
---
## [1.0.0] - 2025-07-03
### Initialversion
- Artikel aus RSS-Feeds einlesen
- Speichern in JSON-Datei
- Anzeige in Tabelle mit Statusfilter
- Rewrite per ChatGPT mit Zusammenfassung und Tag-Generierung
- Exportierbare Inhalte für manuelles Posting auf WordPress
----
## [v1.6.0] - 2025-08-15
### 🎨 Komplette UI-Überarbeitung
- **Modernes Tab-basiertes Design** mit Dashboard, Artikel, Feeds, Bilder und Statistiken-Tabs
- **Card-basierte Artikelansicht** ersetzt die alte Tabellenstruktur
- **Gradient-Header** und moderne CSS-Styling für professionelleres Aussehen
- **Responsive Layout** mit verbesserter mobiler Darstellung
- **Status-Badges** mit farbkodierten Indikatoren
- **Toast-Benachrichtigungen** für besseres User-Feedback
### 🔍 Erweiterte Filter- und Suchfunktionen
- **Kombinierte Filter** für Status, Feed und Volltextsuche
- **Live-Suche** durch Titel, Inhalt und Tags
- **Feed-spezifische Filterung** mit Artikelanzahl-Anzeige
- **Session State Management** für persistente Filter-Einstellungen
### 📊 Neues Dashboard
- **Statistik-Karten** mit visuellen Metriken (Gesamt-Artikel, neue Artikel, Feeds, Online-Artikel)
- **Schnellaktionen** für häufige Aufgaben (Feed-Update, Rewrite, Aufräumen)
- **Neueste Artikel Preview** mit Status-Anzeige
- **Übersichtliche Zahlen** mit modernem Design
### 🖼️ Verbesserte Bildverwaltung
- **Dedizierte Bilder-Seite** mit Galerie-Ansicht
- **Erweiterte Bildextraktion** mit Featured Image Detection
- **OpenGraph und Twitter Card** Unterstützung
- **Intelligente Bildfilterung** (Größe, Typ, Blacklist)
- **Metadaten-Bereinigung** mit Fallback-Werten
### 📰 Optimierte Artikelverarbeitung
- **Erweiterte Duplikatserkennung** basierend auf Titel-Ähnlichkeit und URL
- **Verbesserte Volltextextraktion** mit website-spezifischen Selektoren
- **WordPress-Erkennung** für optimierte Content-Extraktion
- **Retry-Mechanismus** mit exponential backoff
- **Bessere Textbereinigung** und Validierung
### 🛠️ Backend-Verbesserungen
- **Strukturiertes Logging** mit Funktions- und Zeilennummern
- **Session State Management** für bessere Performance
- **Verbesserte Fehlerbehandlung** mit spezifischen Error-Messages
- **JSON-Validierung** vor dem Speichern
- **Encoding-Fixes** für internationale Zeichen
- **Memory-optimierte Verarbeitung**
### 📊 Neue Statistiken-Seite
- **Status-Verteilung** mit Prozentanzeigen
- **Feed-Artikel-Übersicht** sortiert nach Anzahl
- **Textstatistiken** (Durchschnitt, Min/Max Wortanzahl)
- **Tag-Häufigkeiten** der meist verwendeten Tags
- **Lesezeit-Berechnungen** (200 Wörter pro Minute)
### 🔧 Technische Verbesserungen
- **UI Helper Functions** in `utils/ui_helpers.py` für wiederverwendbare Komponenten
- **Verbesserte URL-Validierung** und Domain-Erkennung
- **Smart Content Selectors** für verschiedene Website-Typen
- **Robustes Error Handling** mit spezifischen Fehlermeldungen
- **Performance-Optimierungen** durch reduzierte `st.rerun()` Calls
- **Memory-Management** für große Artikel-Listen
### 📱 UX-Verbesserungen
- **Inline-Bearbeitung** von Artikel-Status direkt in der Card-Ansicht
- **Erweiterte Details-Ansicht** mit Collapsible-Bereichen
- **Copy-to-Clipboard** Funktionalität mit formatiertem Text
- **Hover-Effekte** und Animations für bessere Interaktion
- **Breadcrumb-Navigation** in komplexen Ansichten
- **Loading-Spinner** für längere Operationen
### 🗂️ Neue Dateistruktur
```
├── app.py (komplett überarbeitet)
├── main.py (erweiterte Backend-Logik)
├── utils/
│ ├── ui_helpers.py (neue UI-Komponenten)
│ ├── image_extractor.py (verbesserte Bildextraktion)
│ ├── article_extractor.py (erweiterte Artikelextraktion)
│ └── dalle_generator.py (unverändert)
├── pages/
│ ├── 01_feed_manager.py (bestehend)
│ └── log_viewer.py (bestehend)
└── logs/ (verbessertes Logging)
```
### 🔄 Migration & Kompatibilität
- **Vollständige Rückwärtskompatibilität** mit bestehenden JSON-Daten
- **Automatische Datenmigration** für neue Felder (source_name, word_count, etc.)
- **Graceful Degradation** bei fehlenden Feldern
- **Validierung und Reparatur** ungültiger Datenstrukturen
### ⚡ Performance-Optimierungen
- **Lazy Loading** für große Artikel-Listen
- **Effiziente Filtering** ohne komplette Neuladung
- **Optimierte Bildverarbeitung** mit Größen-Caching
- **Reduzierte API-Calls** durch besseres State Management
- **Memory-optimierte JSON-Verarbeitung**
### 🐛 Bugfixes
- **Status-Änderungen** gehen nicht mehr verloren nach Reload
- **Bildmetadaten** werden korrekt gespeichert und angezeigt
- **Duplikat-Artikel** werden zuverlässig erkannt
- **Encoding-Probleme** bei internationalen Zeichen behoben
- **Feed-Namen** werden korrekt in Filter-Dropdown angezeigt
- **Session State** Konflikte bei mehreren Tabs behoben
### 📋 Breaking Changes
- **Alte Tabellen-UI** wurde durch Card-Layout ersetzt
- **Sidebar-Navigation** wurde durch Tab-Navigation ersetzt
- **Direkte JSON-Manipulation** sollte vermieden werden (neue Validierung)
---
## [v1.5.3] - 2025-07-11 ## [v1.5.3] - 2025-07-11
### ✨ Neue Funktionen ### ✨ Neue Funktionen

View file

@ -1 +1 @@
VERSION = "1.5.3" VERSION = "1.6.2"

358
app.py
View file

@ -8,9 +8,11 @@ from main import (
load_articles, load_articles,
save_articles, save_articles,
process_articles, process_articles,
rewrite_articles rewrite_articles,
upload_articles_to_wp
) )
from utils.dalle_generator import generate_dalle_image from utils.dalle_generator import generate_dalle_image
from utils.wordpress_uploader import WordPressUploader
import os import os
from collections import Counter from collections import Counter
import time import time
@ -66,6 +68,7 @@ st.markdown("""
.status-online { background-color: #e8f5e8; color: #388e3c; } .status-online { background-color: #e8f5e8; color: #388e3c; }
.status-hold { background-color: #fce4ec; color: #c2185b; } .status-hold { background-color: #fce4ec; color: #c2185b; }
.status-trash { background-color: #ffebee; color: #d32f2f; } .status-trash { background-color: #ffebee; color: #d32f2f; }
.status-wp-pending { background-color: #e1f5fe; color: #0277bd; }
/* Filter Section */ /* Filter Section */
.filter-section { .filter-section {
@ -107,6 +110,15 @@ st.markdown("""
min-width: 200px; min-width: 200px;
text-align: center; text-align: center;
} }
/* WordPress Upload Status */
.wp-status {
background: #e3f2fd;
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
border-left: 4px solid #2196f3;
}
</style> </style>
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
@ -129,7 +141,8 @@ def get_status_badge(status):
"Process": "status-process", "Process": "status-process",
"Online": "status-online", "Online": "status-online",
"On Hold": "status-hold", "On Hold": "status-hold",
"Trash": "status-trash" "Trash": "status-trash",
"WordPress Pending": "status-wp-pending"
} }
class_name = status_classes.get(status, "status-new") class_name = status_classes.get(status, "status-new")
return f'<span class="status-badge {class_name}">{status}</span>' return f'<span class="status-badge {class_name}">{status}</span>'
@ -159,21 +172,31 @@ def show_notification(message, type="success"):
elif type == "info": elif type == "info":
st.info(message) st.info(message)
def test_wordpress_connection():
"""Testet die WordPress-Verbindung"""
try:
uploader = WordPressUploader()
success, message = uploader.test_connection()
return success, message
except Exception as e:
return False, f"Fehler beim Testen der Verbindung: {str(e)}"
# === Header === # === Header ===
st.markdown(""" st.markdown("""
<div class="main-header"> <div class="main-header">
<h1>📰 RSS Artikel Manager</h1> <h1>📰 RSS Artikel Manager</h1>
<p>Moderne Verwaltung deiner RSS-Feeds und Artikel</p> <p>Moderne Verwaltung deiner RSS-Feeds und Artikel mit WordPress-Integration</p>
</div> </div>
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
# === Tab Navigation === # === Tab Navigation ===
tab1, tab2, tab3, tab4, tab5 = st.tabs([ tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
"📋 Dashboard", "📋 Dashboard",
"📰 Artikel", "📰 Artikel",
"📡 Feeds", "📡 Feeds",
"🖼️ Bilder", "🖼️ Bilder",
"📊 Statistiken" "📊 Statistiken",
"🔧 WordPress"
]) ])
# === Dashboard Tab === # === Dashboard Tab ===
@ -185,7 +208,7 @@ with tab1:
feeds = load_feeds() feeds = load_feeds()
# Statistiken # Statistiken
col1, col2, col3, col4 = st.columns(4) col1, col2, col3, col4, col5 = st.columns(5)
with col1: with col1:
st.markdown(""" st.markdown("""
@ -205,14 +228,24 @@ with tab1:
""".format(new_count), unsafe_allow_html=True) """.format(new_count), unsafe_allow_html=True)
with col3: with col3:
process_count = len([a for a in all_articles if a.get("status") == "Process"])
st.markdown(""" st.markdown("""
<div class="stats-card"> <div class="stats-card">
<div class="stats-number">{}</div> <div class="stats-number">{}</div>
<div>RSS Feeds</div> <div>Bereit für WP</div>
</div> </div>
""".format(len(feeds)), unsafe_allow_html=True) """.format(process_count), unsafe_allow_html=True)
with col4: with col4:
wp_pending_count = len([a for a in all_articles if a.get("status") == "WordPress Pending"])
st.markdown("""
<div class="stats-card">
<div class="stats-number">{}</div>
<div>WP Ausstehend</div>
</div>
""".format(wp_pending_count), unsafe_allow_html=True)
with col5:
online_count = len([a for a in all_articles if a.get("status") == "Online"]) online_count = len([a for a in all_articles if a.get("status") == "Online"])
st.markdown(""" st.markdown("""
<div class="stats-card"> <div class="stats-card">
@ -226,7 +259,7 @@ with tab1:
# Quick Actions # Quick Actions
st.subheader("⚡ Schnellaktionen") st.subheader("⚡ Schnellaktionen")
col1, col2, col3 = st.columns(3) col1, col2, col3, col4 = st.columns(4)
with col1: with col1:
if st.button("🔄 Alle Feeds aktualisieren", use_container_width=True): if st.button("🔄 Alle Feeds aktualisieren", use_container_width=True):
@ -250,6 +283,32 @@ with tab1:
show_notification("Keine Artikel zum Umschreiben gefunden.", "info") show_notification("Keine Artikel zum Umschreiben gefunden.", "info")
with col3: with col3:
if st.button("📤 WordPress Upload", use_container_width=True):
process_count = len([a for a in all_articles if a.get("status") == "Process"])
if process_count > 0:
with st.spinner(f"{process_count} Artikel werden zu WordPress hochgeladen..."):
upload_results = upload_articles_to_wp()
if upload_results.get('error'):
show_notification(f"Fehler beim WordPress-Upload: {upload_results['error']}", "error")
else:
successful = upload_results.get('successful', 0)
failed = upload_results.get('failed', 0)
duplicates = upload_results.get('duplicates', 0)
if successful > 0:
show_notification(f"{successful} Artikel erfolgreich zu WordPress hochgeladen!")
if failed > 0:
show_notification(f"⚠️ {failed} Artikel konnten nicht hochgeladen werden.", "warning")
if duplicates > 0:
show_notification(f" {duplicates} Duplikate übersprungen.", "info")
time.sleep(2)
st.rerun()
else:
show_notification("Keine Artikel für WordPress-Upload gefunden.", "info")
with col4:
if st.button("🧹 Aufräumen", use_container_width=True): if st.button("🧹 Aufräumen", use_container_width=True):
trash_count = len([a for a in all_articles if a.get("status") == "Trash"]) trash_count = len([a for a in all_articles if a.get("status") == "Trash"])
if trash_count > 0: if trash_count > 0:
@ -257,6 +316,21 @@ with tab1:
else: else:
show_notification("Keine Artikel zum Aufräumen gefunden.", "info") show_notification("Keine Artikel zum Aufräumen gefunden.", "info")
# WordPress-Status-Übersicht
if wp_pending_count > 0 or online_count > 0:
st.subheader("🔗 WordPress-Status")
wp_articles = [a for a in all_articles if a.get("status") in ["WordPress Pending", "Online"]]
for article in wp_articles[:5]: # Nur die ersten 5 anzeigen
st.markdown(f"""
<div class="wp-status">
<strong>{article.get('title', 'Kein Titel')}</strong>
{get_status_badge(article.get('status', 'Unknown'))}
<br>
<small>WP Post ID: {article.get('wp_post_id', 'Unbekannt')} | Upload: {format_date(article.get('wp_upload_date', ''))}</small>
</div>
""", unsafe_allow_html=True)
# Neueste Artikel Preview # Neueste Artikel Preview
st.subheader("🕒 Neueste Artikel") st.subheader("🕒 Neueste Artikel")
recent_articles = sorted(all_articles, key=lambda x: x.get("date", ""), reverse=True)[:5] recent_articles = sorted(all_articles, key=lambda x: x.get("date", ""), reverse=True)[:5]
@ -288,7 +362,7 @@ with tab2:
col1, col2, col3 = st.columns(3) col1, col2, col3 = st.columns(3)
with col1: with col1:
status_options = ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"] status_options = ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash", "WordPress Pending"]
st.session_state.status_filter = st.selectbox( st.session_state.status_filter = st.selectbox(
"Status", "Status",
status_options, status_options,
@ -366,6 +440,10 @@ with tab2:
st.markdown(f"**{title}**") st.markdown(f"**{title}**")
st.markdown(f"📅 {format_date(article.get('date', ''))}") st.markdown(f"📅 {format_date(article.get('date', ''))}")
# WordPress-Info anzeigen falls vorhanden
if article.get("wp_post_id"):
st.markdown(f"🔗 WordPress ID: {article.get('wp_post_id')} | Upload: {format_date(article.get('wp_upload_date', ''))}")
with col2: with col2:
st.markdown(get_status_badge(article.get("status", "New")), unsafe_allow_html=True) st.markdown(get_status_badge(article.get("status", "New")), unsafe_allow_html=True)
@ -388,11 +466,11 @@ with tab2:
st.markdown(f"📡 {source_name}") st.markdown(f"📡 {source_name}")
# Actions # Actions
col1, col2, col3, col4 = st.columns(4) col1, col2, col3, col4, col5 = st.columns(5)
with col1: with col1:
# Status ändern # Status ändern
status_options = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"] status_options = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash", "WordPress Pending"]
current_status = article.get("status", "New") current_status = article.get("status", "New")
new_status = st.selectbox( new_status = st.selectbox(
"Status", "Status",
@ -423,6 +501,30 @@ with tab2:
st.markdown(f"[🔗 Artikel öffnen]({article.get('link', '#')})") st.markdown(f"[🔗 Artikel öffnen]({article.get('link', '#')})")
with col4: with col4:
# WordPress Upload Button für einzelne Artikel
if article.get("status") == "Process":
if st.button("📤 WordPress", key=f"wp_upload_{article['id']}"):
with st.spinner("Lade zu WordPress hoch..."):
from utils.wordpress_uploader import upload_single_article_to_wordpress
success, message, wp_post_id = upload_single_article_to_wordpress(article)
if success:
# Status ändern
for idx, art in enumerate(all_articles):
if art["id"] == article["id"]:
all_articles[idx]["status"] = "WordPress Pending"
all_articles[idx]["wp_upload_date"] = datetime.now().isoformat()
all_articles[idx]["wp_post_id"] = wp_post_id
break
save_articles(all_articles)
show_notification("✅ Erfolgreich zu WordPress hochgeladen!")
else:
show_notification(f"❌ WordPress-Upload fehlgeschlagen: {message}", "error")
time.sleep(1)
st.rerun()
with col5:
# Details anzeigen # Details anzeigen
if st.button("📖 Details", key=f"details_{article['id']}"): if st.button("📖 Details", key=f"details_{article['id']}"):
st.session_state[f"show_details_{article['id']}"] = not st.session_state.get(f"show_details_{article['id']}", False) st.session_state[f"show_details_{article['id']}"] = not st.session_state.get(f"show_details_{article['id']}", False)
@ -641,6 +743,39 @@ with tab5:
for feed_name, count in feed_counts.most_common(): for feed_name, count in feed_counts.most_common():
st.markdown(f"**{feed_name}:** {count} Artikel") st.markdown(f"**{feed_name}:** {count} Artikel")
# WordPress-Statistiken
st.subheader("🔗 WordPress-Statistiken")
wp_articles = [a for a in all_articles if a.get("wp_post_id")]
if wp_articles:
col1, col2, col3 = st.columns(3)
with col1:
st.metric("WordPress Artikel", len(wp_articles))
with col2:
pending_count = len([a for a in wp_articles if a.get("status") == "WordPress Pending"])
st.metric("Ausstehend", pending_count)
with col3:
online_wp_count = len([a for a in wp_articles if a.get("status") == "Online"])
st.metric("Online", online_wp_count)
# Neueste WordPress-Uploads
recent_wp = sorted([a for a in wp_articles if a.get("wp_upload_date")],
key=lambda x: x.get("wp_upload_date", ""), reverse=True)[:5]
if recent_wp:
st.subheader("🕒 Neueste WordPress-Uploads")
for article in recent_wp:
st.markdown(f"""
**{article.get('title', 'Kein Titel')}** {get_status_badge(article.get('status', 'Unknown'))}
WP ID: {article.get('wp_post_id')} | Upload: {format_date(article.get('wp_upload_date', ''))}
""", unsafe_allow_html=True)
else:
st.info("Noch keine Artikel zu WordPress hochgeladen.")
# Weitere Statistiken # Weitere Statistiken
st.subheader("📝 Textstatistiken") st.subheader("📝 Textstatistiken")
@ -668,4 +803,201 @@ with tab5:
for tag, count in tag_counts.most_common(10): for tag, count in tag_counts.most_common(10):
st.markdown(f"**{tag}:** {count}x verwendet") st.markdown(f"**{tag}:** {count}x verwendet")
else: else:
st.info("Keine Tags gefunden.") st.info("Keine Tags gefunden.")
# === WordPress Tab ===
with tab6:
st.header("🔧 WordPress-Integration")
# Verbindungstest
st.subheader("🔗 Verbindungstest")
col1, col2 = st.columns(2)
with col1:
if st.button("🧪 WordPress-Verbindung testen", use_container_width=True):
with st.spinner("Teste Verbindung..."):
success, message = test_wordpress_connection()
if success:
show_notification(f"{message}")
else:
show_notification(f"{message}", "error")
with col2:
# WordPress-Konfiguration anzeigen
wp_url = os.getenv("WP_BASE_URL", "Nicht konfiguriert")
wp_user = os.getenv("WP_USERNAME", "Nicht konfiguriert")
wp_base64 = os.getenv("WP_AUTH_BASE64", "")
st.info(f"""
**WordPress-Konfiguration:**
- URL: {wp_url}
- Benutzer: {wp_user}
- Passwort: {'✅ Konfiguriert' if os.getenv("WP_PASSWORD") else '❌ Nicht konfiguriert'}
- Base64 Auth: {'✅ Konfiguriert' if wp_base64 else '❌ Nicht konfiguriert'}
""")
# WordPress Auth Debug (nur für Entwicklung)
if st.checkbox("🔧 Debug-Modus (Auth-Details anzeigen)", value=False):
st.warning("⚠️ Nur für Entwicklung - zeigt Auth-Details!")
wp_base64 = os.getenv("WP_AUTH_BASE64", "")
if wp_base64:
try:
import base64
decoded = base64.b64decode(wp_base64).decode('utf-8')
st.code(f"Base64: {wp_base64}\nDecoded: {decoded}")
except Exception as e:
st.error(f"Fehler beim Dekodieren: {e}")
else:
st.info("Kein Base64-String konfiguriert")
# Bulk Upload
st.subheader("📦 Massenupload")
process_articles_list = [a for a in all_articles if a.get("status") == "Process"]
if process_articles_list:
st.write(f"**{len(process_articles_list)} Artikel bereit für WordPress-Upload:**")
# Artikel-Vorschau
for article in process_articles_list[:5]: # Nur die ersten 5 anzeigen
st.markdown(f"• **{article.get('title', 'Kein Titel')}** ({get_word_count(article.get('text', ''))} Wörter)")
if len(process_articles_list) > 5:
st.markdown(f"... und {len(process_articles_list) - 5} weitere")
col1, col2 = st.columns(2)
with col1:
if st.button("📤 Alle zu WordPress hochladen", use_container_width=True):
with st.spinner(f"Lade {len(process_articles_list)} Artikel zu WordPress hoch..."):
upload_results = upload_articles_to_wp()
# Detaillierte Ergebnisse anzeigen
st.subheader("📊 Upload-Ergebnisse")
if upload_results.get('error'):
show_notification(f"❌ Fehler: {upload_results['error']}", "error")
else:
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Erfolgreich", upload_results.get('successful', 0))
with col2:
st.metric("Fehlgeschlagen", upload_results.get('failed', 0))
with col3:
st.metric("Duplikate", upload_results.get('duplicates', 0))
# Details anzeigen
if upload_results.get('details'):
st.subheader("📋 Upload-Details")
for detail in upload_results['details']:
status_icon = "" if detail['success'] else ""
st.markdown(f"{status_icon} **{detail['title']}**: {detail['message']}")
time.sleep(2)
st.rerun()
with col2:
st.info("💡 Artikel erhalten den Status 'WordPress Pending' nach erfolgreichem Upload.")
else:
st.info("Keine Artikel mit Status 'Process' gefunden. Artikel müssen zuerst umgeschrieben werden.")
# WordPress-Artikel-Übersicht
st.subheader("📋 WordPress-Artikel-Übersicht")
wp_articles = [a for a in all_articles if a.get("wp_post_id")]
if wp_articles:
# Filter für WordPress-Artikel
wp_status_filter = st.selectbox(
"WordPress-Status filtern",
["Alle", "WordPress Pending", "Online"],
key="wp_status_filter"
)
filtered_wp_articles = wp_articles
if wp_status_filter != "Alle":
filtered_wp_articles = [a for a in wp_articles if a.get("status") == wp_status_filter]
st.write(f"**{len(filtered_wp_articles)} WordPress-Artikel gefunden**")
# WordPress-Artikel anzeigen
for article in filtered_wp_articles:
st.markdown(f"""
<div class="wp-status">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<strong>{article.get('title', 'Kein Titel')}</strong>
<br>
<small>WP ID: {article.get('wp_post_id')} | Upload: {format_date(article.get('wp_upload_date', ''))}</small>
</div>
<div>
{get_status_badge(article.get('status', 'Unknown'))}
</div>
</div>
</div>
""", unsafe_allow_html=True)
else:
st.info("Noch keine Artikel zu WordPress hochgeladen.")
# Konfigurationshilfe
st.subheader("⚙️ Konfiguration")
with st.expander("📋 .env-Datei Vorlage", expanded=False):
st.code("""
# WordPress-Konfiguration
WP_BASE_URL=https://vanityontour.de
WP_USERNAME=ogiertz
WP_PASSWORD=whNEx9aZCIUXViV89Z3e7Z03
# WordPress Base64-Authentifizierung (bevorzugte Methode)
WP_AUTH_BASE64=b2dpZXJ0ejp3aE5FeDlhWkNJVVhWaVY4OVozZTdaMDM=
# OpenAI-Konfiguration (für Artikel-Umschreibung)
OPENAI_API_KEY=sk-...
""", language="bash")
with st.expander("🔑 Base64-Authentifizierung verstehen", expanded=False):
st.markdown("""
**WordPress REST API Authentifizierung:**
Die WordPress REST API erfordert eine Base64-kodierte Authentifizierung im Format:
```
Authorization: Basic <base64_encoded_credentials>
```
**Ihr bereitgestellter Base64-String:**
- `b2dpZXJ0ejp3aE5FeDlhWkNJVVhWaVY4OVozZTdaMDM=`
- Dekodiert: `ogiertz:whNEx9aZCIUXViV89Z3e7Z03`
**So funktioniert es:**
1. Benutzername und Anwendungspasswort werden kombiniert: `username:password`
2. Dieser String wird Base64-kodiert
3. Im Authorization-Header verwendet: `Basic <base64_string>`
**Fallback-Verhalten:**
- Wenn `WP_AUTH_BASE64` gesetzt ist Direkter Base64-String verwendet
- Wenn nicht gesetzt Base64 wird aus `WP_USERNAME:WP_PASSWORD` generiert
""")
with st.expander("📖 WordPress-API Berechtigungen", expanded=False):
st.markdown("""
**Erforderliche Berechtigungen für den WordPress-Benutzer:**
- `edit_posts` - Beiträge erstellen und bearbeiten
- `publish_posts` - Beiträge veröffentlichen (für Status-Änderungen)
- `upload_files` - Dateien hochladen (für spätere Bild-Uploads)
- `edit_categories` - Kategorien verwalten
- `edit_tags` - Tags verwalten
**Anwendungspasswort erstellen:**
1. WordPress Admin Benutzer Profil
2. Unter "Anwendungspasswörter" neues Passwort erstellen
3. Name: "RSS Feed Manager"
4. Generiertes Passwort in .env-Datei eintragen
""")

File diff suppressed because one or more lines are too long

View file

@ -511,3 +511,292 @@ openai.APIConnectionError: Connection error.
2025-08-15 09:55:42,843 - INFO - Lade Feed: https://www.promobil.de/rss/ratgeber 2025-08-15 09:55:42,843 - INFO - Lade Feed: https://www.promobil.de/rss/ratgeber
2025-08-15 09:55:43,180 - INFO - 0 neue Artikel gefunden in https://www.promobil.de/rss/ratgeber 2025-08-15 09:55:43,180 - INFO - 0 neue Artikel gefunden in https://www.promobil.de/rss/ratgeber
2025-08-15 09:55:43,180 - INFO - Keine neuen Artikel gefunden. 2025-08-15 09:55:43,180 - INFO - Keine neuen Artikel gefunden.
2025-08-16 12:11:35,892 - INFO - load_articles:123 - ✅ 53 Artikel geladen
2025-08-16 12:11:35,893 - INFO - load_feeds:92 - ✅ 3 Feeds geladen
2025-08-16 12:42:43,093 - INFO - load_articles:124 - ✅ 53 Artikel geladen
2025-08-16 12:42:43,095 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:48:41,965 - INFO - load_articles:124 - ✅ 53 Artikel geladen
2025-08-16 12:48:41,966 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:49:23,544 - INFO - load_articles:124 - ✅ 53 Artikel geladen
2025-08-16 12:49:23,544 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:49:23,547 - INFO - process_articles:268 - 🚀 Starte Artikel-Verarbeitung
2025-08-16 12:49:23,547 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:49:23,554 - INFO - load_articles:124 - ✅ 53 Artikel geladen
2025-08-16 12:49:23,554 - INFO - fetch_and_process_feed:174 - 🔄 Verarbeite Feed: https://www.camping-news.de/rss/
2025-08-16 12:49:25,076 - INFO - fetch_and_process_feed:181 - 📡 Feed-Name: Camping-News
2025-08-16 12:49:25,076 - INFO - fetch_and_process_feed:187 - 📰 10 Einträge gefunden
2025-08-16 12:49:25,079 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: 15 Jahre PremiumCamps
2025-08-16 12:49:25,080 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Der neue Eriba Feeling und Novaline
2025-08-16 12:49:25,081 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Den eigenen Verbrauchszahlen auf der Spur
2025-08-16 12:49:25,083 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Abenteuer für die Kleinen, Entspannung für
die Großen
2025-08-16 12:49:25,084 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Das weltweit größte Caravaning-Erlebnis
2025-08-16 12:49:25,086 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Komfort und Flexibilität für moderne Camper
2025-08-16 12:49:25,087 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Frühjahrsaktionen vom Verein WOHNmobil für Klimaschutz
2025-08-16 12:49:25,090 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Viel los auf dem Klaukenhof
2025-08-16 12:49:25,092 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Camping Resort Allweglehen bietet "Wellness plus"
2025-08-16 12:49:25,093 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: EU-Führerscheinreform kommt
2025-08-16 12:49:25,093 - INFO - fetch_and_process_feed:257 - ✅ Feed verarbeitet: 0 neue Artikel aus https://www.camping-news.de/rss/
2025-08-16 12:49:26,098 - INFO - fetch_and_process_feed:174 - 🔄 Verarbeite Feed: https://www.promobil.de/rss/news
2025-08-16 12:49:26,570 - INFO - fetch_and_process_feed:181 - 📡 Feed-Name: News bei www.promobil.de
2025-08-16 12:49:26,571 - INFO - fetch_and_process_feed:187 - 📰 20 Einträge gefunden
2025-08-16 12:49:26,571 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Vanlife-Events im Juni bis November 2025: Events für Bulli-Lover und Campervan-Fans
2025-08-16 12:49:26,571 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/termine-veranstaltungen-juni-juli/ (Versuch 1)
2025-08-16 12:49:27,928 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:27,930 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:27,931 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 649 Wörter
2025-08-16 12:49:27,931 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 649 Wörter
2025-08-16 12:49:27,931 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Vanlife-Events im Juni bis November 2025: Events für Bulli-Lover und Campervan-Fans
2025-08-16 12:49:27,932 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Schutz vor Einbruch und Diebstahl fürs Wohnmobil: Wie schütze ich mein Wohnmobil vor Diebstahl?
2025-08-16 12:49:27,932 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/sicherheitszubehoer-wohnmobil-schutz-einbruch-diebstahl-gase/ (Versuch 1)
2025-08-16 12:49:28,227 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:28,228 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:28,230 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 1134 Wörter
2025-08-16 12:49:28,230 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 1134 Wörter
2025-08-16 12:49:28,230 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Schutz vor Einbruch und Diebstahl fürs Wohnmobil: Wie schütze ich mein Wohnmobil vor Diebstahl?
2025-08-16 12:49:28,231 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Hymer kündigt neue Wohnmobil-Marke Corigon an: Hymer bringt neue Günstig-Marke für Camper
2025-08-16 12:49:28,231 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/corigon-neue-hymer-marke-sommer-2025/ (Versuch 1)
2025-08-16 12:49:28,591 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:28,593 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:28,594 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 874 Wörter
2025-08-16 12:49:28,594 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 874 Wörter
2025-08-16 12:49:28,594 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Hymer kündigt neue Wohnmobil-Marke Corigon an: Hymer bringt neue Günstig-Marke für Camper
2025-08-16 12:49:28,594 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Die besten Camper-Innovationen 2025: „Frosch“ ist das genialste neue Camping-Zubehör
2025-08-16 12:49:28,594 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/sieger-frosch-campfire-der-ideen-2025/ (Versuch 1)
2025-08-16 12:49:30,472 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:30,473 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:30,473 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 452 Wörter
2025-08-16 12:49:30,474 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 452 Wörter
2025-08-16 12:49:30,474 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Die besten Camper-Innovationen 2025: „Frosch“ ist das genialste neue Camping-Zubehör
2025-08-16 12:49:30,474 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Leserstimmen zum Update der 9-Gang-Automatik: Was bringt das Softwareupdate für den Ducato 8?
2025-08-16 12:49:30,475 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/fiat-ducato-9-gang-automatik-softwareupdate/ (Versuch 1)
2025-08-16 12:49:31,156 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:31,157 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:31,160 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 1546 Wörter
2025-08-16 12:49:31,160 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 1546 Wörter
2025-08-16 12:49:31,161 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Leserstimmen zum Update der 9-Gang-Automatik: Was bringt das Softwareupdate für den Ducato 8?
2025-08-16 12:49:31,161 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: CARAVANING Jahrgangs Archiv 2024: CARAVANING fürs Archiv
2025-08-16 12:49:31,161 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/caravaning-jahrgangs-archiv-als-pdf-download/ (Versuch 1)
2025-08-16 12:49:32,094 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:32,095 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:32,095 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 136 Wörter
2025-08-16 12:49:32,096 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 136 Wörter
2025-08-16 12:49:32,096 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: CARAVANING Jahrgangs Archiv 2024: CARAVANING fürs Archiv
2025-08-16 12:49:32,096 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Top 10 Clever-Campen-Videos 2024: Die beliebtesten Camping-Videos
2025-08-16 12:49:32,096 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/top-10-clever-campen-videos-2024/ (Versuch 1)
2025-08-16 12:49:33,747 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:33,748 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:33,749 - INFO - extract_full_article:262 - 🔄 Fallback auf Paragraph-Extraktion
2025-08-16 12:49:33,749 - INFO - extract_from_paragraphs:194 - ✅ Fallback-Extraktion aus 18 Paragraphen
2025-08-16 12:49:33,749 - INFO - extract_full_article:271 - 🔄 Letzter Fallback: Body-Text
2025-08-16 12:49:33,752 - INFO - extract_full_article:281 - ⚠️ Body-Extraktion: 0 Wörter
2025-08-16 12:49:33,752 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Top 10 Clever-Campen-Videos 2024: Die beliebtesten Camping-Videos
2025-08-16 12:49:33,752 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Retro-Campingfahrzeuge aus den Nuller-Jahren: Das waren die heißesten Wohnmobile 2004
2025-08-16 12:49:33,752 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/retro-wohnmobile-2004-nuller-jahre-rueckblick-promobil/ (Versuch 1)
2025-08-16 12:49:34,562 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:34,564 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:34,566 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 1870 Wörter
2025-08-16 12:49:34,567 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 1870 Wörter
2025-08-16 12:49:34,567 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Retro-Campingfahrzeuge aus den Nuller-Jahren: Das waren die heißesten Wohnmobile 2004
2025-08-16 12:49:34,567 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Gaswarner im Wohnmobil: So schütze Sie sich vor unsichtbaren Gasen
2025-08-16 12:49:34,568 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/gaswarner-wohnmobil-schutz-gas/ (Versuch 1)
2025-08-16 12:49:35,350 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:35,352 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:35,354 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 1796 Wörter
2025-08-16 12:49:35,354 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 1796 Wörter
2025-08-16 12:49:35,355 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Gaswarner im Wohnmobil: So schütze Sie sich vor unsichtbaren Gasen
2025-08-16 12:49:35,355 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Preisschock bei Wohnmobilversicherungen: Versicherung gestiegen? Das können Sie tun!
2025-08-16 12:49:35,355 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/versicherungen-werden-2025-teuerer-tipps-und-tricks-um-die-kosten-zu-senken/ (Versuch 1)
2025-08-16 12:49:35,626 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:35,627 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:35,628 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 942 Wörter
2025-08-16 12:49:35,629 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 942 Wörter
2025-08-16 12:49:35,629 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Preisschock bei Wohnmobilversicherungen: Versicherung gestiegen? Das können Sie tun!
2025-08-16 12:49:35,629 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Dailycamper Trekking (2025): Neuer Mini-Camper auf Fiat Doblo
2025-08-16 12:49:35,629 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/dailycamper-trekking-mini-camper-fiat-doblo/ (Versuch 1)
2025-08-16 12:49:35,899 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:35,900 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:35,901 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 416 Wörter
2025-08-16 12:49:35,901 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 416 Wörter
2025-08-16 12:49:35,901 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Dailycamper Trekking (2025): Neuer Mini-Camper auf Fiat Doblo
2025-08-16 12:49:35,901 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Campingbus Rocket Camper Ryzon beim Weltrekord: Jonas Deichmanns Camper für Ironman-Rekord
2025-08-16 12:49:35,902 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: E-Trailer für die Campingfahrzeug-Steuerung per App: So werden Wohnwagen & Camper zum Smarthome
2025-08-16 12:49:35,902 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/e-trailer-system-kontrollboard-wohnwagen-camper/ (Versuch 1)
2025-08-16 12:49:36,613 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:36,614 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:36,614 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 499 Wörter
2025-08-16 12:49:36,615 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 499 Wörter
2025-08-16 12:49:36,615 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: E-Trailer für die Campingfahrzeug-Steuerung per App: So werden Wohnwagen & Camper zum Smarthome
2025-08-16 12:49:36,615 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Im Wohnmobil zum 2024-EM-Spielort Leipzig: Nach Leipzig in die Red Bull Arena zur Fußball-EM
2025-08-16 12:49:36,615 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/spielorte-em-2024-red-bull-arena-leipzig/ (Versuch 1)
2025-08-16 12:49:38,736 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:38,737 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:38,739 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 894 Wörter
2025-08-16 12:49:38,739 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 894 Wörter
2025-08-16 12:49:38,739 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Im Wohnmobil zum 2024-EM-Spielort Leipzig: Nach Leipzig in die Red Bull Arena zur Fußball-EM
2025-08-16 12:49:38,740 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Can-Kasim Dogan von Westfalen Mobil im Interview : Wie sieht der Westfalia-CEO die Camping-Zukunft?
2025-08-16 12:49:38,740 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/can-kasim-dogan-interview-westfalia/ (Versuch 1)
2025-08-16 12:49:39,605 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:39,606 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:39,607 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 599 Wörter
2025-08-16 12:49:39,607 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 599 Wörter
2025-08-16 12:49:39,607 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Can-Kasim Dogan von Westfalen Mobil im Interview : Wie sieht der Westfalia-CEO die Camping-Zukunft?
2025-08-16 12:49:39,608 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Top-Fortbewegungsmittel für Wohnmobil-Reisende: Wie bleiben Sie im Campingurlaub mobil?
2025-08-16 12:49:39,608 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/promobil-leser-meinung-mobilitaet-fortbewegungsmittel-fahrrad/ (Versuch 1)
2025-08-16 12:49:40,376 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:40,377 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:40,378 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 639 Wörter
2025-08-16 12:49:40,378 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 639 Wörter
2025-08-16 12:49:40,378 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Top-Fortbewegungsmittel für Wohnmobil-Reisende: Wie bleiben Sie im Campingurlaub mobil?
2025-08-16 12:49:40,379 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Camping-Tipps für EM-Spielorte 2024 : Frankfurt für Fußball-Fans im Wohnmobil
2025-08-16 12:49:40,379 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/spielorte-em-2024-frankfurt/ (Versuch 1)
2025-08-16 12:49:41,173 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:41,174 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:41,175 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 691 Wörter
2025-08-16 12:49:41,176 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 691 Wörter
2025-08-16 12:49:41,176 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Camping-Tipps für EM-Spielorte 2024 : Frankfurt für Fußball-Fans im Wohnmobil
2025-08-16 12:49:41,176 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Spielorte und Stellplätze zur Fußball-EM 2024 : Mit dem Wohnmobil zur Euro 2024 in Stuttgart
2025-08-16 12:49:41,176 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/stuttgart-em-2024-fussball-stellplaetze-wohnmobil-camping/ (Versuch 1)
2025-08-16 12:49:42,081 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:42,083 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:42,084 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 923 Wörter
2025-08-16 12:49:42,085 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 923 Wörter
2025-08-16 12:49:42,085 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Spielorte und Stellplätze zur Fußball-EM 2024 : Mit dem Wohnmobil zur Euro 2024 in Stuttgart
2025-08-16 12:49:42,085 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Camper-Radio Caravan.fm : Radiosender speziell für Camping-Fans
2025-08-16 12:49:42,085 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/camper-radio-radiosender-caravan-fm/ (Versuch 1)
2025-08-16 12:49:42,899 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:42,900 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:42,901 - INFO - extract_full_article:257 - 🎉 Erfolgreiche Extraktion (generisch): 258 Wörter
2025-08-16 12:49:42,901 - INFO - fetch_and_process_feed:215 - ✅ Volltext extrahiert: 258 Wörter
2025-08-16 12:49:42,901 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Camper-Radio Caravan.fm : Radiosender speziell für Camping-Fans
2025-08-16 12:49:42,902 - INFO - fetch_and_process_feed:211 - 🔍 Kurzer Artikel erkannt, versuche Volltext-Extraktion: Professor für Tourismus-Management im Interview: Armin Brysch spricht über virtuelle Reisen
2025-08-16 12:49:42,902 - INFO - extract_full_article:214 - 📰 Starte Volltextextraktion von: https://www.promobil.de/tourismus-management-armin-brysch-interview-virtuelle-reisen/ (Versuch 1)
2025-08-16 12:49:44,184 - INFO - extract_full_article:253 - 🔄 Fallback auf generische Selektoren
2025-08-16 12:49:44,185 - INFO - extract_with_selectors:165 - ✅ Erfolgreiche Extraktion mit Selektor: {'tag': 'article', 'class': None}
2025-08-16 12:49:44,186 - INFO - extract_full_article:262 - 🔄 Fallback auf Paragraph-Extraktion
2025-08-16 12:49:44,186 - INFO - extract_from_paragraphs:194 - ✅ Fallback-Extraktion aus 8 Paragraphen
2025-08-16 12:49:44,187 - INFO - extract_full_article:271 - 🔄 Letzter Fallback: Body-Text
2025-08-16 12:49:44,189 - INFO - extract_full_article:281 - ⚠️ Body-Extraktion: 0 Wörter
2025-08-16 12:49:44,190 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Professor für Tourismus-Management im Interview: Armin Brysch spricht über virtuelle Reisen
2025-08-16 12:49:44,190 - INFO - fetch_and_process_feed:257 - ✅ Feed verarbeitet: 0 neue Artikel aus https://www.promobil.de/rss/news
2025-08-16 12:49:45,196 - INFO - fetch_and_process_feed:174 - 🔄 Verarbeite Feed: https://www.promobil.de/rss/ratgeber
2025-08-16 12:49:46,821 - INFO - fetch_and_process_feed:181 - 📡 Feed-Name: ratgeber bei www.promobil.de
2025-08-16 12:49:46,821 - INFO - fetch_and_process_feed:187 - 📰 20 Einträge gefunden
2025-08-16 12:49:46,822 - INFO - extract_images_with_metadata:149 - 🖼️ Starte Bildextraktion von: https://www.promobil.de/weitere-ratgeber/gasflaschenservice-fuer-camping-und-grillfans-im-promobil-test/
2025-08-16 12:49:47,648 - INFO - extract_images_with_metadata:167 - 🔍 16 img-Tags gefunden
2025-08-16 12:49:47,649 - INFO - extract_images_with_metadata:180 - ✅ Bild hinzugefügt: Automat...
2025-08-16 12:49:47,649 - INFO - extract_images_with_metadata:180 - ✅ Bild hinzugefügt: Camping, Gas...
2025-08-16 12:49:47,650 - INFO - extract_images_with_metadata:180 - ✅ Bild hinzugefügt: Gasflaschen, Tauschautomat, Energie Rath, Dinkels...
2025-08-16 12:49:47,650 - INFO - extract_images_with_metadata:180 - ✅ Bild hinzugefügt: LPG...
2025-08-16 12:49:47,650 - INFO - extract_images_with_metadata:180 - ✅ Bild hinzugefügt: Bild aus Originalartikel...
2025-08-16 12:49:47,650 - ERROR - extract_images_with_metadata:200 - ❌ Unerwarteter Fehler bei Bildextraktion von https://www.promobil.de/weitere-ratgeber/gasflaschenservice-fuer-camping-und-grillfans-im-promobil-test/: unsupported operand type(s) for *: 'NoneType' and 'NoneType'
2025-08-16 12:49:47,651 - INFO - fetch_and_process_feed:244 - 🖼️ 0 Bilder für 'Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper' extrahiert
2025-08-16 12:49:47,651 - INFO - fetch_and_process_feed:249 - ✅ Neuer Artikel hinzugefügt: Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper
2025-08-16 12:49:47,652 - INFO - extract_images_with_metadata:149 - 🖼️ Starte Bildextraktion von: https://www.promobil.de/weitere-ratgeber/neue-bussgelder-in-italien-falsche-muellentsorgung-aus-dem-wohnmobil-wird-besonders-teuer/
2025-08-16 12:49:50,073 - INFO - extract_images_with_metadata:167 - 🔍 13 img-Tags gefunden
2025-08-16 12:49:50,074 - INFO - extract_images_with_metadata:180 - ✅ Bild hinzugefügt: Basiswissen, Der gute Ton, Stellplatz-Knigge, Müll...
2025-08-16 12:49:50,074 - INFO - extract_images_with_metadata:180 - ✅ Bild hinzugefügt: Bild aus Originalartikel...
2025-08-16 12:49:50,074 - ERROR - extract_images_with_metadata:200 - ❌ Unerwarteter Fehler bei Bildextraktion von https://www.promobil.de/weitere-ratgeber/neue-bussgelder-in-italien-falsche-muellentsorgung-aus-dem-wohnmobil-wird-besonders-teuer/: unsupported operand type(s) for *: 'NoneType' and 'NoneType'
2025-08-16 12:49:50,075 - INFO - fetch_and_process_feed:244 - 🖼️ 0 Bilder für 'Italien verschärft Regeln zur Müllentsorgung: Bis zu 18.000 Euro Strafe für Müll vor dem Camper' extrahiert
2025-08-16 12:49:50,075 - INFO - fetch_and_process_feed:249 - ✅ Neuer Artikel hinzugefügt: Italien verschärft Regeln zur Müllentsorgung: Bis zu 18.000 Euro Strafe für Müll vor dem Camper
2025-08-16 12:49:50,076 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Regeln auf Frankreichs Autobahnen im Pannenfall: Kein Schutz durch ADAC & Co. auf Autobahnen
2025-08-16 12:49:50,078 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Fahrradtransport in Italien: Warntafel bei Fahrradträgern doch wieder Pflicht?
2025-08-16 12:49:50,081 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Neue Mautregeln für Wohnmobile mit 3,5 t: Diese Camper brauchen ab dem Stichtag eine GO-Box
2025-08-16 12:49:50,083 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Film-Stunt mit Caravan wie realistisch ist das?: Sexsymbol Jensen Ackles wagt Stunt auf Wohnwagen
2025-08-16 12:49:50,085 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: CLEVER CAMPEN Podcast Folge 40: Gravelbikes und Camping die beste Kombi?
2025-08-16 12:49:50,086 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Stellplatz-Radar Sommeraktion 2025: 30 Tage Stellplatz-Radar PLUS gratis testen
2025-08-16 12:49:50,088 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Digitaler Fahrzeugschein für Camper: promobil testet den digitalen Fahrzeugschein
2025-08-16 12:49:50,089 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Badezimmer beim Camping - Umfrage: Welches Bad brauchen Sie im Wohnmobil?
2025-08-16 12:49:50,091 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Umfrage zum Kaffeegenuss beim Camping: So kochen Sie am liebsten Ihren Kaffee
2025-08-16 12:49:50,092 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Zwei Menschen, zwei Meinungen: Das Mietbad spaltet die Campingwelt
2025-08-16 12:49:50,094 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Zubehör auf der größten Campingmesse kaufen: Deshalb müssen Zubehör-Shopper zum Caravan Salon
2025-08-16 12:49:50,096 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Tipps für die größte Camping-Messe Deutschlands: Darum dürfen Sie den Caravan Salon nicht verpassen
2025-08-16 12:49:50,099 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Sommerhitze im Wohnmobil & Wohnwagen: Die besten Tipps gegen Hitze im Camper
2025-08-16 12:49:50,101 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Dethleffs Reiselust-Prämie: Bis zu 20.000 Euro Rabatt auf Wohnmobile
2025-08-16 12:49:50,102 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Der neue promobil-Newsletter - gratis!: Zum Frühstück die spannendsten Camping-Themen
2025-08-16 12:49:50,103 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Drittes Camping-Rekordjahr in Folge: Süd schlägt Nord Hier wird am häufigsten gecampt
2025-08-16 12:49:50,104 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Wohnmobil-Handel treibt Vorzelt-Profi in Insolvenz: Camping-Ausrüster Herzog beantragt Insolvenz
2025-08-16 12:49:50,106 - INFO - fetch_and_process_feed:251 - 🔄 Duplikat übersprungen: Pannen und Probleme im Wohnmobil & Wohnwagen: Erste Hilfe für die Camper-Bordtechnik
2025-08-16 12:49:50,106 - INFO - fetch_and_process_feed:257 - ✅ Feed verarbeitet: 2 neue Artikel aus https://www.promobil.de/rss/ratgeber
2025-08-16 12:49:51,131 - INFO - save_articles:144 - ✅ 55 Artikel gespeichert
2025-08-16 12:49:51,131 - INFO - process_articles:310 - 🎉 Verarbeitung abgeschlossen: 2 neue Artikel in 27.58s hinzugefügt
2025-08-16 12:49:52,222 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:49:52,223 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:50:27,304 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:50:27,305 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:50:27,334 - INFO - save_articles:144 - ✅ 55 Artikel gespeichert
2025-08-16 12:50:27,909 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:50:27,910 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:50:33,666 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:50:33,667 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:50:47,623 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:50:47,624 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:50:49,242 - INFO - _get_default_category_id:63 - ✅ Standard-Kategorie 'Allgemein' gefunden: ID 1
2025-08-16 12:50:49,242 - INFO - test_connection:237 - 🔧 Teste WordPress-API-Verbindung...
2025-08-16 12:50:49,751 - INFO - test_connection:247 - ✅ WordPress-API-Verbindung erfolgreich
2025-08-16 12:51:22,341 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:51:22,342 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:51:22,344 - INFO - rewrite_articles:320 - ✍️ Starte Artikel-Umschreibung
2025-08-16 12:51:22,346 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:51:22,347 - INFO - rewrite_articles:337 - ✍️ Umschreiben von: Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper
2025-08-16 12:51:37,237 - INFO - _send_single_request:1025 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-08-16 12:51:39,037 - INFO - _send_single_request:1025 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-08-16 12:51:39,038 - INFO - rewrite_articles:392 - ✅ Artikel erfolgreich umgeschrieben: Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper
2025-08-16 12:51:41,055 - INFO - save_articles:144 - ✅ 55 Artikel gespeichert
2025-08-16 12:51:41,055 - INFO - rewrite_articles:404 - 🎉 1 Artikel erfolgreich umgeschrieben
2025-08-16 12:51:42,128 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:51:42,128 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:52:01,541 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:52:01,542 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:52:08,641 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:52:08,641 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:52:14,550 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:52:14,550 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:52:32,308 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:52:32,311 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:53:12,402 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:53:12,403 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:53:15,939 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:53:15,939 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:53:15,959 - INFO - save_articles:144 - ✅ 55 Artikel gespeichert
2025-08-16 12:53:16,006 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:53:16,006 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:53:21,156 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:53:21,156 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:53:21,821 - INFO - _get_default_category_id:63 - ✅ Standard-Kategorie 'Allgemein' gefunden: ID 1
2025-08-16 12:53:21,821 - INFO - test_connection:237 - 🔧 Teste WordPress-API-Verbindung...
2025-08-16 12:53:22,205 - INFO - test_connection:247 - ✅ WordPress-API-Verbindung erfolgreich
2025-08-16 12:53:22,205 - INFO - upload_article:158 - 📤 Starte WordPress-Upload: Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper
2025-08-16 12:53:23,117 - ERROR - upload_article:190 - ❌ WordPress-Fehler 400 für 'Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper': Ungültige(r) Parameter: tags
2025-08-16 12:53:24,214 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:53:24,214 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:58:02,027 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:58:02,028 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:58:14,389 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:58:14,391 - INFO - load_feeds:93 - ✅ 3 Feeds geladen
2025-08-16 12:58:14,395 - INFO - upload_articles_to_wp:412 - 📤 Starte WordPress-Upload
2025-08-16 12:58:14,408 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:58:14,408 - INFO - upload_articles_to_wp:421 - 📦 1 Artikel für WordPress-Upload gefunden
2025-08-16 12:58:14,410 - INFO - __init__:51 - ✅ WordPress-Authentifizierung: Verwende bereitgestellten Base64-String
2025-08-16 12:58:16,007 - INFO - _get_default_category_id:87 - ✅ Standard-Kategorie 'Allgemein' gefunden: ID 1
2025-08-16 12:58:16,007 - INFO - test_connection:338 - 🔧 Teste WordPress-API-Verbindung mit Base64-Auth...
2025-08-16 12:58:16,007 - INFO - test_connection:342 - 🔑 Authorization Header: Basic b2dpZXJ0ejp3aE...
2025-08-16 12:58:16,501 - INFO - test_connection:351 - 📡 API-Response Status: 200
2025-08-16 12:58:16,501 - INFO - test_connection:352 - 📡 API-Response Headers: {'Content-Type': 'application/json; charset=UTF-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'X-WS-RateLimit-Limit': '1000', 'X-WS-RateLimit-Remaining': '998', 'Date': 'Sat, 16 Aug 2025 10:58:16 GMT', 'Server': 'Apache', 'X-Powered-By': 'PHP/8.2.29', 'Pragma': 'no-cache', 'X-Robots-Tag': 'noindex', 'X-Content-Type-Options': 'nosniff', 'Access-Control-Expose-Headers': 'X-WP-Total, X-WP-TotalPages, Link', 'Access-Control-Allow-Headers': 'Authorization, X-WP-Nonce, Content-Disposition, Content-MD5, Content-Type', 'X-WP-Total': '4', 'X-WP-TotalPages': '4', 'Link': '<https://vanityontour.de/wp-json/wp/v2/categories?per_page=1&page=2>; rel="next"', 'Allow': 'GET, POST', 'Expires': 'Wed, 11 Jan 1984 05:00:00 GMT', 'Cache-Control': 'no-cache, must-revalidate, max-age=0, no-store, private'}
2025-08-16 12:58:16,501 - INFO - test_connection:355 - ✅ WordPress-API-Verbindung erfolgreich
2025-08-16 12:58:16,501 - INFO - upload_multiple_articles:392 - 📦 Starte Batch-Upload von 1 Artikeln zu WordPress
2025-08-16 12:58:16,502 - INFO - upload_multiple_articles:396 - 📤 Upload 1/1: Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper
2025-08-16 12:58:16,502 - INFO - upload_article:249 - 📤 Starte WordPress-Upload: Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper
2025-08-16 12:58:18,037 - INFO - _get_or_create_tags:149 - ✅ Neuer Tag erstellt: 'Gasflaschen-Automat' (ID: 891)
2025-08-16 12:58:18,550 - INFO - _get_or_create_tags:135 - ✅ Existierender Tag gefunden: 'Wohnmobil' (ID: 646)
2025-08-16 12:58:19,573 - INFO - _get_or_create_tags:149 - ✅ Neuer Tag erstellt: 'Globus-Baumarkt' (ID: 892)
2025-08-16 12:58:19,573 - INFO - _get_or_create_tags:158 - 🏷️ Tags verarbeitet: 3 Tag-IDs erstellt
2025-08-16 12:58:19,573 - INFO - _prepare_post_data:194 - 📝 Post-Daten vorbereitet: Titel='Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper', Tags=3, Kategorie=1
2025-08-16 12:58:20,442 - INFO - upload_article:274 - ✅ WordPress-Upload erfolgreich: 'Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper' (ID: 3378)
2025-08-16 12:58:20,442 - INFO - upload_article:275 - 🔗 WordPress-URL: https://vanityontour.de/?p=3378
2025-08-16 12:58:20,442 - INFO - upload_multiple_articles:422 - 📊 Batch-Upload abgeschlossen: 1 erfolgreich, 0 fehlgeschlagen, 0 Duplikate
2025-08-16 12:58:20,443 - INFO - upload_articles_to_wp:441 - ✅ Status geändert für 'Tauschautomat für Gasflaschen im Praxischeck: Wie funktioniert der 24/7-Service für Camper': Process → WordPress Pending
2025-08-16 12:58:20,453 - INFO - save_articles:144 - ✅ 55 Artikel gespeichert
2025-08-16 12:58:20,454 - INFO - upload_articles_to_wp:446 - 💾 Artikel-Status nach WordPress-Upload aktualisiert
2025-08-16 12:58:22,520 - INFO - load_articles:124 - ✅ 55 Artikel geladen
2025-08-16 12:58:22,522 - INFO - load_feeds:93 - ✅ 3 Feeds geladen

48
main.py
View file

@ -10,6 +10,7 @@ import logging
import openai import openai
from utils.image_extractor import extract_images_with_metadata from utils.image_extractor import extract_images_with_metadata
from utils.article_extractor import extract_full_article from utils.article_extractor import extract_full_article
from utils.wordpress_uploader import upload_articles_to_wordpress
import hashlib import hashlib
import time import time
@ -34,7 +35,7 @@ openai.api_key = os.getenv("OPENAI_API_KEY")
ARTICLES_FILE = "data/articles.json" ARTICLES_FILE = "data/articles.json"
FEEDS_FILE = "data/feeds.json" FEEDS_FILE = "data/feeds.json"
VALID_STATUSES = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"] VALID_STATUSES = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash", "WordPress Pending"]
# === Datenordner erstellen === # === Datenordner erstellen ===
os.makedirs("data", exist_ok=True) os.makedirs("data", exist_ok=True)
@ -405,6 +406,51 @@ def rewrite_articles():
except Exception as e: except Exception as e:
logging.error(f"❌ Kritischer Fehler beim Umschreiben: {e}") logging.error(f"❌ Kritischer Fehler beim Umschreiben: {e}")
def upload_articles_to_wp():
"""Lädt Artikel mit Status 'Process' zu WordPress hoch"""
try:
logging.info("📤 Starte WordPress-Upload")
articles = load_articles()
process_articles_list = [a for a in articles if a.get("status") == "Process"]
if not process_articles_list:
logging.info(" Keine Artikel für WordPress-Upload gefunden")
return {"total": 0, "successful": 0, "failed": 0, "message": "Keine Artikel zum Hochladen gefunden"}
logging.info(f"📦 {len(process_articles_list)} Artikel für WordPress-Upload gefunden")
# WordPress-Upload durchführen
upload_results = upload_articles_to_wordpress(process_articles_list)
# Status der erfolgreich hochgeladenen Artikel ändern
if upload_results.get('successful', 0) > 0:
changed = False
for detail in upload_results.get('details', []):
if detail.get('success'):
article_id = detail.get('article_id')
# Artikel in der Liste finden und Status ändern
for article in articles:
if article.get('id') == article_id:
article['status'] = "WordPress Pending"
article['wp_upload_date'] = datetime.now().isoformat()
article['wp_post_id'] = detail.get('wp_post_id')
changed = True
logging.info(f"✅ Status geändert für '{article.get('title')}': Process → WordPress Pending")
break
if changed:
save_articles(articles)
logging.info(f"💾 Artikel-Status nach WordPress-Upload aktualisiert")
return upload_results
except Exception as e:
logging.error(f"❌ Kritischer Fehler beim WordPress-Upload: {e}")
return {"total": 0, "successful": 0, "failed": 1, "error": str(e)}
def get_article_stats(): def get_article_stats():
"""Gibt Statistiken über die Artikel zurück""" """Gibt Statistiken über die Artikel zurück"""
try: try:

468
utils/wordpress_uploader.py Normal file
View file

@ -0,0 +1,468 @@
# utils/wordpress_uploader.py
import requests
import json
import os
import logging
import base64
from datetime import datetime
from typing import Dict, List, Optional, Tuple
from dotenv import load_dotenv
load_dotenv()
# WordPress API Konfiguration
WP_BASE_URL = os.getenv("WP_BASE_URL", "https://vanityontour.de")
WP_USERNAME = os.getenv("WP_USERNAME", "ogiertz")
WP_PASSWORD = os.getenv("WP_PASSWORD", "whNEx9aZCIUXViV89Z3e7Z03")
WP_AUTH_BASE64 = os.getenv("WP_AUTH_BASE64", "b2dpZXJ0ejp3aE5FeDlhWkNJVVhWaVY4OVozZTdaMDM=")
WP_API_ENDPOINT = f"{WP_BASE_URL}/wp-json/wp/v2"
# Request-Konfiguration
REQUEST_TIMEOUT = 30
MAX_RETRIES = 3
USER_AGENT = 'RSS-Feed-Manager/1.6.1'
class WordPressUploader:
"""
Klasse für den Upload von Artikeln zu WordPress über die REST API
mit Base64-Authentifizierung
"""
def __init__(self):
self.base_url = WP_BASE_URL
self.api_endpoint = WP_API_ENDPOINT
self.username = WP_USERNAME
self.password = WP_PASSWORD
self.auth_base64 = WP_AUTH_BASE64
# Session für bessere Performance
self.session = requests.Session()
# Authentifizierung über Authorization Header mit Base64
if self.auth_base64:
# Verwende bereitgestellten Base64-String
self.session.headers.update({
'Authorization': f'Basic {self.auth_base64}',
'User-Agent': USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json'
})
logging.info("✅ WordPress-Authentifizierung: Verwende bereitgestellten Base64-String")
else:
# Fallback: Generiere Base64 aus Username/Password
if self.username and self.password:
credentials = f"{self.username}:{self.password}"
encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
self.session.headers.update({
'Authorization': f'Basic {encoded_credentials}',
'User-Agent': USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json'
})
logging.info("✅ WordPress-Authentifizierung: Base64 aus Username/Password generiert")
else:
logging.error("❌ WordPress-Authentifizierung: Weder Base64-String noch Username/Password verfügbar")
raise ValueError("WordPress-Authentifizierung nicht konfiguriert")
# Standard-Kategorie ID ermitteln
self.default_category_id = self._get_default_category_id()
def _get_default_category_id(self) -> int:
"""
Ermittelt die ID der Standard-Kategorie 'Allgemein'
"""
try:
response = self.session.get(
f"{self.api_endpoint}/categories",
params={'search': 'Allgemein', 'per_page': 10},
timeout=REQUEST_TIMEOUT
)
response.raise_for_status()
categories = response.json()
for category in categories:
if category['name'].lower() == 'allgemein':
logging.info(f"✅ Standard-Kategorie 'Allgemein' gefunden: ID {category['id']}")
return category['id']
# Fallback: Erste Kategorie oder Standard-ID
if categories:
logging.warning(f"⚠️ Kategorie 'Allgemein' nicht gefunden, verwende '{categories[0]['name']}' (ID: {categories[0]['id']})")
return categories[0]['id']
else:
logging.warning("⚠️ Keine Kategorien gefunden, verwende Standard-ID 1")
return 1
except Exception as e:
logging.error(f"❌ Fehler beim Ermitteln der Standard-Kategorie: {e}")
return 1 # WordPress Standard-Kategorie
def _get_or_create_tags(self, tag_names: List[str]) -> List[int]:
"""
Ermittelt oder erstellt Tags und gibt deren IDs zurück
"""
tag_ids = []
if not tag_names:
return tag_ids
try:
# Bestehende Tags abrufen
for tag_name in tag_names:
tag_name = tag_name.strip()
if not tag_name:
continue
try:
# Suche nach existierendem Tag
response = self.session.get(
f"{self.api_endpoint}/tags",
params={'search': tag_name, 'per_page': 10},
timeout=REQUEST_TIMEOUT
)
response.raise_for_status()
existing_tags = response.json()
tag_found = False
# Exakte Übereinstimmung suchen
for tag in existing_tags:
if tag['name'].lower() == tag_name.lower():
tag_ids.append(tag['id'])
tag_found = True
logging.info(f"✅ Existierender Tag gefunden: '{tag_name}' (ID: {tag['id']})")
break
# Tag erstellen falls nicht gefunden
if not tag_found:
create_response = self.session.post(
f"{self.api_endpoint}/tags",
json={'name': tag_name},
timeout=REQUEST_TIMEOUT
)
if create_response.status_code == 201:
new_tag = create_response.json()
tag_ids.append(new_tag['id'])
logging.info(f"✅ Neuer Tag erstellt: '{tag_name}' (ID: {new_tag['id']})")
else:
logging.warning(f"⚠️ Tag '{tag_name}' konnte nicht erstellt werden: {create_response.status_code}")
continue
except Exception as e:
logging.error(f"❌ Fehler beim Verarbeiten von Tag '{tag_name}': {e}")
continue
logging.info(f"🏷️ Tags verarbeitet: {len(tag_ids)} Tag-IDs erstellt")
return tag_ids
except Exception as e:
logging.error(f"❌ Allgemeiner Fehler bei Tag-Verarbeitung: {e}")
return []
def _prepare_post_data(self, article: Dict) -> Dict:
"""
Bereitet die Artikel-Daten für WordPress vor
"""
# Tags verarbeiten - WordPress benötigt Tag-IDs, nicht Namen
tag_names = article.get('tags', [])
tag_ids = self._get_or_create_tags(tag_names)
# Basis Post-Daten
post_data = {
'title': article.get('title', 'Kein Titel'),
'content': article.get('text', ''),
'status': 'pending', # Artikel als "Ausstehend" markieren
'categories': [self.default_category_id],
'excerpt': article.get('summary', '')[:300], # WordPress Excerpt
'meta': {
'rss_source': article.get('source', ''),
'rss_original_link': article.get('link', ''),
'rss_import_date': datetime.now().isoformat(),
'rss_article_id': article.get('id', '')
}
}
# Tags nur hinzufügen wenn vorhanden
if tag_ids:
post_data['tags'] = tag_ids
# Optional: Author setzen (falls unterschiedliche Autoren gewünscht)
# post_data['author'] = 1 # WordPress User ID
logging.info(f"📝 Post-Daten vorbereitet: Titel='{post_data['title']}', Tags={len(tag_ids)}, Kategorie={self.default_category_id}")
return post_data
def _check_duplicate(self, article: Dict) -> Optional[int]:
"""
Prüft, ob ein Artikel bereits in WordPress existiert
"""
try:
# Suche nach Titel
title = article.get('title', '')
if not title:
return None
response = self.session.get(
f"{self.api_endpoint}/posts",
params={
'search': title,
'per_page': 5,
'status': 'any' # Alle Status durchsuchen
},
timeout=REQUEST_TIMEOUT
)
response.raise_for_status()
posts = response.json()
for post in posts:
# Exakte Titel-Übereinstimmung
if post['title']['rendered'].strip() == title.strip():
logging.info(f"🔄 Duplikat gefunden: '{title}' (WordPress ID: {post['id']})")
return post['id']
# Prüfe auch Custom Meta Fields (RSS Article ID)
article_id = article.get('id')
if article_id:
# Meta-Felder würden eine separate API-Abfrage erfordern
# Für jetzt: Nur Titel-basierte Duplikatserkennung
pass
return None
except Exception as e:
logging.error(f"❌ Fehler bei Duplikatsprüfung für '{article.get('title', 'Unbekannt')}': {e}")
return None
def upload_article(self, article: Dict) -> Tuple[bool, str, Optional[int]]:
"""
Lädt einen einzelnen Artikel zu WordPress hoch
Returns:
Tuple[bool, str, Optional[int]]: (Erfolg, Nachricht, WordPress Post ID)
"""
title = article.get('title', 'Unbekannt')
try:
logging.info(f"📤 Starte WordPress-Upload: {title}")
# Duplikatsprüfung
existing_post_id = self._check_duplicate(article)
if existing_post_id:
return False, f"Artikel '{title}' existiert bereits in WordPress (ID: {existing_post_id})", existing_post_id
# Post-Daten vorbereiten
post_data = self._prepare_post_data(article)
# Upload mit Retry-Logik
for attempt in range(MAX_RETRIES):
try:
response = self.session.post(
f"{self.api_endpoint}/posts",
json=post_data,
timeout=REQUEST_TIMEOUT
)
if response.status_code == 201:
# Erfolgreich erstellt
wp_post = response.json()
wp_post_id = wp_post['id']
wp_url = wp_post['link']
logging.info(f"✅ WordPress-Upload erfolgreich: '{title}' (ID: {wp_post_id})")
logging.info(f"🔗 WordPress-URL: {wp_url}")
return True, f"Erfolgreich hochgeladen: {wp_url}", wp_post_id
elif response.status_code == 400:
# Client Error - nicht wiederholen
error_data = response.json()
error_msg = error_data.get('message', 'Unbekannter Fehler')
error_code = error_data.get('code', 'unknown')
# Detaillierte Fehleranalyse
if 'parameter' in error_msg.lower() and 'tags' in error_msg.lower():
logging.error(f"❌ WordPress-Tag-Fehler für '{title}': {error_msg}")
logging.error(f"📋 Post-Daten: {json.dumps(post_data, indent=2, ensure_ascii=False)}")
return False, f"Tag-Fehler: {error_msg} (Artikel-Tags: {article.get('tags', [])})", None
else:
logging.error(f"❌ WordPress-Fehler 400 für '{title}': {error_msg} (Code: {error_code})")
logging.error(f"📋 Post-Daten: {json.dumps(post_data, indent=2, ensure_ascii=False)}")
return False, f"WordPress-Fehler: {error_msg}", None
elif response.status_code == 401:
# Authentifizierungsfehler
logging.error(f"❌ WordPress-Authentifizierungsfehler für '{title}'")
return False, "Authentifizierungsfehler - bitte Zugangsdaten prüfen", None
elif response.status_code == 403:
# Berechtigungsfehler
logging.error(f"❌ WordPress-Berechtigungsfehler für '{title}'")
return False, "Keine Berechtigung zum Erstellen von Posts", None
else:
# Server Error - Retry möglich
if attempt < MAX_RETRIES - 1:
logging.warning(f"⚠️ WordPress-Upload Versuch {attempt + 1} fehlgeschlagen für '{title}' (Status: {response.status_code}), versuche erneut...")
continue
else:
logging.error(f"❌ WordPress-Upload nach {MAX_RETRIES} Versuchen fehlgeschlagen für '{title}' (Status: {response.status_code})")
return False, f"Upload fehlgeschlagen nach {MAX_RETRIES} Versuchen (HTTP {response.status_code})", None
except requests.exceptions.Timeout:
if attempt < MAX_RETRIES - 1:
logging.warning(f"⏱️ Timeout bei WordPress-Upload für '{title}' (Versuch {attempt + 1}), versuche erneut...")
continue
else:
logging.error(f"❌ Timeout bei WordPress-Upload für '{title}' nach {MAX_RETRIES} Versuchen")
return False, f"Timeout nach {MAX_RETRIES} Versuchen", None
except requests.exceptions.ConnectionError as e:
if attempt < MAX_RETRIES - 1:
logging.warning(f"🌐 Verbindungsfehler bei WordPress-Upload für '{title}' (Versuch {attempt + 1}): {e}")
continue
else:
logging.error(f"❌ Verbindungsfehler bei WordPress-Upload für '{title}' nach {MAX_RETRIES} Versuchen: {e}")
return False, f"Verbindungsfehler nach {MAX_RETRIES} Versuchen", None
except Exception as e:
logging.error(f"❌ Unerwarteter Fehler bei WordPress-Upload für '{title}': {e}")
return False, f"Unerwarteter Fehler: {str(e)}", None
def test_connection(self) -> Tuple[bool, str]:
"""
Testet die Verbindung zur WordPress API mit Base64-Authentifizierung
"""
try:
logging.info("🔧 Teste WordPress-API-Verbindung mit Base64-Auth...")
# Debug: Auth-Header prüfen
auth_header = self.session.headers.get('Authorization', 'Nicht gesetzt')
logging.info(f"🔑 Authorization Header: {auth_header[:20]}..." if len(auth_header) > 20 else f"🔑 Authorization Header: {auth_header}")
# Einfache Abfrage der Kategorien als Test
response = self.session.get(
f"{self.api_endpoint}/categories",
params={'per_page': 1},
timeout=REQUEST_TIMEOUT
)
logging.info(f"📡 API-Response Status: {response.status_code}")
logging.info(f"📡 API-Response Headers: {dict(response.headers)}")
if response.status_code == 200:
logging.info("✅ WordPress-API-Verbindung erfolgreich")
return True, "Verbindung zur WordPress API erfolgreich"
elif response.status_code == 401:
logging.error("❌ WordPress-API-Authentifizierung fehlgeschlagen")
logging.error(f"Response Body: {response.text}")
return False, "Authentifizierung fehlgeschlagen - bitte Base64-String oder Zugangsdaten prüfen"
elif response.status_code == 403:
logging.error("❌ WordPress-API-Berechtigung fehlgeschlagen")
logging.error(f"Response Body: {response.text}")
return False, "Keine Berechtigung - bitte Benutzerrechte prüfen"
else:
logging.error(f"❌ WordPress-API-Test fehlgeschlagen (Status: {response.status_code})")
logging.error(f"Response Body: {response.text}")
return False, f"API-Test fehlgeschlagen (HTTP {response.status_code}): {response.text[:100]}"
except requests.exceptions.ConnectionError as e:
logging.error(f"❌ Verbindungsfehler zur WordPress API: {e}")
return False, f"Verbindungsfehler: {str(e)}"
except Exception as e:
logging.error(f"❌ Unerwarteter Fehler beim WordPress-API-Test: {e}")
return False, f"Unerwarteter Fehler: {str(e)}"
def upload_multiple_articles(self, articles: List[Dict]) -> Dict:
"""
Lädt mehrere Artikel zu WordPress hoch
Returns:
Dict mit Statistiken über erfolgreiche und fehlgeschlagene Uploads
"""
results = {
'total': len(articles),
'successful': 0,
'failed': 0,
'duplicates': 0,
'details': []
}
logging.info(f"📦 Starte Batch-Upload von {len(articles)} Artikeln zu WordPress")
for i, article in enumerate(articles, 1):
title = article.get('title', f'Artikel {i}')
logging.info(f"📤 Upload {i}/{len(articles)}: {title}")
success, message, wp_post_id = self.upload_article(article)
result_detail = {
'article_id': article.get('id'),
'title': title,
'success': success,
'message': message,
'wp_post_id': wp_post_id
}
results['details'].append(result_detail)
if success:
results['successful'] += 1
elif 'existiert bereits' in message:
results['duplicates'] += 1
else:
results['failed'] += 1
# Kurze Pause zwischen Uploads
if i < len(articles):
import time
time.sleep(1)
logging.info(f"📊 Batch-Upload abgeschlossen: {results['successful']} erfolgreich, {results['failed']} fehlgeschlagen, {results['duplicates']} Duplikate")
return results
def __del__(self):
"""
Session sauber schließen
"""
if hasattr(self, 'session'):
self.session.close()
def upload_articles_to_wordpress(articles: List[Dict]) -> Dict:
"""
Convenience-Funktion für den Upload von Artikeln zu WordPress
"""
uploader = WordPressUploader()
# Verbindung testen
connection_ok, connection_msg = uploader.test_connection()
if not connection_ok:
logging.error(f"❌ WordPress-Verbindung fehlgeschlagen: {connection_msg}")
return {
'total': len(articles),
'successful': 0,
'failed': len(articles),
'duplicates': 0,
'error': connection_msg,
'details': []
}
# Artikel hochladen
return uploader.upload_multiple_articles(articles)
def upload_single_article_to_wordpress(article: Dict) -> Tuple[bool, str, Optional[int]]:
"""
Convenience-Funktion für den Upload eines einzelnen Artikels
"""
uploader = WordPressUploader()
# Verbindung testen
connection_ok, connection_msg = uploader.test_connection()
if not connection_ok:
return False, connection_msg, None
# Artikel hochladen
return uploader.upload_article(article)