From 23a56b5acfe033f0422702d4684ffbe6042c059c Mon Sep 17 00:00:00 2001 From: Michaelis Date: Sun, 1 Mar 2026 20:01:52 +0000 Subject: [PATCH] Initial commit: Clean project structure without secrets in history --- .gitignore | 6 ++ README.md | 40 ++++++++++ cron_log.txt | 61 +++++++++++++++ gemini_instructions.md | 24 ++++++ urls.txt | 14 ++++ web_check.py | 163 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 308 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cron_log.txt create mode 100644 gemini_instructions.md create mode 100644 urls.txt create mode 100644 web_check.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce46afb --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +results.json +__pycache__/ +.gemini/ +*.log +GEMINI.md +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..817750d --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Polit-Scraper (Antifaschistisches Info-Tool Rottal-Inn) + +Ein automatisiertes Überwachungswerkzeug für Webseiten und Nachrichtenportale, spezialisiert auf die Erkennung von rechten Aktivitäten, staatlicher Repression und linker Solidarität im Raum Niederbayern. + +## 🚀 Kernfunktionen + +- **Intelligente KI-Analyse:** Nutzt die Gemini-CLI (Sitzung 1), um Textänderungen aus einer dezidiert linken, antifaschistischen Perspektive zu bewerten. +- **Geografische Hierarchie:** Priorisiert Meldungen aus dem Kerngebiet (Pfarrkirchen, Eggenfelden) und dem Landkreis Rottal-Inn gegenüber landesweiten News. +- **Cloudflare-Bypass:** Dank `cloudscraper` werden auch durch Cloudflare geschützte Seiten (z.B. AfD-Fraktionen) zuverlässig ausgelesen. +- **Deep-Analysis & Link-Veredelung:** Extrahiert Links aus neuen Artikeln. Die KI bewertet den Kontext und schickt den direkten Link zum Volltext an das Endgerät. +- **ntfy-Benachrichtigungen:** + - **Public Kanal:** Relevante politische News (ohne Passwort lesbar). + - **Admin Kanal:** Technische Fehler und Statusmeldungen (Passwort-geschützt). + - **Priorisierung:** Nutzt ntfy-Prioritäten (1-5) für unterschiedliche Vibrations- und Soundmuster je nach Wichtigkeit. + +## 🛠 Technische Details + +- **Sprache:** Python 3 +- **Bibliotheken:** `cloudscraper`, `BeautifulSoup4`, `requests` +- **KI-Integration:** Gemini Pro via CLI +- **Datenhaltung:** `results.json` (speichert den letzten Stand der Webseiten) +- **Konfiguration:** `urls.txt` (Liste der Ziel-URLs) und `gemini_instructions.md` (KI-Regelwerk) + +## 📋 Installation & Setup + +1. **Abhängigkeiten installieren:** + ```bash + pip install cloudscraper beautifulsoup4 requests --break-system-packages + ``` +2. **Gemini-CLI vorbereiten:** + Eine Sitzung mit Index 1 erstellen und die `gemini_instructions.md` als System-Prompt einlesen. +3. **Ziele definieren:** + URLs in die `urls.txt` eintragen (eine pro Zeile). +4. **Starten:** + ```bash + python3 web_check.py + ``` + +## ⚖️ Lizenz & Zweck +Dieses Tool dient ausschließlich der antifaschistischen Informationsarbeit und dem Monitoring politischer Gegner. diff --git a/cron_log.txt b/cron_log.txt new file mode 100644 index 0000000..bb6efb3 --- /dev/null +++ b/cron_log.txt @@ -0,0 +1,61 @@ +Ich prüfe: https://pfarrkirchen.de/aktuelles/bekanntmachungen/ + [OK] Keine Änderungen auf https://pfarrkirchen.de/aktuelles/bekanntmachungen/. +Ich prüfe: https://pfarrkirchen.de/aktuelles/veranstaltungen/ + [OK] Keine Änderungen auf https://pfarrkirchen.de/aktuelles/veranstaltungen/. +Ich prüfe: https://www.pnp.de/lokales/landkreis-rottal-inn/pfarrkirchen + [NEU] Änderung auf https://www.pnp.de/lokales/landkreis-rottal-inn/pfarrkirchen gefunden. Ich frage die KI... + [INFO] KI sagt: Nicht relevant. +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn. +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn/eggenfelden + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn/eggenfelden. +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn/simbach + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn/simbach. +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn/pfarrkirchen + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn/pfarrkirchen. +Ich prüfe: https://afdbayern.de/kreis-ortsverbaende/niederbayern/rottal-inn/ + [OK] Keine Änderungen auf https://afdbayern.de/kreis-ortsverbaende/niederbayern/rottal-inn/. +Ich prüfe: https://afdbayern.de/aktuelles/ + [OK] Keine Änderungen auf https://afdbayern.de/aktuelles/. +Ich prüfe: https://afdkompakt.de/landesverbaende/lv-bayern/ + [OK] Keine Änderungen auf https://afdkompakt.de/landesverbaende/lv-bayern/. +Ich prüfe: https://www.afdfraktionrottalinn.de/news/ + [OK] Keine Änderungen auf https://www.afdfraktionrottalinn.de/news/. +Ich prüfe: https://gruene-rottal-inn.de/aktuelles/ + [OK] Keine Änderungen auf https://gruene-rottal-inn.de/aktuelles/. +Ich prüfe: https://gruene-rottal-inn.de/termine/ + [OK] Keine Änderungen auf https://gruene-rottal-inn.de/termine/. +Ich prüfe: https://gruene-pfarrkirchen.de/aktuelles/ + [OK] Keine Änderungen auf https://gruene-pfarrkirchen.de/aktuelles/. + +Fertig. Gute Nacht! +Ich prüfe: https://pfarrkirchen.de/aktuelles/bekanntmachungen/ + [OK] Keine Änderungen auf https://pfarrkirchen.de/aktuelles/bekanntmachungen/. +Ich prüfe: https://pfarrkirchen.de/aktuelles/veranstaltungen/ + [OK] Keine Änderungen auf https://pfarrkirchen.de/aktuelles/veranstaltungen/. +Ich prüfe: https://www.pnp.de/lokales/landkreis-rottal-inn/pfarrkirchen + [NEU] Änderung auf https://www.pnp.de/lokales/landkreis-rottal-inn/pfarrkirchen gefunden. Ich frage die KI... +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn. +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn/eggenfelden + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn/eggenfelden. +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn/simbach + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn/simbach. +Ich prüfe: https://www.pnp.de/polizei/landkreis-rottal-inn/pfarrkirchen + [OK] Keine Änderungen auf https://www.pnp.de/polizei/landkreis-rottal-inn/pfarrkirchen. +Ich prüfe: https://afdbayern.de/kreis-ortsverbaende/niederbayern/rottal-inn/ + [OK] Keine Änderungen auf https://afdbayern.de/kreis-ortsverbaende/niederbayern/rottal-inn/. +Ich prüfe: https://afdbayern.de/aktuelles/ + [OK] Keine Änderungen auf https://afdbayern.de/aktuelles/. +Ich prüfe: https://afdkompakt.de/landesverbaende/lv-bayern/ + [OK] Keine Änderungen auf https://afdkompakt.de/landesverbaende/lv-bayern/. +Ich prüfe: https://www.afdfraktionrottalinn.de/news/ + [OK] Keine Änderungen auf https://www.afdfraktionrottalinn.de/news/. +Ich prüfe: https://gruene-rottal-inn.de/aktuelles/ + [OK] Keine Änderungen auf https://gruene-rottal-inn.de/aktuelles/. +Ich prüfe: https://gruene-rottal-inn.de/termine/ + [OK] Keine Änderungen auf https://gruene-rottal-inn.de/termine/. +Ich prüfe: https://gruene-pfarrkirchen.de/aktuelles/ + [OK] Keine Änderungen auf https://gruene-pfarrkirchen.de/aktuelles/. + +Fertig. Gute Nacht! diff --git a/gemini_instructions.md b/gemini_instructions.md new file mode 100644 index 0000000..1f7e3bf --- /dev/null +++ b/gemini_instructions.md @@ -0,0 +1,24 @@ +# SYSTEM-INSTRUKTION: Antifaschistischer News-Analyst (STRENGSTER FOKUS) + +## DEINE IDENTITÄT & POLITISCHER DOKTUS +Du bist ein linker, antifaschistischer Analyst. Du bewertest Nachrichten aus einer radikalen, solidarischen Perspektive. Dein Ziel ist es, nur Informationen zu liefern, die für direkten Aktivismus oder als Warnung vor Rechten/Repression dienen. + +## RELEVANZ-FILTER (EXTREM SELEKTIV) +- **FOKUS:** Rottal-Inn (Pfarrkirchen, Eggenfelden). +- **IGNORIEREN:** SPD/Grüne/CSU-Mainstream (Standard-Termine), allgemeine Kriminalität. +- **WICHTIG:** Rechte Bedrohung, linke Solidarität, staatliche Repression. + +## GEOGRAFIE-HIERARCHIE +1. Pfarrkirchen/Eggenfelden (Max) +2. Rottal-Inn (High) +3. Niederbayern (Default) + +## FORMAT (NUR JSON) +{ + "relevant": boolean, + "titel": "[Politischer Titel]", + "nachricht": "[Politischer Inhalt]", + "category": "[demo|socialmedia|polizei|stadt|solidaritaet|info]", + "priority": integer (1-5), + "direct_link": "[URL]" +} diff --git a/urls.txt b/urls.txt new file mode 100644 index 0000000..b3b0b70 --- /dev/null +++ b/urls.txt @@ -0,0 +1,14 @@ +https://pfarrkirchen.de/aktuelles/bekanntmachungen/ +https://pfarrkirchen.de/aktuelles/veranstaltungen/ +https://www.pnp.de/lokales/landkreis-rottal-inn/pfarrkirchen +https://www.pnp.de/polizei/landkreis-rottal-inn +https://www.pnp.de/polizei/landkreis-rottal-inn/eggenfelden +https://www.pnp.de/polizei/landkreis-rottal-inn/simbach +https://www.pnp.de/polizei/landkreis-rottal-inn/pfarrkirchen +https://afdbayern.de/kreis-ortsverbaende/niederbayern/rottal-inn/ +https://afdbayern.de/aktuelles/ +https://afdkompakt.de/landesverbaende/lv-bayern/ +https://www.afdfraktionrottalinn.de/news/ +https://gruene-rottal-inn.de/aktuelles/ +https://gruene-rottal-inn.de/termine/ +https://gruene-pfarrkirchen.de/aktuelles/ diff --git a/web_check.py b/web_check.py new file mode 100644 index 0000000..63b8ff1 --- /dev/null +++ b/web_check.py @@ -0,0 +1,163 @@ +import cloudscraper +import json +import os +import subprocess +import difflib +import sys +from bs4 import BeautifulSoup +from urllib.parse import urljoin +from datetime import datetime +from dotenv import load_dotenv + +# --- KONFIGURATION --- +# Laden der Umgebungsvariablen aus .env Datei +load_dotenv() + +ADMIN_USER = os.getenv("ADMIN_USER") +ADMIN_PW = os.getenv("ADMIN_PW") +NTFY_BASE = os.getenv("NTFY_BASE") + +JSON_DATEI = "./results.json" +GEMINI_SESSION = "1" + +# ntfy-Themen (Öffentlich) +TOPIC_PUBLIC = "polit-scraper-public" +TOPIC_ADMIN = "polit-scraper-admin" + +# UTF-8 für Konsole erzwingen +sys.stdout.reconfigure(encoding='utf-8') + +# Cloudscraper initialisieren (Chrome-Emulation) +scraper = cloudscraper.create_scraper( + browser={'browser': 'chrome', 'platform': 'windows', 'desktop': True} +) + +def analysiere_mit_gemini(text, links): + """KI-Analyse: Text und Links an Gemini senden, JSON extrahieren.""" + links_text = "\nRelevante Links: " + ", ".join(links) if links else "" + anweisung = f"Analysiere diesen Text: {text}{links_text}" + befehl = ["gemini", "-r", GEMINI_SESSION, "-p", anweisung] + try: + prozess = subprocess.run(befehl, capture_output=True, text=True, encoding='utf-8') + antwort_roh = prozess.stdout.strip() + start = antwort_roh.find('{') + ende = antwort_roh.rfind('}') + 1 + if start != -1 and ende != -1: + return json.loads(antwort_roh[start:ende]) + return {"relevant": False} + except Exception as e: + print(f" [!] KI-Fehler: {e}") + return {"relevant": False} + +def sende_public(titel, nachricht, klick_url, category="info", priority=3): + """Öffentlicher ntfy-Versand mit Priorität und Klick-URL.""" + if not NTFY_BASE: return + url = f"{NTFY_BASE}/{TOPIC_PUBLIC}" + emoji_mapping = { + "demo": "rotating_light,loudspeaker", + "socialmedia": "detective,no_entry", + "polizei": "police_car,warning", + "stadt": "cityscape,newspaper", + "solidaritaet": "fist,heart", + "info": "newspaper" + } + tags = emoji_mapping.get(category.lower(), "newspaper") + headers = { + "Title": titel.encode('utf-8'), + "Tags": tags, + "Click": klick_url, + "Priority": str(priority) + } + try: + import requests + requests.post(url, data=nachricht.encode("utf-8"), headers=headers, + auth=(ADMIN_USER, ADMIN_PW), timeout=5) + except Exception as e: + print(f" [!] Fehler Public-Versand: {e}") + +def sende_admin(titel, nachricht): + """Admin-Benachrichtigung bei technischen Fehlern.""" + if not NTFY_BASE: return + url = f"{NTFY_BASE}/{TOPIC_ADMIN}" + headers = {"Title": titel.encode('utf-8'), "Tags": "warning,skull", "Priority": "4"} + try: + import requests + requests.post(url, data=nachricht.encode("utf-8"), headers=headers, + auth=(ADMIN_USER, ADMIN_PW), timeout=5) + except Exception as e: + print(f" [!] Fehler Admin-Versand: {e}") + +def lade_gedaechtnis(): + if not os.path.exists(JSON_DATEI): return {} + with open(JSON_DATEI, "r", encoding='utf-8') as f: + try: return json.load(f) + except: return {} + +def speichere_gedaechtnis(daten): + with open(JSON_DATEI, "w", encoding='utf-8') as f: + json.dump(daten, f, indent=4, ensure_ascii=False) + +def extrahiere_links(soup, basis_url): + links = [] + for a in soup.find_all('a', href=True): + links.append(urljoin(basis_url, a['href'])) + return list(set(links)) + +def verarbeite_website(url, gedaechtnis): + try: + antwort = scraper.get(url, timeout=15) + antwort.encoding = 'utf-8' + status = antwort.status_code + + if status != 200: + print(f" [!] Seite fehlerhaft: {url} (Status: {status})") + sende_admin("Fehler bei Webseite", f"{url} lieferte Status {status}.") + return + + soup = BeautifulSoup(antwort.text, "html.parser") + hauptbereich = soup.find("main") or soup.find("article") or soup.body or soup + neuer_text = hauptbereich.get_text(separator=" ", strip=True) + + alter_eintrag = gedaechtnis.get(url, {}) + alter_text = alter_eintrag.get("inhalt", "") + + if alter_text != neuer_text: + print(f" [NEU] Änderung auf {url} gefunden. Ich frage die KI...") + alt_z = alter_text.splitlines() + neu_z = neuer_text.splitlines() + diff = [z[2:] for z in difflib.ndiff(alt_z, neu_z) if z.startswith("+ ")] + text_fuer_ki = "\n".join(diff) + alle_links = extrahiere_links(hauptbereich, url) + + if text_fuer_ki.strip(): + analyse = analysiere_mit_gemini(text_fuer_ki, alle_links) + if analyse.get("relevant") == True: + print(f" [!] KI bestätigt Relevanz: {analyse.get('titel')}") + klick_url = analyse.get("direct_link", url) + sende_public( + titel=analyse.get("titel", "Polit-Update"), + nachricht=analyse.get("nachricht", "Neu auf der Seite"), + klick_url=klick_url, + category=analyse.get("category", "info"), + priority=analyse.get("priority", 3) + ) + + gedaechtnis[url] = {"inhalt": neuer_text, "zeit": str(datetime.now()), "status": status} + else: + print(f" [OK] Keine Änderungen auf {url}.") + + except Exception as e: + print(f" ❌ Fehler bei {url}: {e}") + sende_admin("Programmfehler", f"{url}: {e}") + +# --- HAUPTPROGRAMM --- +gedaechtnis = lade_gedaechtnis() +with open("./urls.txt", "r", encoding='utf-8') as datei: + for zeile in datei: + adresse = zeile.strip() + if not adresse or adresse.startswith("#"): continue + print(f"Ich prüfe: {adresse}") + verarbeite_website(adresse, gedaechtnis) + +speichere_gedaechtnis(gedaechtnis) +print("\nFertig. Gute Nacht!")