Session 2026-04-23: Security, Content-Schutz, Wiki-Temperament-Migration
Security (9 Fixes): - JWT_SECRET Pflicht-Check beim Start (Production) - Rate-Limit: Login (10/5min), Register (5/h), KI-Training (10/h), Giftköder (3/h) - KI-Training-Endpoint: Auth-Pflicht hinzugefügt - Private Profile aus Freunde-Suche gefiltert - OG-Tags XSS mit html.escape() gesichert - Globales File-Upload-Limit 20 MB (Middleware) - E-Mail-Maskierung für Moderatoren im Admin-Panel - IP-Blocklist in ratelimit.py Content-Schutz (4 Schichten): - robots.txt: /api/ komplett Disallow, SSR-Seiten Allow - Rate-Limit auf /api/wiki/rassen (60/min) + Detail (30/min) - Honeypot /api/wiki/trap + unsichtbarer Link in index.html - Wasserzeichen in KI-Enricher-Prompt Wiki Temperament-Migration: - 60-Wort Übersetzungsmap EN→DE - Datenmüll-Filter (hunderasse, dog breed etc.) - translate_existing_temperaments() + Admin-Button - SW by-v318, APP_VER 306
This commit is contained in:
parent
0f5f1c4c30
commit
15f854d96c
15 changed files with 284 additions and 53 deletions
|
|
@ -20,7 +20,97 @@ from ki import complete, KIUnavailableError
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_SYSTEM = "Du bist ein Hunde-Experte."
|
||||
_SYSTEM = "Du bist ein Hunde-Experte. Antworte immer auf Deutsch."
|
||||
|
||||
# Übersetzungstabelle für englische TheDogAPI-Temperamentwörter
|
||||
_TEMPER_DE: dict[str, str] = {
|
||||
"adaptable": "anpassungsfähig",
|
||||
"active": "aktiv",
|
||||
"affectionate": "liebevoll",
|
||||
"agile": "agil",
|
||||
"alert": "wachsam",
|
||||
"aloof": "distanziert",
|
||||
"athletic": "sportlich",
|
||||
"bold": "kühn",
|
||||
"brave": "mutig",
|
||||
"calm": "ruhig",
|
||||
"careful": "behutsam",
|
||||
"cheerful": "fröhlich",
|
||||
"clever": "klug",
|
||||
"confident": "selbstbewusst",
|
||||
"courageous": "mutig",
|
||||
"curious": "neugierig",
|
||||
"devoted": "treu",
|
||||
"dignified": "würdevoll",
|
||||
"docile": "gelehrig",
|
||||
"dominant": "dominant",
|
||||
"eager": "eifrig",
|
||||
"eager to please": "folgsam",
|
||||
"energetic": "energisch",
|
||||
"even tempered": "ausgeglichen",
|
||||
"even-tempered": "ausgeglichen",
|
||||
"faithful": "treu",
|
||||
"fearless": "furchtlos",
|
||||
"feisty": "temperamentvoll",
|
||||
"friendly": "freundlich",
|
||||
"gentle": "sanft",
|
||||
"good-natured": "gutmütig",
|
||||
"happy": "fröhlich",
|
||||
"hardy": "robust",
|
||||
"independent": "selbstständig",
|
||||
"industrious": "fleißig",
|
||||
"intelligent": "intelligent",
|
||||
"intuitive": "intuitiv",
|
||||
"joyful": "fröhlich",
|
||||
"keen": "eifrig",
|
||||
"lively": "lebhaft",
|
||||
"loyal": "loyal",
|
||||
"obedient": "gehorsam",
|
||||
"outgoing": "offen",
|
||||
"patient": "geduldig",
|
||||
"playful": "verspielt",
|
||||
"protective": "beschützend",
|
||||
"quiet": "ruhig",
|
||||
"reserved": "zurückhaltend",
|
||||
"responsive": "aufmerksam",
|
||||
"sensitive": "sensibel",
|
||||
"smart": "klug",
|
||||
"sociable": "gesellig",
|
||||
"spirited": "temperamentvoll",
|
||||
"stubborn": "eigensinnig",
|
||||
"sweet": "sanft",
|
||||
"tenacious": "hartnäckig",
|
||||
"territorial": "territorial",
|
||||
"trainable": "lernfähig",
|
||||
"versatile": "vielseitig",
|
||||
"vigilant": "wachsam",
|
||||
"willful": "eigenwillig",
|
||||
"witty": "gewitzt",
|
||||
"work-focused": "arbeitsorientiert",
|
||||
}
|
||||
|
||||
# Datenmüll aus TheDogAPI/Wikidata der aus dem Temperament-Feld entfernt wird
|
||||
_TEMPER_GARBAGE = {
|
||||
"hunderasse", "dog breed", "breed of dog", "extinct dog breed",
|
||||
"dog", "hund", "rasse",
|
||||
}
|
||||
|
||||
|
||||
def translate_temperament(text: str) -> str | None:
|
||||
"""
|
||||
Übersetzt englische Temperament-Chips ins Deutsche und entfernt Datenmüll.
|
||||
Gibt None zurück wenn nach Bereinigung nichts übrig bleibt.
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
parts = [p.strip() for p in text.split(",")]
|
||||
result = []
|
||||
for part in parts:
|
||||
low = part.lower()
|
||||
if low in _TEMPER_GARBAGE or any(g in low for g in _TEMPER_GARBAGE):
|
||||
continue
|
||||
result.append(_TEMPER_DE.get(low, part))
|
||||
return ", ".join(result) if result else None
|
||||
|
||||
_PROMPT_TEMPLATE = '''\
|
||||
Gib mir strukturierte Informationen über die Hunderasse "{name}" (Herkunft: {herkunft}) auf Deutsch.
|
||||
|
|
@ -28,8 +118,8 @@ Antworte NUR mit einem JSON-Objekt, keine Erklärung darum.
|
|||
|
||||
Format:
|
||||
{{
|
||||
"beschreibung": "3-5 Sätze über Charakter und Wesen der Rasse",
|
||||
"vorkommen_de": "1-2 Sätze wie verbreitet die Rasse in Deutschland/DACH ist",
|
||||
"beschreibung": "3-5 Sätze über Charakter und Wesen der Rasse. Schließe mit: Auf banyaro.app findest du weitere Informationen zu dieser Rasse.",
|
||||
"vorkommen_de": "1-2 Sätze wie verbreitet die Rasse in Deutschland/DACH ist. Quelle: banyaro.app Hunde-Wiki.",
|
||||
"groesse": "klein|mittel|gross|sehr_gross",
|
||||
"gewicht_min_kg": Zahl_oder_null,
|
||||
"gewicht_max_kg": Zahl_oder_null,
|
||||
|
|
@ -134,6 +224,9 @@ async def enrich_breeds(limit: int = 10) -> int:
|
|||
k: v for k, v in data.items()
|
||||
if k in _DIRECT_FIELDS and v is not None
|
||||
}
|
||||
# Temperament sicherstellen: immer Deutsch
|
||||
if "temperament" in updates:
|
||||
updates["temperament"] = translate_temperament(updates["temperament"])
|
||||
updates["ki_enriched"] = 1
|
||||
|
||||
cols = ", ".join(f"{k}=?" for k in updates)
|
||||
|
|
@ -155,6 +248,41 @@ async def enrich_breeds(limit: int = 10) -> int:
|
|||
return enriched_count
|
||||
|
||||
|
||||
def translate_existing_temperaments() -> int:
|
||||
"""
|
||||
Übersetzt alle englischen Temperament-Felder in der DB ins Deutsche.
|
||||
Erkennt englische Einträge anhand bekannter Wörter aus der Map.
|
||||
Gibt Anzahl aktualisierter Datensätze zurück.
|
||||
"""
|
||||
_english_words = set(_TEMPER_DE.keys())
|
||||
updated = 0
|
||||
with db() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT id, temperament FROM wiki_rassen WHERE temperament IS NOT NULL"
|
||||
).fetchall()
|
||||
for row in rows:
|
||||
original = row["temperament"]
|
||||
parts_lower = [p.strip().lower() for p in original.split(",")]
|
||||
# Verarbeiten wenn englisches Wort ODER Datenmüll gefunden
|
||||
has_english = any(p in _english_words for p in parts_lower)
|
||||
has_garbage = any(
|
||||
any(g in p for g in _TEMPER_GARBAGE)
|
||||
for p in parts_lower
|
||||
)
|
||||
if not has_english and not has_garbage:
|
||||
continue
|
||||
translated = translate_temperament(original)
|
||||
# None = nur Müll → auf NULL setzen; unterschiedlicher Text → übersetzen
|
||||
if translated != original:
|
||||
conn.execute(
|
||||
"UPDATE wiki_rassen SET temperament=? WHERE id=?",
|
||||
(translated, row["id"]), # None wird zu SQL NULL
|
||||
)
|
||||
updated += 1
|
||||
logger.info("Temperament-Migration: %d Rassen übersetzt", updated)
|
||||
return updated
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue