from pathlib import Path import json import logging from datetime import datetime from typing import Tuple, Dict, List from flask import Flask, render_template, request, send_from_directory app = Flask(__name__) # Logging konfigurieren import os # Logs-Verzeichnis erstellen, falls es nicht existiert logs_dir = Path(__file__).parent / "logs" logs_dir.mkdir(exist_ok=True) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(logs_dir / 'app.log', encoding='utf-8'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) def log_page_view(page: str, user_agent: str = None): """Protokolliert Seitenaufrufe ohne IP-Adressen""" timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') user_agent_clean = user_agent[:100] if user_agent else 'Unknown' logger.info(f"PAGE_VIEW: {page} | User-Agent: {user_agent_clean}") def log_search_query(search_params: dict, user_agent: str = None): """Protokolliert Suchanfragen ohne IP-Adressen""" timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') user_agent_clean = user_agent[:100] if user_agent else 'Unknown' # Suchparameter für Logging vorbereiten pos_str = ''.join(search_params.get('pos', [''] * 5)) includes = search_params.get('includes', '') excludes = search_params.get('excludes', '') sources = [] if search_params.get('use_ot'): sources.append('OT') if search_params.get('use_wf'): sources.append('WF') logger.info(f"SEARCH: pos='{pos_str}' includes='{includes}' excludes='{excludes}' sources={sources} | User-Agent: {user_agent_clean}") def load_words() -> Tuple[List[str], Dict[str, List[str]]]: data_dir = Path(__file__).parent / "data" txt_path = data_dir / "words_de_5.txt" json_path = data_dir / "words_de_5_sources.json" words: List[str] = [] sources_map: Dict[str, List[str]] = {} if txt_path.exists(): with txt_path.open("r", encoding="utf-8") as f: for line in f: word = line.strip().lower() if len(word) == 5 and word.isalpha(): words.append(word) if json_path.exists(): try: sources_map = json.loads(json_path.read_text(encoding="utf-8")) except Exception: sources_map = {} return words, sources_map def filter_words(words: List[str], position_letters: List[str], includes_text: str, excludes_text: str) -> List[str]: results: List[str] = [] includes_letters = [ch for ch in includes_text.lower() if ch.isalpha()] excludes_letters = [ch for ch in excludes_text.lower() if ch.isalpha()] for word in words: # feste Positionen if any(ch and word[idx] != ch for idx, ch in enumerate(position_letters)): continue # muss-enthalten if not all(ch in word for ch in includes_letters): continue # darf-nicht-enthalten if any(ch in word for ch in excludes_letters): continue results.append(word) return results @app.route("/", methods=["GET", "POST"]) def index(): # Seitenaufruf protokollieren log_page_view("index", request.headers.get('User-Agent')) all_words, sources_map = load_words() results_display: List[str] | None = None pos: List[str] = ["", "", "", "", ""] includes: str = "" excludes: str = "" use_ot: bool = True use_wf: bool = False if request.method == "POST": pos = [ (request.form.get("pos1") or "").strip().lower(), (request.form.get("pos2") or "").strip().lower(), (request.form.get("pos3") or "").strip().lower(), (request.form.get("pos4") or "").strip().lower(), (request.form.get("pos5") or "").strip().lower(), ] includes = (request.form.get("includes") or "").strip() excludes = (request.form.get("excludes") or "").strip() use_ot = request.form.get("use_ot") is not None use_wf = request.form.get("use_wf") is not None # Suchanfrage protokollieren search_params = { 'pos': pos, 'includes': includes, 'excludes': excludes, 'use_ot': use_ot, 'use_wf': use_wf } log_search_query(search_params, request.headers.get('User-Agent')) # 1) Buchstaben-/Positionssuche über alle Wörter matched = filter_words(all_words, pos, includes, excludes) # 2) Quellen-Filter nur auf Ergebnisansicht anwenden allowed = set() if use_ot: allowed.add("ot") if use_wf: allowed.add("wf") if allowed: results_display = [w for w in matched if any(src in allowed for src in sources_map.get(w, []))] else: # Keine Quelle gewählt → leere Anzeige (Suche wurde dennoch ausgeführt) results_display = [] return render_template( "index.html", results=results_display, pos=pos, includes=includes, excludes=excludes, words_count=len(all_words), sources_map=sources_map, use_ot=use_ot, use_wf=use_wf, error_message=None, ) @app.route('/manifest.webmanifest') def manifest_file(): log_page_view("manifest", request.headers.get('User-Agent')) return send_from_directory(Path(__file__).parent / 'static', 'manifest.webmanifest', mimetype='application/manifest+json') @app.route('/sw.js') def service_worker(): # Service Worker muss auf Top-Level liegen log_page_view("service_worker", request.headers.get('User-Agent')) return send_from_directory(Path(__file__).parent / 'static', 'sw.js', mimetype='application/javascript') if __name__ == "__main__": app.run(debug=True)