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

358
app.py
View file

@ -8,9 +8,11 @@ from main import (
load_articles,
save_articles,
process_articles,
rewrite_articles
rewrite_articles,
upload_articles_to_wp
)
from utils.dalle_generator import generate_dalle_image
from utils.wordpress_uploader import WordPressUploader
import os
from collections import Counter
import time
@ -66,6 +68,7 @@ st.markdown("""
.status-online { background-color: #e8f5e8; color: #388e3c; }
.status-hold { background-color: #fce4ec; color: #c2185b; }
.status-trash { background-color: #ffebee; color: #d32f2f; }
.status-wp-pending { background-color: #e1f5fe; color: #0277bd; }
/* Filter Section */
.filter-section {
@ -107,6 +110,15 @@ st.markdown("""
min-width: 200px;
text-align: center;
}
/* WordPress Upload Status */
.wp-status {
background: #e3f2fd;
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
border-left: 4px solid #2196f3;
}
</style>
""", unsafe_allow_html=True)
@ -129,7 +141,8 @@ def get_status_badge(status):
"Process": "status-process",
"Online": "status-online",
"On Hold": "status-hold",
"Trash": "status-trash"
"Trash": "status-trash",
"WordPress Pending": "status-wp-pending"
}
class_name = status_classes.get(status, "status-new")
return f'<span class="status-badge {class_name}">{status}</span>'
@ -159,21 +172,31 @@ def show_notification(message, type="success"):
elif type == "info":
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 ===
st.markdown("""
<div class="main-header">
<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>
""", unsafe_allow_html=True)
# === Tab Navigation ===
tab1, tab2, tab3, tab4, tab5 = st.tabs([
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
"📋 Dashboard",
"📰 Artikel",
"📡 Feeds",
"🖼️ Bilder",
"📊 Statistiken"
"📊 Statistiken",
"🔧 WordPress"
])
# === Dashboard Tab ===
@ -185,7 +208,7 @@ with tab1:
feeds = load_feeds()
# Statistiken
col1, col2, col3, col4 = st.columns(4)
col1, col2, col3, col4, col5 = st.columns(5)
with col1:
st.markdown("""
@ -205,14 +228,24 @@ with tab1:
""".format(new_count), unsafe_allow_html=True)
with col3:
process_count = len([a for a in all_articles if a.get("status") == "Process"])
st.markdown("""
<div class="stats-card">
<div class="stats-number">{}</div>
<div>RSS Feeds</div>
<div>Bereit für WP</div>
</div>
""".format(len(feeds)), unsafe_allow_html=True)
""".format(process_count), unsafe_allow_html=True)
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"])
st.markdown("""
<div class="stats-card">
@ -226,7 +259,7 @@ with tab1:
# Quick Actions
st.subheader("⚡ Schnellaktionen")
col1, col2, col3 = st.columns(3)
col1, col2, col3, col4 = st.columns(4)
with col1:
if st.button("🔄 Alle Feeds aktualisieren", use_container_width=True):
@ -250,6 +283,32 @@ with tab1:
show_notification("Keine Artikel zum Umschreiben gefunden.", "info")
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):
trash_count = len([a for a in all_articles if a.get("status") == "Trash"])
if trash_count > 0:
@ -257,6 +316,21 @@ with tab1:
else:
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
st.subheader("🕒 Neueste Artikel")
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)
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(
"Status",
status_options,
@ -366,6 +440,10 @@ with tab2:
st.markdown(f"**{title}**")
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:
st.markdown(get_status_badge(article.get("status", "New")), unsafe_allow_html=True)
@ -388,11 +466,11 @@ with tab2:
st.markdown(f"📡 {source_name}")
# Actions
col1, col2, col3, col4 = st.columns(4)
col1, col2, col3, col4, col5 = st.columns(5)
with col1:
# 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")
new_status = st.selectbox(
"Status",
@ -423,6 +501,30 @@ with tab2:
st.markdown(f"[🔗 Artikel öffnen]({article.get('link', '#')})")
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
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)
@ -641,6 +743,39 @@ with tab5:
for feed_name, count in feed_counts.most_common():
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
st.subheader("📝 Textstatistiken")
@ -668,4 +803,201 @@ with tab5:
for tag, count in tag_counts.most_common(10):
st.markdown(f"**{tag}:** {count}x verwendet")
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
""")