UI-Anpassung: Feed-Anzeige entfernt, Tabelle verbessert

This commit is contained in:
Oliver 2025-07-05 16:36:29 +02:00
parent fe2191e6c8
commit 2db9544fda
9 changed files with 438 additions and 156 deletions

4
.gitignore vendored
View file

@ -24,8 +24,8 @@ Thumbs.db
.env
# JSON-Datenbanken / Zwischenspeicherung
*.json
!feeds.json # optional entfernbar, wenn du Beispielfeeds mit versionieren willst
*.json
!feeds.json # optional entfernbar, wenn du Beispielfeeds mit versionieren willst
# LOG Files ausschließen, dienen nur zum Debuggen
# *.log

View file

@ -1,5 +1,25 @@
# CHANGELOG.md
## [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

View file

@ -1,5 +0,0 @@
2025-07-04 09:29:50,207 - INFO - Status von 1 Artikel(n) auf 'Rewrite' gesetzt.
2025-07-04 09:30:17,000 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-04 09:30:17,010 - INFO - ✅ Artikel umgeschrieben: Das weltweit größte Caravaning-Erlebnis
2025-07-04 09:46:03,001 - INFO - Status von 1 Artikel(n) auf 'Online' gesetzt.
2025-07-04 19:36:22,449 - INFO - 1 neue Artikel geladen.

143
app.py
View file

@ -1,92 +1,95 @@
# app.py
import streamlit as st
import json
import os
from datetime import datetime
from dotenv import load_dotenv
from email.utils import parsedate_to_datetime
from main import load_articles, save_articles, process_articles, fetch_and_process_feed, rewrite_articles
from main import (
load_feeds,
save_feeds,
load_articles,
save_articles,
process_articles,
rewrite_articles
)
import os
load_dotenv()
st.set_page_config(page_title="📰 RSS Artikel Manager", layout="wide")
st.title("📰 RSS Artikel Manager")
st.set_page_config(layout="wide", page_title="RSS Article Manager")
# RSS Feed Verwaltung
st.sidebar.header("📡 RSS Feeds verwalten")
feeds = load_feeds()
new_feed = st.sidebar.text_input("Neuen RSS Feed hinzufügen")
if st.sidebar.button("Feed hinzufügen"):
if new_feed and new_feed not in feeds:
feeds.append({"url": new_feed})
save_feeds(feeds)
st.sidebar.success("Feed hinzugefügt")
# Artikelstatusfilter
status_filter = st.sidebar.selectbox("🔍 Artikelstatus filtern", ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"])
# Neuen Feed hinzufügen
st.sidebar.markdown("---")
st.sidebar.header(" RSS Feed hinzufügen")
new_feed_url = st.sidebar.text_input("Feed URL")
if st.sidebar.button("Feed hinzufügen") and new_feed_url:
fetch_and_process_feed(new_feed_url)
st.rerun()
# Alle Feeds neu laden
if st.sidebar.button("Alle Feeds neu laden"):
process_articles()
st.rerun()
#if feeds:
# st.sidebar.write("### Aktuelle Feeds:")
# for feed in feeds:
# url = feed["url"] if isinstance(feed, dict) else feed
# st.sidebar.markdown(f"- {url}")
#else:
# st.sidebar.info("Noch keine Feeds hinzugefügt.")
# Artikel laden
try:
articles = load_articles()
except json.decoder.JSONDecodeError:
articles = []
if st.sidebar.button("🔄 Alle Feeds neu laden"):
existing_ids = [a["id"] for a in load_articles()]
process_articles(existing_ids)
st.rerun()
# Artikel nach Status filtern
if st.sidebar.button("✍️ Artikel umschreiben (Rewrite)"):
rewrite_articles()
st.rerun()
# Artikelübersicht
st.header("📋 Artikelübersicht")
status_filter = st.selectbox("Status filtern", ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"])
articles = load_articles()
if status_filter != "Alle":
articles = [a for a in articles if a.get("status") == status_filter]
# Artikelübersicht
st.title("📰 RSS Artikel Übersicht")
st.markdown("---")
if not articles:
st.info("Keine Artikel gefunden.")
else:
st.markdown("### 📄 Artikelliste")
selected_ids = []
all_statuses = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
# Tabelle anzeigen
if articles:
st.markdown("### 📄 Übersichtstabelle")
st.write("**Spaltenübersicht:** Auswahl | Datum | Titel | Zusammenfassung | Wörter | Tags | Status")
for article in articles:
col1, col2, col3, col4, col5, col6, col7 = st.columns([0.5, 1.2, 2.5, 2, 1, 2, 1.2])
with col1:
if st.checkbox("", key=f"select_{article['id']}"):
selected_ids.append(article['id'])
with col2:
date = parsedate_to_datetime(article['date']).strftime("%d.%m.%y")
st.markdown(date)
with col3:
st.markdown(article['title'])
with col4:
st.markdown(article['summary'][:150] + ("..." if len(article['summary']) > 150 else ""))
with col5:
word_count = len(article['text'].split())
st.markdown(str(word_count))
with col6:
cols = st.columns([0.05, 0.1, 0.2, 0.25, 0.05, 0.2, 0.15])
with cols[0]:
st.checkbox("", key=f"select_{article['id']}")
with cols[1]:
st.markdown(datetime.strptime(article["date"], "%a, %d %b %Y %H:%M:%S %z").strftime("%d.%m.%y") if "GMT" in article["date"] or "+" in article["date"] else article["date"][:10])
with cols[2]:
st.markdown(f"**{article['title']}**")
with cols[3]:
st.markdown(article.get("summary", "")[:150])
with cols[4]:
st.markdown(str(len(article.get("text", "").split())))
with cols[5]:
st.markdown(", ".join(article.get("tags", [])))
with col7:
status = st.selectbox("", all_statuses, index=all_statuses.index(article.get("status", "New")), key=f"status_{article['id']}")
if status != article.get("status"):
article["status"] = status
with cols[6]:
status_options = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
current_status = article.get("status", "New")
new_status = st.selectbox("", status_options, index=status_options.index(current_status), key=f"status_{article['id']}")
if new_status != current_status:
article["status"] = new_status
save_articles(articles)
st.rerun()
if selected_ids:
new_status = st.selectbox("Status für ausgewählte Artikel setzen", all_statuses)
if st.button("✅ Status aktualisieren"):
for article in articles:
if article['id'] in selected_ids:
article['status'] = new_status
save_articles(articles)
st.success("Status aktualisiert.")
st.rerun()
with st.expander(f"🔍 {article['title']}"):
st.markdown("#### ✍️ Artikeltext")
st.code(f"{article['title']}\n\n{article['text']}\n\nQuelle: {article['link']}", language="markdown")
st.markdown("---")
st.markdown("#### 🏷️ Tags")
st.code(", ".join(article.get("tags", [])), language="markdown")
if st.button("✍️ Artikel mit Status 'Rewrite' umschreiben"):
rewrite_articles()
st.rerun()
st.markdown("#### 🖼️ Bilder")
for img in article.get("images", []):
st.image(img["url"], caption=img.get("caption", "Kein Titel"), use_column_width=True)
st.caption(f"© {img.get('copyright', 'Unbekannt')} | [Quelle]({img.get('copyright_url', '#')})")
st.markdown("---")
else:
st.info("Keine Artikel für den gewählten Status gefunden.")

154
data/articles.json Normal file
View file

@ -0,0 +1,154 @@
[
{
"id": "Artikel 5926 von camping-news.de",
"title": "Das weltweit gr\u00f6\u00dfte Caravaning-Erlebnis",
"date": "Tue, 13 May 2025 00:00:00 +0200",
"summary": "Vom 29. August (Preview Day) bis zum 7. September 2025 wird Düsseldorf erneut zum Treffpunkt der Caravaning-Welt",
"text": "Der CARAVAN SALON ist die f\u00fchrende Messe f\u00fcr Caravaning weltweit und bietet eine gro\u00dfe Auswahl an Reisemobilen, Caravans und Campervans. Erg\u00e4nzt wird das Angebot durch eine Vielzahl von Zubeh\u00f6r, technischem Equipment und Dienstleistungen im Bereich Caravaning und Camping. Auch Campingpl\u00e4tze und Reisemobilstellpl\u00e4tze werden vorgestellt. Diese Messe ist ein unerl\u00e4sslicher Treffpunkt der Branche, bei dem Kunden die M\u00f6glichkeit haben, Fahrzeuge vor dem Kauf zu erleben und zu vergleichen.\n\nDie Messe pr\u00e4sentiert zudem die Premieren der kommenden Saison, darunter Top-Produkte, Innovationen und Weltneuheiten, die erstmals \u00f6ffentlich zug\u00e4nglich sind. Dank einer neuen Hallenstruktur k\u00f6nnen Besucher schnell und gezielt ihr Interessengebiet erreichen, egal ob sie Camping-Neulinge, Technik-Fans, kreative Selbstausbauer oder Luxus-Liebhaber sind.\n\nCaravaning erfreut sich seit vielen Jahren wachsender Beliebtheit aufgrund der individuellen und flexiblen Reiseform, die ein selbstbestimmter Urlaub im Einklang mit der Natur erm\u00f6glicht. Dieser Trend hat Caravaning zu einer der beliebtesten Reiseformen weltweit gemacht.\n\nAm 29. August findet der Preview Day statt, an dem die Neuheiten der Branche in Ruhe entdeckt werden k\u00f6nnen. Der Ticketverkauf startet am 11. Juni. Weitere Informationen sind auf der offiziellen Website der Messe erh\u00e4ltlich.",
"tags": [
"#Caravan Salon",
"#Camping Messe",
"#Reisemobile"
],
"status": "Online",
"link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926",
"images": [
{
"url": "https://www.camping-in-deutschland.de/pics2020/logo_weiss.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/img/news13/caravan24_ct7020.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.hundefreundliche-campingplaetze.de/logos/Familienfreundliche.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2938.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/1839.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/1887.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
}
]
},
{
"id": "Artikel 5923 von camping-news.de",
"title": "Komfort und Flexibilit\u00e4t f\u00fcr moderne Camper",
"date": "Wed, 07 May 2025 00:00:00 +0200",
"summary": "dwt-Zelte präsentiert das Luftzelt Scala Air",
"text": "Der Hersteller dwt-Zelte pr\u00e4sentiert mit dem Modell Scala Air ein neues Zelt f\u00fcr Camper, das in zwei Gr\u00f6\u00dfen erh\u00e4ltlich ist und sich durch seinen gro\u00dfz\u00fcgigen Raum und eine durchdachte Bel\u00fcftung auszeichnet. Das Zelt nutzt eine moderne Air-In-Technologie f\u00fcr einen schnellen Aufbau und bietet eine stabile Struktur dank robuster Luftschl\u00e4uche.\n\nDie Ausf\u00fchrungen Scala Air 250 und Scala Air 280 unterscheiden sich in ihrer Tiefe von 250 bzw. 280 cm und eignen sich besonders f\u00fcr Kurzurlaube und wechselfrequente Standorte. Die Zelte sind windstabil und dank zus\u00e4tzlicher Luftschl\u00e4uche und durchdachter Fensterkonstruktionen optimal bel\u00fcftet. \n\nDas Design ist modular, was eine flexible Raumgestaltung erm\u00f6glicht. Entnehmbare W\u00e4nde, hochrollbare T\u00fcr und anpassbare Seitenw\u00e4nde bieten ein individuelles Raumkonzept. Das Zelt ist aus robusten, wetterfesten und pflegeleichten Materialien gefertigt und der Preis bewegt sich je nach Gr\u00f6\u00dfe zwischen 1.995 \u20ac und 2.215 \u20ac.\n\nDie Zelte verf\u00fcgen \u00fcber Sicherheitsgurte f\u00fcr windige Standorte und sind mit H\u00f6henausgleichs-Blocks ausgestattet, um Bodenunebenheiten ausgleichen zu k\u00f6nnen. Damit richten sich die Scala Air Zelte an alle Camper, die Wert auf ein flexibles, robustes und schnell aufzubauendes Zelt legen. Weitere Informationen sind auf der Herstellerseite zu finden.",
"tags": [
"\"Camping\"",
"\"dwt-Zelte\"",
"\"Air-In-Technologie\""
],
"status": "Process",
"link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923",
"images": [
{
"url": "https://www.camping-in-deutschland.de/pics2020/logo_weiss.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/img/news13/scala_dia5199.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.hundefreundliche-campingplaetze.de/logos/Familienfreundliche.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2940.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2433.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2495.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
}
]
}
]

View file

@ -5,3 +5,52 @@ INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
INFO:root:✅ Artikel 'Abenteuer für die Kleinen, Entspannung für
die Großen' umgeschrieben.
INFO:root:Abrufen von Feed: https://www.camping-news.de/rss/
INFO:root:Starte Umschreiben von Artikeln mit Status 'Rewrite' ...
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
INFO:root:✅ Artikel 'Den eigenen Verbrauchszahlen auf der Spur' umgeschrieben.
INFO:root:Starte Umschreiben von Artikeln mit Status 'Rewrite' ...
INFO:root:Abrufen von Feed: https://www.camping-news.de/rss/
INFO:root:🔄 Starte Verarbeitung von 0 Feeds...
INFO:root:✅ Alle Feeds erfolgreich verarbeitet.
INFO:root:🔄 Starte Verarbeitung von 1 Feeds...
INFO:root:📥 Verarbeite Feed: https://www.camping-news.de/rss/
INFO:root:✅ Alle Feeds erfolgreich verarbeitet.
INFO:root:🔄 Starte Verarbeitung von 1 Feeds...
INFO:root:📥 Verarbeite Feed: https://www.camping-news.de/rss/
INFO:root:✅ Alle Feeds erfolgreich verarbeitet.
2025-07-05 15:53:57,795 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 15:54:02,133 - INFO - 10 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 15:54:02,136 - INFO - 10 neue Artikel gespeichert.
2025-07-05 15:57:50,216 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 15:57:54,463 - INFO - 10 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 15:57:54,465 - INFO - 10 neue Artikel gespeichert.
2025-07-05 16:00:32,416 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:00:36,108 - INFO - 9 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:00:36,109 - INFO - 9 neue Artikel gespeichert.
2025-07-05 16:06:28,832 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:06:30,875 - INFO - 4 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:06:30,877 - INFO - 4 neue Artikel gespeichert.
2025-07-05 16:08:14,063 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:08:15,413 - INFO - 3 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:08:15,414 - INFO - 3 neue Artikel gespeichert.
2025-07-05 16:09:14,943 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:09:18,883 - INFO - 9 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:09:18,885 - INFO - 9 neue Artikel gespeichert.
2025-07-05 16:17:36,273 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:17:36,691 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:17:36,691 - INFO - Keine neuen Artikel gefunden.
2025-07-05 16:18:45,562 - INFO - ✍️ Umschreiben von: Den eigenen Verbrauchszahlen auf der Spur
2025-07-05 16:19:04,394 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:06,129 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:06,132 - INFO - ✅ Artikel umgeschrieben: Den eigenen Verbrauchszahlen auf der Spur
2025-07-05 16:19:06,132 - INFO - ✍️ Umschreiben von: Das weltweit größte Caravaning-Erlebnis
2025-07-05 16:19:18,595 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:20,334 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:20,336 - INFO - ✅ Artikel umgeschrieben: Das weltweit größte Caravaning-Erlebnis
2025-07-05 16:19:20,336 - INFO - ✍️ Umschreiben von: Komfort und Flexibilität für moderne Camper
2025-07-05 16:19:33,242 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:34,796 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:34,799 - INFO - ✅ Artikel umgeschrieben: Komfort und Flexibilität für moderne Camper
2025-07-05 16:19:34,803 - INFO - Alle Artikel mit Status 'Rewrite' wurden verarbeitet.

219
main.py
View file

@ -1,108 +1,169 @@
# main.py
import feedparser
import json
import os
from bs4 import BeautifulSoup
from datetime import datetime
import feedparser
from utils.image_extractor import extract_images_with_metadata
from openai import OpenAI
from dotenv import load_dotenv
import logging
from utils.image_extractor import extract_images_with_metadata
import openai
load_dotenv()
# Log-Verzeichnis sicherstellen
os.makedirs("logs", exist_ok=True)
# Logging konfigurieren
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
logging.basicConfig(
filename=os.path.join(log_dir, "rss_tool.log"),
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
openai.api_key = os.getenv("OPENAI_API_KEY")
client = OpenAI()
# 📝 Logging konfigurieren
logging.basicConfig(filename='logs/rss_tool.log', level=logging.INFO)
ARTICLES_FILE = "data/articles.json"
FEEDS_FILE = "data/feeds.json"
ARTICLES_FILE = "processed_articles.json"
FEEDS_FILE = "feeds.json"
VALID_STATUSES = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
def load_articles():
if os.path.exists(ARTICLES_FILE):
with open(ARTICLES_FILE, "r") as f:
return json.load(f)
return []
def save_articles(articles):
with open(ARTICLES_FILE, "w") as f:
json.dump(articles, f, indent=2)
def load_feeds():
if os.path.exists(FEEDS_FILE):
with open(FEEDS_FILE, "r") as f:
return json.load(f)
return []
if not os.path.exists(FEEDS_FILE):
return []
with open(FEEDS_FILE, "r") as f:
return json.load(f)
def save_feeds(feeds):
with open(FEEDS_FILE, "w") as f:
json.dump(feeds, f, indent=2)
def fetch_and_process_feed(url):
logging.info(f"Abrufen von Feed: {url}")
feed = feedparser.parse(url)
articles = load_articles()
for entry in feed.entries:
if any(a["link"] == entry.link for a in articles):
continue
try:
images = extract_images_with_metadata(entry.link)
except Exception as e:
logging.warning(f"Fehler beim Bildextrakt: {e}")
images = []
article = {
"id": f"{entry.link}",
"title": entry.title,
"summary": entry.summary,
"link": entry.link,
"date": entry.get("published", datetime.now().isoformat()),
"text": entry.summary,
"status": "New",
"images": images,
"tags": []
}
articles.append(article)
save_articles(articles)
def load_articles():
if not os.path.exists(ARTICLES_FILE):
return []
with open(ARTICLES_FILE, "r") as f:
articles = json.load(f)
def process_articles():
# Sicherstellen, dass jeder Artikel einen gültigen Status hat
for article in articles:
if article.get("status") not in VALID_STATUSES:
article["status"] = "New"
return articles
def save_articles(articles):
with open(ARTICLES_FILE, "w") as f:
json.dump(articles, f, indent=2)
def fetch_and_process_feed(feed_url, existing_ids):
feed = feedparser.parse(feed_url)
new_articles = []
for entry in feed.entries:
article_id = entry.get("id") or entry.get("link")
if not article_id or article_id in existing_ids:
continue
title = entry.get("title", "Kein Titel")
date = entry.get("published", datetime.now().isoformat())
summary = entry.get("summary", "")
content = entry.get("content", [{}])[0].get("value") or entry.get("description", "")
soup = BeautifulSoup(content, "html.parser")
clean_text = soup.get_text(" ", strip=True)
images = extract_images_with_metadata(entry.link)
new_articles.append({
"id": article_id,
"title": title,
"date": date,
"summary": summary,
"text": clean_text,
"tags": [],
"status": "New",
"link": entry.get("link", ""),
"images": images
})
return new_articles
def process_articles(existing_ids):
feeds = load_feeds()
for url in feeds:
fetch_and_process_feed(url)
all_articles = load_articles()
new_entries = []
for feed in feeds:
if isinstance(feed, dict):
url = feed.get("url")
else:
url = feed
if not url:
continue
try:
logging.info(f"Lade Feed: {url}")
entries = fetch_and_process_feed(url, existing_ids)
new_entries.extend(entries)
logging.info(f"{len(entries)} neue Artikel gefunden in {url}")
except Exception as e:
logging.error(f"Fehler beim Verarbeiten von {url}: {e}")
# Nur neue Artikel speichern, deren ID noch nicht vorhanden ist
existing_article_ids = set(article["id"] for article in all_articles)
unique_new_entries = [a for a in new_entries if a["id"] not in existing_article_ids]
if unique_new_entries:
all_articles.extend(unique_new_entries)
save_articles(all_articles)
logging.info(f"{len(unique_new_entries)} neue Artikel gespeichert.")
else:
logging.info("Keine neuen Artikel gefunden.")
def rewrite_articles():
logging.info("Starte Umschreiben von Artikeln mit Status 'Rewrite' ...")
articles = load_articles()
updated = False
for article in articles:
if article["status"] != "Rewrite":
continue
try:
prompt = f"Fasse diesen Text neu und interessant zusammen:\n\n{article['summary']}"
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
rewritten = response.choices[0].message.content.strip()
article["text"] = rewritten
article["status"] = "Done"
if article.get("status") == "Rewrite":
try:
logging.info(f"✍️ Umschreiben von: {article['title']}")
prompt = f"Schreibe folgenden Artikel um und fasse ihn verständlich zusammen:\n\n{article['text']}"
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Du bist ein professioneller Redakteur."},
{"role": "user", "content": prompt}
]
)
new_text = response.choices[0].message.content.strip()
article["text"] = f"{article['title']}\n\n{new_text}"
article["status"] = "Process"
# Tags generieren
tag_prompt = f"Erstelle passende 3-5 Tags für diesen Text:\n\n{rewritten}"
tag_response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": tag_prompt}]
)
tags = [tag.strip() for tag in tag_response.choices[0].message.content.split(",")]
article["tags"] = tags
tag_prompt = f"Erstelle 3 passende, kurze Stichwörter (Tags) für diesen Artikel:\n\n{new_text}"
tag_response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Du bist ein Blog-Tag-Generator."},
{"role": "user", "content": tag_prompt}
]
)
tags_raw = tag_response.choices[0].message.content.strip()
tags = [tag.strip(" ,") for tag in tags_raw.replace("\n", ",").split(",") if tag.strip()]
article["tags"] = tags
updated = True
logging.info(f"✅ Artikel '{article['title']}' umgeschrieben.")
except Exception as e:
logging.error(f"❌ Fehler beim Umschreiben von '{article['title']}':\n{e}")
if updated:
save_articles(articles)
# Sicherstellen, dass Bildmetadaten vollständig sind
for img in article.get("images", []):
if "caption" not in img:
img["caption"] = "Kein Bildtitel vorhanden"
if "copyright" not in img:
img["copyright"] = "Unbekannt"
if "copyright_url" not in img:
img["copyright_url"] = "#"
logging.info(f"✅ Artikel umgeschrieben: {article['title']}")
except Exception as e:
logging.error(f"❌ Fehler beim Umschreiben von '{article['title']}': {e}")
save_articles(articles)
logging.info("Alle Artikel mit Status 'Rewrite' wurden verarbeitet.")