From b7f9e7a6bb8b38ad6c955d51db3b521a15d0e382 Mon Sep 17 00:00:00 2001 From: Daniel Nagel Date: Tue, 7 Apr 2026 08:30:53 +0000 Subject: [PATCH] =?UTF-8?q?Impressums=20Seite.=20Datenschutzerkl=C3=A4rung?= =?UTF-8?q?.=20Servitor=20Basic=20Test=20funktion.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/servitor.py | 15 +++ gui/imprint.json | 124 ++++++++++++++++++ gui/imprint_gui.py | 75 +++++++++++ ...e_statistic.py => league_statistic_gui.py} | 0 gui/main_gui.py | 8 +- gui/match_gui.py | 3 +- main.py | 6 +- 7 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 bot/servitor.py create mode 100644 gui/imprint.json create mode 100644 gui/imprint_gui.py rename gui/{league_statistic.py => league_statistic_gui.py} (100%) diff --git a/bot/servitor.py b/bot/servitor.py new file mode 100644 index 0000000..3832529 --- /dev/null +++ b/bot/servitor.py @@ -0,0 +1,15 @@ + +import requests + +webhook_url = "https://liga-n8n.au-fab.eu/webhook/21066d30-2757-4d64-9c72-e439ecd70f94" + +discord_tokken = "MTQ3NTg1ODU5OTQwNTQyNDcwMQ.GRo63W.1erAk0janBqS8NlHB6FHEbXM1bolIOPH2Uc4vs" + +def send_message(event, text): + data = { + event: "Neuer Spieler", + text: "mr_teels" + } + + response = requests.post(webhook_url, json=data) + diff --git a/gui/imprint.json b/gui/imprint.json new file mode 100644 index 0000000..190143a --- /dev/null +++ b/gui/imprint.json @@ -0,0 +1,124 @@ +{ + "version": 1, + "default_locale": "de", + "locales": { + "de": { + "page": { + "title": "Info" + }, + "sections": [ + { + "id": "rules", + "order": 10, + "title": "Allgemeine Regeln", + "blocks": [ + { + "type": "markdown", + "content": [ + "Mit der Anmeldung in der Liga stimmst du folgenden Richtlinien und Regeln zu:\n", + "* Freundliches und Respektvolles Verhalten gegenüber anderen Mitspielern.", + "* Kein Cheating / absichtliches Falschmelden in die Liga! Fehler können passieren.", + "* Es giltet ein Gentleman's Agreement: 'Wir schreiben keine absichtlichen Konterlisten. Schlecht geschriebene Regeln werden nicht zu unserem Vorteil ausgenutzt!'.", + "* Wir spielen mit **Intent**. Sprich: Es werden keine Regeln der eigenen Armee dem Gegner verheimlicht um Überraschungen zu produzieren." + ] + } + ] + }, + { + "id": "imprint", + "order": 20, + "title": "Impressum", + "blocks": [ + { + "type": "markdown", + "content": "**Verantwortlich:** Daniel Nagel (Privatperson) \n**Adresse:** PRIVAT \n**Kontakt:** admin@danielnagel.at" + } + ] + }, + { + "id": "privacy", + "order": 30, + "title": "Datenschutz", + "blocks": [ + { + "type": "markdown", + "content": [ + "Die Daten die für den Betrieb der Liga nötig sind werden auf einem privaten Server in Österreich gespeichert. Das bezieht ein: \n", + "* Spieler Namen (Automatisch generiert oder Selbst eingetragen)", + "* Spieler ID in form der Discord Nutzer ID.", + "* Discord Nutzernamen", + "* Discord url Pfad zum Profilfoto", + "\nDiese Daten werden **keinem** anderen Dienst, Service, Anbieter oder Drittem zur Verfügung gestellt!", + "\nEine Löschung der eigenen Daten kann jederzeit beantragt werden wenn man nicht mehr an der Liga teilnehmen will. \n", + "Bestimmte Ereignisse oder Eingaben in die App werden in eine Log Funktion geschrieben (mit Zeitstempel) für die Qualitätssicherung und störungsfreie Funktion der App. Unter anderem: \n", + "* Alle Eingaben vom Match Formular. Inkl. Spieler der es eingegeben hat.", + "* Wenn ein Match abgelehnt oder gelöscht wird. Inkl. Spieler der es gelöscht/abgelehnt hat.", + "* Genaue Aufschlüsselung der MMR Punkte Berechnung pro eingetragenem Match.", + "* Neu angelegte Spieler und ihren Discord Namen." + ] + } + ] + } + ] + }, + "en": { + "page": { + "title": "Info" + }, + "sections": [ + { + "id": "rules", + "order": 10, + "title": "General Rules", + "blocks": [ + { + "type": "markdown", + "content": [ + "By registering for the league you agree to the following guidelines and rules:\n", + "* Friendly and respectful behaviour towards all other players.", + "* No cheating or intentional misreporting of match results! Honest mistakes can happen.", + "* A Gentleman's Agreement applies: 'We do not write intentional counter-lists. Poorly written rules are not exploited for our own advantage!'.", + "* We play with **Intent**. This means: no rules of your own army are hidden from your opponent in order to create surprises." + ] + } + ] + }, + { + "id": "imprint", + "order": 20, + "title": "Imprint", + "blocks": [ + { + "type": "markdown", + "content": "**Responsible:** Daniel Nagel (Private individual) \n**Address:** PRIVATE \n**Contact:** admin@danielnagel.at" + } + ] + }, + { + "id": "privacy", + "order": 30, + "title": "Privacy Policy", + "blocks": [ + { + "type": "markdown", + "content": [ + "Data required to operate the league is stored on a private server located in Austria. This includes: \n", + "* Player names (automatically generated or manually entered).", + "* Player ID in the form of the Discord user ID.", + "* Discord usernames.", + "* Discord URL path to the profile picture.", + "\nThis data is **not** shared with any other service, provider, or third party.", + "\nDeletion of your own data can be requested at any time if you no longer wish to participate in the league. \n", + "Certain events or inputs within the app are written to a log function (with timestamp) to ensure quality and stable operation of the app. This includes: \n", + "* All inputs from the match form, including the player who submitted it.", + "* When a match is rejected or deleted, including the player who rejected/deleted it.", + "* Detailed breakdown of the MMR point calculation per submitted match.", + "* Newly created players and their Discord names." + ] + } + ] + } + ] + } + } +} diff --git a/gui/imprint_gui.py b/gui/imprint_gui.py new file mode 100644 index 0000000..484b181 --- /dev/null +++ b/gui/imprint_gui.py @@ -0,0 +1,75 @@ +import json +from pathlib import Path +from nicegui import ui +from fastapi import Request +from gui import gui_style + +# JSON laden - liegt laut Screenshot direkt in gui/ +_JSON_PATH = Path(__file__).resolve().parent / 'imprint.json' + +with open(_JSON_PATH, encoding='utf-8') as f: + imprint_data: dict = json.load(f) + +# ------------------------------------------------------- + +SECTION_TITLES_FALLBACK = { + "howto": "Kurzanleitung", + "rules": "Allgemeine Regeln", + "imprint": "Impressum", + "privacy": "Datenschutz", +} + +def _get_locale(request: Request) -> str: + default = imprint_data.get('default_locale', 'de') + lang = request.query_params.get('lang', default) + if lang not in imprint_data.get('locales', {}): + lang = default + return lang + +def _get_locale_options() -> list[str]: + return list(imprint_data.get('locales', {}).keys()) + +def setup_routes(): + @ui.page('/info', dark=True) + def info_page(request: Request): + gui_style.apply_design() + + lang = _get_locale(request) + locale_data = imprint_data['locales'][lang] + + page_title = locale_data.get('page', {}).get('title', 'Info') + sections = sorted(locale_data.get('sections', []), key=lambda s: s.get('order', 0)) + + with ui.row().classes("w-full items-center justify-between"): + ui.button(text="Zurück", on_click=lambda: ui.navigate.to('/')) + ui.label(page_title).classes("text-xl font-bold") + ui.select( + options=_get_locale_options(), + value=lang, + label='Sprache', + on_change=lambda e: ui.navigate.to(f'/info?lang={e.value}'), + ).classes("min-w-[8rem]") + + ui.separator().classes("my-4") + + with ui.column().classes("w-full gap-4"): + for section in sections: + title = section.get('title', SECTION_TITLES_FALLBACK.get(section.get('id', ''), '')) + blocks = section.get('blocks', []) + + with ui.card().classes("w-full"): + if title: + ui.label(title).classes("text-lg font-semibold mb-2") + + for block in blocks: + btype = block.get('type', 'markdown') + if btype == 'markdown': + raw = block.get('content', '') + # Unterstützt sowohl String als auch Array + if isinstance(raw, list): + content = '\n'.join(raw) + else: + content = raw + ui.markdown(content).classes("prose max-w-none") + elif btype == 'divider': + ui.separator().classes("my-2") diff --git a/gui/league_statistic.py b/gui/league_statistic_gui.py similarity index 100% rename from gui/league_statistic.py rename to gui/league_statistic_gui.py diff --git a/gui/main_gui.py b/gui/main_gui.py index adc4251..b088ee9 100644 --- a/gui/main_gui.py +++ b/gui/main_gui.py @@ -16,19 +16,17 @@ def setup_routes(admin_discord_id): # Fehlt die Discord-ID (altes Cookie) ODER sagt die Datenbank, dass da was nicht stimmt? if not discord_id or not data_api.validate_user_session(db_id, discord_id): - # Ausweis ungültig! Wir vernichten das Cookie sofort. app.storage.user.clear() ui.notify("Deine Sitzung ist ungültig oder abgelaufen. Bitte neu einloggen!", color="negative") ui.navigate.reload() return # ----------------------------------------- - # --------------------------- - # --- NAVIGATIONSLEISTE (HEADER) --- + # --- NAVIGATIONSLEISTE (HEADER) # --------------------------- - with ui.header().classes('items-center justify-between bg-zinc-900 shadow-lg').props("reveal reveal-offset=1"): + with ui.header(fixed=False).classes('items-center justify-between bg-zinc-900 shadow-lg'): # --- LINKE SEITE --- # Vereinslogo und den Titel in einer eigenen Reihe (Reihe 1) @@ -291,3 +289,5 @@ def setup_routes(admin_discord_id): else: ui.label("Noch keine Spiele absolviert.").classes("text-infotext italic") + with ui.footer(fixed=False).classes('items-center justify-between bg-zinc-900 shadow-lg'): + ui.button(icon="description", on_click=lambda: ui.navigate.to('/info')) \ No newline at end of file diff --git a/gui/match_gui.py b/gui/match_gui.py index de18d6e..0873928 100644 --- a/gui/match_gui.py +++ b/gui/match_gui.py @@ -5,10 +5,9 @@ from match_calculations import calc_match from gui.info_text import info_system def setup_routes(): - # 1. Die {}-Klammern definieren eine dynamische Variable in der URL @ui.page('/add-match/{system_name}', dark=True) - def match_form_page(system_name: str): # <--- WICHTIG: Hier fangen wir das Wort aus der URL auf! + def match_form_page(system_name: str): # <-- Hier wird der Name des Spielsystems gefiltert. gui_style.apply_design() # --- SICHERHEITS-CHECK --- diff --git a/main.py b/main.py index 2a7621a..85cbfd5 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ from dotenv import load_dotenv from nicegui import ui, app from data import database -from gui import main_gui, match_gui, discord_login, league_statistic, admin_gui, match_history_gui +from gui import main_gui, match_gui, discord_login, league_statistic_gui, admin_gui, match_history_gui, imprint_gui from wood import logger from gui.info_text import info_system @@ -30,11 +30,11 @@ logger.setup_log_db() # 3. Seitenrouten aufbauen main_gui.setup_routes(admin_discord_id) discord_login.setup_login_routes() -league_statistic.setup_routes() +league_statistic_gui.setup_routes() match_gui.setup_routes() admin_gui.setup_routes() match_history_gui.setup_routes() - +imprint_gui.setup_routes() # 4. Wir starten die NiceGUI App ui.run(title="Westside Diceghost Liga", port=9000, storage_secret="EIN_super-geheimes_Pa$$wort#!", favicon="gui/pictures/wsdg.png")