Initial commit: Clean project structure without secrets in history
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
results.json
|
||||
__pycache__/
|
||||
.gemini/
|
||||
*.log
|
||||
GEMINI.md
|
||||
.env
|
||||
40
README.md
Normal file
40
README.md
Normal 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
61
cron_log.txt
Normal 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
24
gemini_instructions.md
Normal 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
14
urls.txt
Normal 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
163
web_check.py
Normal 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!")
|
||||
Reference in New Issue
Block a user