Compare commits
7 Commits
87cb02aef4
...
106c8bb5e3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
106c8bb5e3 | ||
|
|
7b119a085f | ||
|
|
c905464924 | ||
|
|
b7f9e7a6bb | ||
|
|
b707dd96d6 | ||
|
|
397d454ebc | ||
|
|
f9ed925643 |
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,6 +5,7 @@
|
|||
# 2. Python Caches
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.nicegui/storage*
|
||||
|
||||
# 3. Virtuelle Umgebungen
|
||||
venv/
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Verzweifelter Kultist","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Daniel N.","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Stolpernder Meta-Chaser","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"113708052485636100","discord_name":"staelwulf","db_id":4,"display_name":"Max","discord_avatar_url":"https://cdn.discordapp.com/avatars/113708052485636100/d53339dd6a6659231c5c16645ba258df.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Blinder Grot","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Verzweifelter Kultist","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":1,"display_name":"Daniel N.","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Daniel N","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Daniel","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
15
bot/servitor.py
Normal file
15
bot/servitor.py
Normal file
|
|
@ -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)
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
|
|||
cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2))
|
||||
new_match_id = cursor.lastrowid
|
||||
|
||||
logger.log(f" New Match ID{new_match_id} in {system_name}. {get_player_name(player1_id)}:({score_p1}) -VS- {get_player_name(player2_id)}:({score_p2})")
|
||||
logger.log(f"{get_player_name(player1_id)}:({score_p1}) posted Match. System: {system_name}, {get_player_name(player2_id)}:({score_p2})")
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
|
|
|||
124
gui/imprint.json
Normal file
124
gui/imprint.json
Normal file
|
|
@ -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."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
75
gui/imprint_gui.py
Normal file
75
gui/imprint_gui.py
Normal file
|
|
@ -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")
|
||||
|
|
@ -12,10 +12,18 @@
|
|||
"**ACHTUNG:** Ein Spieler ist nur als Gegner auswählbar wenn er sich in der Liga angemeldet hat!",
|
||||
"Solltest du einen Fehler machen kannst du das 'falsche' Match auf der Hauptseite noch löschen bevor es bestätigt wurde."
|
||||
],
|
||||
"khorne": ["Khorne will Blut und Schädel!"],
|
||||
"tzeentch": ["tzeentch Pläne gehen auf!"],
|
||||
"basis_mmr": ["Berechnet mit dem MMR Unterschied."],
|
||||
|
||||
"mmr_calc": [
|
||||
"Die Berechnung und das System dahinter sind ein wenig komplex. Ich versuche trotzdem es so einfach wie möchlich zu erklären.",
|
||||
"**Khorne**: Khorne interresiert es nicht woher die Schädel kommen! hat man in den letzten 16Tagen (~2 Wochen) in dieser Liga schon gespielt, bekommt man 8 Punkte",
|
||||
"**Tzeentch**: Der Herr der Intriegen mag es wenn Pläne aufgehen. Ausgehend von den Maximalen Siegpunkten gewährt er einen Bonus wenn der Gegner mit vielen Punkten besiegt wird. 0-9 Punkte",
|
||||
"**Slaanesh**: Noch in Arbeit. Irgendwas mit dem ***Tyrann***, ***Prügelknabe***, ***Nemesis***. Das System steht noch nicht.",
|
||||
"**Nurgle**: Der Meister der Entrophie wird den Spielern ein wenig die MMR Punkte ***verotten***. Das System steht noch nicht!",
|
||||
"**Elo Faktor**: Elo beschreibt das verhältnis zwischen den Sieger MMR und Verlierer MMR. Damit wird festgestellt wer stärker und wer schwächer ist. Ein Stärkerer Spieler der einen Schwächeren besiegt, kriegt weniger MMR Punkte als umgekehrt. Die Berechnung ist etwas komplex. Führ mehr googelt bitte 'elo schach'. Der Elo Faktor bestimmt die ***Basis MMR***.",
|
||||
"**Rost Faktor**: Der Rostfaktor ist ein Punktefaktor der zum Einsatz kommt wenn ein Spieler eine Weile nicht mehr gespielt hat. Ab 30 Tagen ist er 0.8 . Von da an wird er graduell weniger bis 90 Tage (0.1). Verhindert das ***eingerostete*** Spieler fertigemacht werden oder gelegentliche gute Spieler zu weit hoch schießen.",
|
||||
"**Gesamt Berechnung**:",
|
||||
"Sieger (w_base + w_khorne + slaanesh + tzeentch) * rust_factor).",
|
||||
"Verlierer: (-l_base + l_khorne - slaanesh - tzeentch) * rust_factor)"
|
||||
],
|
||||
"tyrann_info": [],
|
||||
"prügelknabe_info": [],
|
||||
"rang_info": [
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
@ -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 ---
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from nicegui import ui, app
|
||||
from data import data_api
|
||||
from gui import gui_style
|
||||
from gui.info_text.info_system import create_info_button
|
||||
|
||||
|
||||
def setup_routes():
|
||||
|
|
@ -19,6 +20,7 @@ def setup_routes():
|
|||
|
||||
with ui.row().classes('w-full items-center justify-between mb-6'):
|
||||
ui.label("Komplette Match Historie").classes("text-3xl font-bold text-white")
|
||||
create_info_button("mmr_calc")
|
||||
ui.button("Zurück", icon="arrow_back", on_click=lambda: ui.navigate.to('/')).classes('bg-zinc-700 text-white')
|
||||
|
||||
raw_matches = data_api.get_match_history_log(player_id)
|
||||
|
|
|
|||
6
main.py
6
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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user