Initial commit: Clean project structure without secrets in history

This commit is contained in:
Michaelis
2026-03-01 20:01:52 +00:00
commit 23a56b5acf
6 changed files with 308 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
results.json
__pycache__/
.gemini/
*.log
GEMINI.md
.env

40
README.md Normal file
View File

@@ -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.

61
cron_log.txt Normal file
View File

@@ -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!

24
gemini_instructions.md Normal file
View File

@@ -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]"
}

14
urls.txt Normal file
View File

@@ -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/

163
web_check.py Normal file
View File

@@ -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!")