🔖 Release v1.4.5

This commit is contained in:
Oliver 2025-07-07 11:37:22 +02:00
parent 691c9e00b6
commit e7d98dba3a
10 changed files with 1322 additions and 147 deletions

34
.github/release.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: 📦 Release
on:
push:
tags:
- 'v*'
jobs:
release:
name: 🔖 GitHub Release
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Repository
uses: actions/checkout@v4
- name: 🧪 Version anzeigen
run: |
echo "Tag: ${{ github.ref_name }}"
- name: 📜 Release erstellen aus CHANGELOG
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body_path: .github/release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 📝 Release Notes aus CHANGELOG extrahieren
run: |
version="${{ github.ref_name }}"
awk "/## \[$version\]/,/^## \[/" CHANGELOG.md | head -n -1 > .github/release_notes.md || true

11
.gitignore vendored
View file

@ -24,11 +24,14 @@ 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
# Start Bash ignirieren
start.sh
# Interne Informationen ignorieren
internal/start.sh
internal/copy_files.sh
internal/_line.txt

52
app.py
View file

@ -15,25 +15,16 @@ import os
st.set_page_config(page_title="📰 RSS Artikel Manager", layout="wide")
st.title("📰 RSS Artikel Manager")
# RSS Feed Verwaltung
# === Sidebar: 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:
if new_feed and new_feed not in [f.get("url", f) for f in feeds]:
feeds.append({"url": new_feed})
save_feeds(feeds)
st.sidebar.success("Feed hinzugefügt")
#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
if st.sidebar.button("🔄 Alle Feeds neu laden"):
existing_ids = [a["id"] for a in load_articles()]
process_articles(existing_ids)
@ -43,27 +34,40 @@ if st.sidebar.button("✍️ Artikel umschreiben (Rewrite)"):
rewrite_articles()
st.rerun()
# Artikelübersicht
# === Hauptbereich: Artikelübersicht ===
st.header("📋 Artikelübersicht")
status_filter = st.selectbox("Status filtern", ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"])
status_filter = st.selectbox("Status filtern", ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"], index=1)
articles = load_articles()
if status_filter != "Alle":
articles = [a for a in articles if a.get("status") == status_filter]
# Tabelle anzeigen
# === Artikel-Tabelle ===
if articles:
st.markdown("### 📄 Übersichtstabelle")
st.write("**Spaltenübersicht:** Auswahl | Datum | Titel | Zusammenfassung | Wörter | Tags | Status")
for article in articles:
has_incomplete_images = any(
not all(k in img and img[k] for k in ("caption", "copyright", "copyright_url"))
for img in article.get("images", [])
)
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])
date_str = article["date"]
if "GMT" in date_str or "+" in date_str:
date_str = datetime.strptime(date_str, "%a, %d %b %Y %H:%M:%S %z").strftime("%d.%m.%y")
else:
date_str = date_str[:10]
st.markdown(date_str)
with cols[2]:
st.markdown(f"**{article['title']}**")
title = f"**{article['title']}**"
if has_incomplete_images:
title += " ⚠️"
st.markdown(title)
with cols[3]:
st.markdown(article.get("summary", "")[:150])
with cols[4]:
@ -83,13 +87,23 @@ if articles:
st.markdown("#### ✍️ Artikeltext")
st.code(f"{article['title']}\n\n{article['text']}\n\nQuelle: {article['link']}", language="markdown")
st.markdown("#### 🏷️ Tags")
st.markdown("#### 🌿 Tags")
st.code(", ".join(article.get("tags", [])), language="markdown")
st.markdown("#### 🖼️ Bilder")
for img in article.get("images", []):
for i, img in enumerate(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', '#')})")
with st.form(f"edit_image_{article['id']}_{i}", clear_on_submit=False):
caption = st.text_input("Bildtitel", value=img.get("caption", ""))
copyright = st.text_input("Copyright", value=img.get("copyright", ""))
copyright_url = st.text_input("Quelle", value=img.get("copyright_url", ""))
if st.form_submit_button("Änderungen speichern"):
img["caption"] = caption or "Kein Bildtitel vorhanden"
img["copyright"] = copyright or "Unbekannt"
img["copyright_url"] = copyright_url or "#"
save_articles(articles)
st.success("Bilddaten gespeichert")
else:
st.info("Keine Artikel für den gewählten Status gefunden.")

File diff suppressed because one or more lines are too long

View file

@ -54,3 +54,112 @@ INFO:root:✅ Alle Feeds erfolgreich verarbeitet.
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.
2025-07-05 16:41:31,067 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:41:34,773 - INFO - 8 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:41:34,775 - INFO - 8 neue Artikel gespeichert.
2025-07-05 16:43:12,785 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:43:13,160 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:43:13,160 - INFO - Keine neuen Artikel gefunden.
2025-07-05 16:43:52,517 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:43:56,021 - INFO - 9 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:43:56,022 - INFO - 9 neue Artikel gespeichert.
INFO:root:🔄 Starte Artikelabruf...
INFO:root:✅ 10 neue Artikel aus https://www.camping-news.de/rss/
INFO:root:✅ Artikelverarbeitung abgeschlossen.
INFO:root:🔄 Starte Artikelabruf...
INFO:root:✅ 9 neue Artikel aus https://www.camping-news.de/rss/
INFO:root:✅ Artikelverarbeitung abgeschlossen.
2025-07-07 09:26:35,739 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 09:26:36,041 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 09:26:36,413 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 09:26:36,414 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 09:26:36,640 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 09:26:36,640 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 09:26:36,862 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 09:26:36,863 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 09:26:37,085 - INFO - 16 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 09:26:37,086 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5924
2025-07-07 09:26:37,315 - INFO - 9 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5924
2025-07-07 09:26:37,316 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5922
2025-07-07 09:26:37,544 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5922
2025-07-07 09:26:37,545 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5921
2025-07-07 09:26:37,779 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5921
2025-07-07 09:26:37,779 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5920
2025-07-07 09:26:38,001 - INFO - 9 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5920
2025-07-07 09:26:38,001 - INFO - 8 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 09:26:38,003 - INFO - 8 neue Artikel gespeichert.
2025-07-07 09:27:24,535 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 09:27:24,690 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 09:27:24,914 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 09:27:24,915 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 09:27:25,127 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 09:27:25,127 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 09:27:25,345 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 09:27:25,346 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 09:27:25,530 - INFO - 16 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 09:27:25,531 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926
2025-07-07 09:27:25,761 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926
2025-07-07 09:27:25,762 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923
2025-07-07 09:27:25,983 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923
2025-07-07 09:27:25,984 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5924
2025-07-07 09:27:26,176 - INFO - 9 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5924
2025-07-07 09:27:26,177 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5922
2025-07-07 09:27:26,391 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5922
2025-07-07 09:27:26,391 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5921
2025-07-07 09:27:26,583 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5921
2025-07-07 09:27:26,583 - INFO - 9 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 09:27:26,584 - INFO - 9 neue Artikel gespeichert.
2025-07-07 10:00:07,513 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:00:07,906 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 10:00:08,272 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 10:00:08,273 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 10:00:08,599 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 10:00:08,599 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 10:00:08,834 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 10:00:08,835 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 10:00:09,058 - INFO - 16 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 10:00:09,059 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926
2025-07-07 10:00:09,268 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926
2025-07-07 10:00:09,268 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5920
2025-07-07 10:00:09,486 - INFO - 9 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5920
2025-07-07 10:00:09,487 - INFO - 6 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:00:09,488 - INFO - 6 neue Artikel gespeichert.
2025-07-07 10:00:30,026 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:00:30,189 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:00:30,189 - INFO - Keine neuen Artikel gefunden.
2025-07-07 10:00:51,192 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:00:51,355 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:00:51,355 - INFO - Keine neuen Artikel gefunden.
2025-07-07 10:01:08,635 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:01:08,815 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:01:08,815 - INFO - Keine neuen Artikel gefunden.
2025-07-07 10:01:50,297 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:01:50,469 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 10:01:50,741 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5927
2025-07-07 10:01:50,742 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 10:01:50,940 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5928
2025-07-07 10:01:50,941 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 10:01:51,127 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5929
2025-07-07 10:01:51,128 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 10:01:51,316 - INFO - 16 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5925
2025-07-07 10:01:51,317 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926
2025-07-07 10:01:51,508 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926
2025-07-07 10:01:51,510 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923
2025-07-07 10:01:51,703 - INFO - 10 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923
2025-07-07 10:01:51,704 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5922
2025-07-07 10:01:51,944 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5922
2025-07-07 10:01:51,945 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5921
2025-07-07 10:01:52,167 - INFO - 17 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5921
2025-07-07 10:01:52,168 - INFO - 📷 Extrahiere Bilder von https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5920
2025-07-07 10:01:52,380 - INFO - 9 Bilder gefunden bei https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5920
2025-07-07 10:01:52,381 - INFO - 9 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:01:52,382 - INFO - 9 neue Artikel gespeichert.
2025-07-07 10:13:46,271 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:13:46,597 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:13:46,598 - INFO - Keine neuen Artikel gefunden.
2025-07-07 10:36:37,313 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:36:37,741 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:36:37,741 - INFO - Keine neuen Artikel gefunden.
2025-07-07 10:38:21,362 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-07 10:38:21,538 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-07 10:38:21,538 - INFO - Keine neuen Artikel gefunden.

47
main.py
View file

@ -12,11 +12,12 @@ import openai
load_dotenv()
# Logging konfigurieren
# === Logging konfigurieren ===
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, "rss_tool.log")
logging.basicConfig(
filename=os.path.join(log_dir, "rss_tool.log"),
filename=log_file,
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
@ -25,7 +26,6 @@ openai.api_key = os.getenv("OPENAI_API_KEY")
ARTICLES_FILE = "data/articles.json"
FEEDS_FILE = "data/feeds.json"
VALID_STATUSES = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
@ -35,26 +35,29 @@ def load_feeds():
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 load_articles():
if not os.path.exists(ARTICLES_FILE):
return []
with open(ARTICLES_FILE, "r") as f:
articles = json.load(f)
# 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 = []
@ -88,41 +91,44 @@ def fetch_and_process_feed(feed_url, existing_ids):
return new_articles
def process_articles(existing_ids):
feeds = load_feeds()
all_articles = load_articles()
articles_by_id = {article["id"]: article for article in all_articles if "id" in article}
new_entries = []
for feed in feeds:
if isinstance(feed, dict):
url = feed.get("url")
else:
url = feed
url = feed.get("url") if isinstance(feed, dict) else 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}")
logging.exception(f"Fehler beim Verarbeiten von {url}:")
# 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]
added = 0
for entry in new_entries:
if entry["id"] not in articles_by_id:
articles_by_id[entry["id"]] = entry
added += 1
else:
logging.info(f"Artikel bereits vorhanden, wird übersprungen: {entry['title']}")
if unique_new_entries:
all_articles.extend(unique_new_entries)
save_articles(all_articles)
logging.info(f"{len(unique_new_entries)} neue Artikel gespeichert.")
if added > 0:
save_articles(list(articles_by_id.values()))
logging.info(f"{added} neue Artikel gespeichert.")
else:
logging.info("Keine neuen Artikel gefunden.")
def rewrite_articles():
articles = load_articles()
changed = False
for article in articles:
if article.get("status") == "Rewrite":
try:
@ -151,7 +157,6 @@ def rewrite_articles():
tags = [tag.strip(" ,") for tag in tags_raw.replace("\n", ",").split(",") if tag.strip()]
article["tags"] = tags
# Sicherstellen, dass Bildmetadaten vollständig sind
for img in article.get("images", []):
if "caption" not in img:
img["caption"] = "Kein Bildtitel vorhanden"
@ -161,9 +166,11 @@ def rewrite_articles():
img["copyright_url"] = "#"
logging.info(f"✅ Artikel umgeschrieben: {article['title']}")
changed = True
except Exception as e:
logging.error(f"❌ Fehler beim Umschreiben von '{article['title']}': {e}")
logging.exception(f"❌ Fehler beim Umschreiben von '{article['title']}':")
if changed:
save_articles(articles)
logging.info("Alle Artikel mit Status 'Rewrite' wurden verarbeitet.")

23
pages/log_viewer.py Normal file
View file

@ -0,0 +1,23 @@
# log_viewer.py
import streamlit as st
import os
st.set_page_config(page_title="🧾 Log Viewer", layout="wide")
st.title("🧾 Letzte Logeinträge anzeigen")
LOG_FILE = "logs/rss_tool.log"
MAX_LINES = 500
if not os.path.exists(LOG_FILE):
st.warning("Keine Logdatei gefunden.")
else:
with open(LOG_FILE, "r") as f:
lines = f.readlines()
st.write(f"Letzte {min(len(lines), MAX_LINES)} Zeilen aus `{LOG_FILE}`:")
st.code("".join(lines[-MAX_LINES:]), language="text")
if st.button("🔄 Neu laden"):
st.rerun()

View file

@ -52,3 +52,4 @@ typing-inspection==0.4.1
typing_extensions==4.14.0
tzdata==2025.2
urllib3==2.5.0
typer[all]==0.12.3

View file

@ -1,51 +1,60 @@
# utils/image_extractor.py
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import logging
def extract_images_with_metadata(article_url):
"""
Versucht, Bilder mit Bildunterschrift und Copyright aus dem Originalartikel zu extrahieren.
Gibt eine Liste mit Dictionaries zurück: {url, alt, copyright_text, copyright_link}
Gibt eine Liste mit Dictionaries zurück: {url, alt, copyright, copyright_url, caption}
"""
images = []
try:
logging.info(f"📷 Extrahiere Bilder von {article_url}")
response = requests.get(article_url, timeout=10)
if response.status_code != 200:
logging.warning(f"Keine gültige Antwort von {article_url} (Status {response.status_code})")
return []
soup = BeautifulSoup(response.content, "html.parser")
images = []
for img_tag in soup.find_all("img"):
src = img_tag.get("src")
if not src:
continue
# Vollständige URL bauen
img_url = urljoin(article_url, src)
alt_text = img_tag.get("alt", "").strip()
# Copyright-Hinweis suchen: z.B. umgebender <figure> oder <div>
copyright_text = ""
copyright_link = ""
copyright_text = "Unbekannt"
copyright_link = article_url
caption = alt_text or "Bild aus Originalartikel"
parent = img_tag.find_parent(["figure", "div"])
if parent:
caption = parent.find("figcaption")
if caption:
copyright_text = caption.get_text(strip=True)
link_tag = caption.find("a")
figcaption = parent.find("figcaption")
if figcaption:
caption = figcaption.get_text(strip=True)
link_tag = figcaption.find("a")
if link_tag and link_tag.has_attr("href"):
copyright_link = link_tag["href"]
copyright_text = link_tag.get_text(strip=True)
images.append({
image_data = {
"url": img_url,
"alt": alt_text or "Bild aus Originalartikel",
"copyright_text": copyright_text or "Unbekannt",
"copyright_link": copyright_link or article_url
})
"alt": alt_text,
"caption": caption or "Kein Bildtitel vorhanden",
"copyright": copyright_text or "Unbekannt",
"copyright_url": copyright_link or article_url
}
images.append(image_data)
logging.info(f"{len(images)} Bilder gefunden bei {article_url}")
return images
except Exception as e:
print(f"[extract_images_with_metadata] Fehler bei {article_url}: {e}")
logging.exception(f"Fehler bei der Bildextraktion aus {article_url}:")
return []

132
versioning.py Normal file
View file

@ -0,0 +1,132 @@
# versioning.py
import re
import subprocess
from pathlib import Path
from datetime import datetime
import typer
app = typer.Typer()
CHANGELOG_FILE = Path("CHANGELOG.md")
VERSION_FILE = Path("__version__.py")
VERSION_PATTERN = r"## \[v?(\d+\.\d+\.\d+)\]"
def get_latest_version():
content = CHANGELOG_FILE.read_text(encoding="utf-8")
matches = re.findall(VERSION_PATTERN, content)
return matches[0] if matches else "0.0.0"
def bump_version(version: str, level: str = "patch") -> str:
major, minor, patch = map(int, version.split("."))
if level == "major":
return f"{major + 1}.0.0"
elif level == "minor":
return f"{major}.{minor + 1}.0"
return f"{major}.{minor}.{patch + 1}"
def write_version_file(version: str):
VERSION_FILE.write_text(f"VERSION = \"{version}\"\n", encoding="utf-8")
typer.echo(f"🔢 __version__.py aktualisiert auf {version}")
def prepend_changelog(version: str):
today = datetime.today().strftime("%Y-%m-%d")
new_entry = f"\n\n## [v{version}] {today}\n\n### 💡 Neue Funktionen\n- \n\n### 🔧 Änderungen & Fixes\n- \n\n### 📦 Internes\n- "
original = CHANGELOG_FILE.read_text(encoding="utf-8")
CHANGELOG_FILE.write_text(new_entry + original, encoding="utf-8")
typer.echo(f"📝 Neuer Eintrag für v{version} zu CHANGELOG.md hinzugefügt")
def validate_changelog(version: str) -> bool:
content = CHANGELOG_FILE.read_text(encoding="utf-8")
pattern = rf"## \[v?{re.escape(version)}\](.*?)^## \["
match = re.search(pattern, content + "\n## [", re.DOTALL | re.MULTILINE)
if match:
section = match.group(1).strip()
if any(line.strip() != "-" for line in section.splitlines() if line.strip()):
return True
typer.echo("⚠️ CHANGELOG-Eintrag ist noch leer oder unvollständig.")
return False
def create_git_tag(version: str):
try:
subprocess.run(["git", "add", str(CHANGELOG_FILE), str(VERSION_FILE)], check=True)
subprocess.run(["git", "commit", "-m", f"🔖 Release v{version}"], check=True)
subprocess.run(["git", "tag", f"v{version}"], check=True)
typer.echo(f"🏷️ Git-Tag 'v{version}' erstellt und commit durchgeführt.")
except subprocess.CalledProcessError:
typer.echo("⚠️ Git-Fehler beim Taggen oder Committen. Bitte manuell prüfen.")
def push_to_github():
try:
subprocess.run(["git", "push"], check=True)
subprocess.run(["git", "push", "--tags"], check=True)
typer.echo("🚀 Änderungen und Tags an GitHub gepusht.")
except subprocess.CalledProcessError:
typer.echo("⚠️ Fehler beim Pushen zu GitHub. Bitte Zugang oder Netzwerk prüfen.")
@app.command()
def list():
"Listet alle verfügbaren Versionen aus dem CHANGELOG"
typer.echo("\n📚 Verfügbare Versionen im CHANGELOG:")
content = CHANGELOG_FILE.read_text(encoding="utf-8")
versions = re.findall(VERSION_PATTERN, content)
for v in versions:
typer.echo(f"- v{v}")
@app.command()
def rollback():
"Letzte Version zurückrollen (Tag löschen + Commit zurücknehmen)"
last_version = get_latest_version()
if typer.confirm(f"⚠️ Letzte Version 'v{last_version}' wirklich zurücknehmen?"):
try:
subprocess.run(["git", "tag", "-d", f"v{last_version}"], check=True)
subprocess.run(["git", "reset", "--hard", "HEAD~1"], check=True)
typer.echo(f"🔙 Version 'v{last_version}' wurde zurückgerollt.")
except subprocess.CalledProcessError:
typer.echo("❌ Rollback fehlgeschlagen.")
else:
typer.echo("⛔ Abgebrochen.")
@app.command()
def create(level: str = typer.Option("patch", help="Versionstyp: patch, minor oder major"),
push: bool = typer.Option(False, help="Änderungen direkt an GitHub pushen")):
"Neue Version erstellen inkl. CHANGELOG, Git-Tag und optional Push"
current_version = get_latest_version()
next_version = bump_version(current_version, level)
typer.echo(f"💡 Aktuelle Version: {current_version}")
typer.echo(f"🚀 Neue Version: {next_version}")
if typer.confirm("Version übernehmen und eintragen?"):
write_version_file(next_version)
prepend_changelog(next_version)
typer.echo("\nBitte CHANGELOG.md bearbeiten und danach fortfahren.")
typer.prompt("Drücke Enter, sobald du den neuen Abschnitt ausgefüllt hast")
if not validate_changelog(next_version):
typer.echo("❌ Release abgebrochen: Bitte fülle den CHANGELOG-Eintrag aus.")
raise typer.Exit(code=1)
create_git_tag(next_version)
if push:
push_to_github()
typer.echo(f"✅ Version {next_version} erfolgreich erstellt.")
else:
typer.echo("❌ Abgebrochen.")
if __name__ == "__main__":
app()