diff --git a/.gitea/workflows/create_and_push_multiarch_container.yml b/.gitea/workflows/create_and_push_multiarch_container.yml index f31c6e1..dbf2e97 100644 --- a/.gitea/workflows/create_and_push_multiarch_container.yml +++ b/.gitea/workflows/create_and_push_multiarch_container.yml @@ -6,7 +6,7 @@ on: image_tag: description: '2. Tag für das Docker-Image (außer latest) (z.B. v1.0.0)' required: true - default: '0.7.3' + default: '1.0.0' env: image_name: mysteryhelfer diff --git a/.gitignore b/.gitignore index 74bf0ff..7b6240e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /.idea/ /app/data/morse-de.dic /app/data/t9-de.dic +/.venv/ diff --git a/Dockerfile b/Dockerfile index 82f9ff7..3a13ca1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,34 @@ -FROM python:3.13-slim - +# Copyright (c) 2025 Martin Kayser (tebarius) +# Licensed under the MIT License. See LICENSE file in the project root. +ARG PYTHON_VERSION="3.14" +FROM python:${PYTHON_VERSION}-slim LABEL authors="tebarius" LABEL description="tebarius Mysteryhelfer web" -WORKDIR /app +ARG PYTHON_VERSION +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PATH="/myst-venv/bin:$PATH" RUN apt-get update \ && apt-get install -y --no-install-recommends curl \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +WORKDIR /app COPY requirements.txt . -COPY ./app /app -RUN python -m pip install --upgrade pip \ +RUN python -m venv /myst-venv \ + && python -m pip install --upgrade pip \ && pip install --no-cache-dir -r requirements.txt - +COPY ./app /app # ein bisschen Patchen um auch beim Bookmarken oder Versenden der Webadresse per Messenger den richtigen Titel und das # richtige Favicon zu verwenden/sehen -COPY ./app/images/favicon.ico /usr/local/lib/python3.13/site-packages/streamlit/static/favicon.ico +COPY ./app/images/favicon.ico /myst-venv/lib/python${PYTHON_VERSION}/site-packages/streamlit/static/favicon.ico RUN sed -i -e 's|favicon\.png|favicon.ico|' \ - -e 's|.*|tebarius Mysteryhelfer (web)|' \ - /usr/local/lib/python3.13/site-packages/streamlit/static/index.html \ + -e 's|.*|tebarius Mysteryhelfer (web)|' \ + /myst-venv/lib/python${PYTHON_VERSION}/site-packages/streamlit/static/index.html \ && useradd -m -u 1000 myst \ && chown -R myst:myst /app USER myst @@ -31,4 +37,4 @@ EXPOSE 8501 HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health -ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"] +ENTRYPOINT ["/bin/bash", "entrypoint.sh"] diff --git a/app/app.py b/app/app.py index 3992531..a7c52af 100644 --- a/app/app.py +++ b/app/app.py @@ -7,11 +7,16 @@ import matplotlib.pyplot as plt import helper standard_output = ('#### Um den HILFE-Text zu einzelnen Funktionen aufzurufen bitte die Funktion mit leerem' - ' Eingabefeld aufrufen.') + ' Eingabefeld aufrufen.\n' + '__Diese APP ist OpenSource:__ \n' + '- Sourcecode: https://gitea.tebarius.duckdns.org/tebarius/Mysteryhelfer-web\n' + '- Docker-Images sind verfügbar via:\n' + ' - https://hub.docker.com/r/tebarius/mysteryhelfer\n' + ' - https://gitea.tebarius.duckdns.org/tebarius/-/packages/container/mysteryhelfer/latest') st.set_page_config( # we do also patching static-files of streamlit in the docker-container so bookmarks will have - # the same favicon and if posting links for example in whatsapp they will have the same title + # the same favicon and if posting links for example in WhatsApp they will have the same title page_title="tebarius Mysteryhelfer (web)", page_icon="images/favicon.ico", layout="wide", @@ -224,7 +229,6 @@ if 'input_text' not in st.session_state: st.session_state.input_text = "" if 'output_text' not in st.session_state: st.session_state.output_text = standard_output - st.session_state.output_text += helper.generate_special_files() if 'map_data' not in st.session_state: st.session_state.map_data = None if 'graph_data' not in st.session_state: diff --git a/app/entrypoint.sh b/app/entrypoint.sh new file mode 100644 index 0000000..22237e2 --- /dev/null +++ b/app/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Copyright (c) 2025 Martin Kayser (tebarius) +# Licensed under the MIT License. See LICENSE file in the project root. + +set -e # Script bei Fehler abbrechen + +# 1) einmalig bei Containerstart ausführen +echo "Generiere Special-WBs..." +python generate_special_wbs.py + +# 2) Streamlit-App starten +echo "Starte Hauptprogramm..." +streamlit run app.py --server.port=8501 --server.address=0.0.0.0 diff --git a/app/generate_special_wbs.py b/app/generate_special_wbs.py new file mode 100644 index 0000000..20ee2f8 --- /dev/null +++ b/app/generate_special_wbs.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# Copyright (c) 2025 Martin Kayser (tebarius) +# Licensed under the MIT License. See LICENSE file in the project root. +import os + +def t9_generate_t9de(): + alphabet = {'A': '2', 'B': '2', 'C': '2', 'D': '3', 'E': '3', 'F': '3', 'G': '4', + 'H': '4', 'I': '4', 'J': '5', 'K': '5', 'L': '5', 'M': '6', 'N': '6', + 'O': '6', 'P': '7', 'Q': '7', 'R': '7', 'S': '7', 'T': '8', 'U': '8', + 'V': '8', 'W': '9', 'X': '9', 'Y': '9', 'Z': '9', '1': '1', '2': '2', + '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', + '0': '0', 'Ñ': '6', 'É': '3', 'È': '3', 'À': '2', 'Ü': '8', 'Ö': '6', + 'Ä': '2', '@': '1', '?': '1', '=': '0', ':': '1', '/': '1', '.': '1', + '-': '1', ',': '1', '+': '0', ')': '1', '(': '1', 'SS': '7'} + ofile = open("./data/t9-de.dic", "a", encoding="iso-8859-15") + file = open("./data/german.dic", "r", encoding="iso-8859-15") + for zeile in file: + omsg = "" + zeile = zeile.rstrip() + try: + for char in zeile: + omsg = omsg + alphabet[char.upper()] + except KeyError: + continue + else: + ofile.write(omsg + "," + zeile + "\n") + file.close() + ofile.close() + +def remorse_generate_morsede(): + alphabet = {'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', + 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', + 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', + 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--', 'Z': '--..', '1': '.----', + '2': '..---', '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', + '8': '---..', '9': '----.', '0': '-----', 'Ñ': '--.--', 'É': '..-..', 'È': '.-..-', + 'À': '.--.-', 'Ü': '..--', 'Ö': '---.', 'Ä': '.-.-', '_': '..--.-', '@': '.--.-.', + '?': '..--..', '=': '-...-', ';': '-.-.-.', ':': '---...', '/': '-..-.', + '.': '.-.-.-', '-': '-....-', ',': '--..--', '+': '.-.-.', ')': '-.--.-', + '(': '-.--.', "'": '.----.', 'SS': '...--..'} + with open("./data/morse-de.dic", "w", encoding="iso-8859-1") as ofile: + for symbol, morse in alphabet.items(): + ofile.write(f"{morse},{symbol}\n") + with open("./data/german.dic", "r", encoding="iso-8859-1") as infile: + for line in infile: + word = line.strip() + try: + morse_word = ''.join(alphabet[char.upper()] for char in word) + ofile.write(f"{morse_word},{word}\n") + except KeyError: + # Überspringe Wörter mit nicht-unterstützten Zeichen + continue + infile.close() + ofile.close() + +def generate_special_files(): + if os.path.exists("./data/morse-de.dic"): + print("Übersprungen...[deutsches Re-Morse-Wörterbuch existiert bereits]") + else: + remorse_generate_morsede() + print("[deutsches Re-Morse-Wörterbuch wurde generiert]") + if os.path.exists("./data/t9-de.dic"): + print("Übersprungen...[deutsches T9-Wörterbuch wurde existiert bereits]") + else: + t9_generate_t9de() + print("[deutsches T9-Wörterbuch wurde generiert]") + +if __name__ == "__main__": + generate_special_files() diff --git a/app/helper.py b/app/helper.py index 8612f73..3da55b9 100644 --- a/app/helper.py +++ b/app/helper.py @@ -2,10 +2,10 @@ # Licensed under the MIT License. See LICENSE file in the project root. import math from re import match # für unkennify + +import folium import streamlit as st from streamlit_folium import st_folium -import folium -import os # ***recursive quersummenfunktion*** @@ -176,70 +176,6 @@ def unkennify(text): decoded = decoded + text[i:] return decoded -def generate_special_files(): - out="\n\n\n" - if os.path.exists("./data/morse-de.dic"): - pass - else: - remorse_generate_morsede() - out+=":blue[deutsches Re-Morse-Wörterbuch wurde generiert] \n" - if os.path.exists("./data/t9-de.dic"): - pass - else: - t9_generate_t9de() - out+=":blue[deutsches T9-Wörterbuch wurde generiert] \n" - return out - -def remorse_generate_morsede(): - alphabet = {'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', - 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', - 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', - 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--', 'Z': '--..', '1': '.----', - '2': '..---', '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', - '8': '---..', '9': '----.', '0': '-----', 'Ñ': '--.--', 'É': '..-..', 'È': '.-..-', - 'À': '.--.-', 'Ü': '..--', 'Ö': '---.', 'Ä': '.-.-', '_': '..--.-', '@': '.--.-.', - '?': '..--..', '=': '-...-', ';': '-.-.-.', ':': '---...', '/': '-..-.', - '.': '.-.-.-', '-': '-....-', ',': '--..--', '+': '.-.-.', ')': '-.--.-', - '(': '-.--.', "'": '.----.', 'SS': '...--..'} - with open("./data/morse-de.dic", "w", encoding="iso-8859-1") as ofile: - for symbol, morse in alphabet.items(): - ofile.write(f"{morse},{symbol}\n") - with open("./data/german.dic", "r", encoding="iso-8859-1") as infile: - for line in infile: - word = line.strip() - try: - morse_word = ''.join(alphabet[char.upper()] for char in word) - ofile.write(f"{morse_word},{word}\n") - except KeyError: - # Überspringe Wörter mit nicht-unterstützten Zeichen - continue - infile.close() - ofile.close() - -def t9_generate_t9de(): - alphabet = {'A': '2', 'B': '2', 'C': '2', 'D': '3', 'E': '3', 'F': '3', 'G': '4', - 'H': '4', 'I': '4', 'J': '5', 'K': '5', 'L': '5', 'M': '6', 'N': '6', - 'O': '6', 'P': '7', 'Q': '7', 'R': '7', 'S': '7', 'T': '8', 'U': '8', - 'V': '8', 'W': '9', 'X': '9', 'Y': '9', 'Z': '9', '1': '1', '2': '2', - '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', - '0': '0', 'Ñ': '6', 'É': '3', 'È': '3', 'À': '2', 'Ü': '8', 'Ö': '6', - 'Ä': '2', '@': '1', '?': '1', '=': '0', ':': '1', '/': '1', '.': '1', - '-': '1', ',': '1', '+': '0', ')': '1', '(': '1', 'SS': '7'} - ofile = open("./data/t9-de.dic", "a", encoding="iso-8859-15") - file = open("./data/german.dic", "r", encoding="iso-8859-15") - for zeile in file: - omsg = "" - zeile = zeile.rstrip() - try: - for char in zeile: - omsg = omsg + alphabet[char.upper()] - except KeyError: - continue - else: - ofile.write(omsg + "," + zeile + "\n") - file.close() - ofile.close() - def rail_encrypt(plain_text: str, rails: int): arr = [["" for _ in range(len(plain_text))] for _ in range(rails)] r = 0 @@ -293,10 +229,10 @@ def rail_decrypt(cipher: str, rails: int): y -= 1 elif down: y += 1 - elif down is False and y == 0: + elif not down and y == 0: down = True y += 1 - elif down is False: + elif not down: y -= 1 return out #from folium.map import DivIcon @@ -329,4 +265,4 @@ def show_map_folium(df): # Schritt 5: In Streamlit anzeigen st_folium(m, use_container_width=True) else: - st.markdown(":red[Keine sinnvoll darstellbaren Koordinaten gefunden]") \ No newline at end of file + st.markdown(":red[Keine sinnvoll darstellbaren Koordinaten gefunden]") diff --git a/app/hilfetexte.py b/app/hilfetexte.py index 2e0a8d6..9d7cc02 100644 --- a/app/hilfetexte.py +++ b/app/hilfetexte.py @@ -227,7 +227,7 @@ naknak_to_text = ("### NakNak to Text\n" "Diese Funktion übersetzt das Geschnatter in verständliche Buchstaben/Zeichen. !!Bitte unbedingt " "die Groß- und Kleinschreibung beibehalten!! Wer diesen Quatsch unbedingt umgekehrt übersetzten " "will, wird wohl etwas suchen müssen, da der Original-Übersetzer, der unter " - "http://uebersetzer.schnatterente.net erreichbar war, nicht mehr online ist und ich ganz bestimmt " + "https://uebersetzer.schnatterente.net erreichbar war, nicht mehr online ist und ich ganz bestimmt " "keinen hier integrieren werde.") navajo_to_text = ("### Navajo-ABC to Text\n" @@ -240,7 +240,7 @@ navajo_to_text = ("### Navajo-ABC to Text\n" "Wörter bzw. deren Schreibweise angeht im Netz, mit welchen codiert wird, weshalb ich hier nur die " "Dekodierung anbiete. weitere Codewörter und Erklärungen gibt es z.B. hier: " "https://ww2db.com/other.php?other_id=29 oder hier: " - "http://math.ucsd.edu/~crypto/Projects/RobertoSandoval/NavajoWindtalkers.pdf") + "https://math.ucsd.edu/~crypto/Projects/RobertoSandoval/NavajoWindtalkers.pdf") pi_suche = ("### PI Nachkommastellensuche\n" "Für die eingegebene Zahl/Zahlenreihe X wird versucht die entsprechende X. Nachkommastelle auszugeben " @@ -498,7 +498,7 @@ url_decode = ("### URL-decode\n" "Listing erscheinen wird es manchmal schwer die ursprüngliche URL und/oder den Dateinamen des " "Bildes noch lesen zu können. Die URL hat dort nun jeweils folgendes Muster: " "`https://imgproxy.geocaching.com/......?url=.....` wobei nach dem `?url=` dann die ursprüngliche " - "Bild-URL folgt, allerdings wird dabei dann aus `http://` folgendes `http%3A%2F%2F` und um genau " + "Bild-URL folgt, allerdings wird dabei dann aus `https://` folgendes `https%3A%2F%2F` und um genau " "dieses wieder normal lesbar zu machen dient diese Funktion") reversewig = ('### Reverse Wherigo zu Koordinaten\n' @@ -607,4 +607,4 @@ rlou_to_graph = ("### RLOU/RLUD -> Graph\n" "englischen Variante halt L=left, R=right, U=up, D=down ist. \n" "Die Funktion hier versucht das ganz halt zu malen und als Grafik auszugeben, wobei versucht wird " "automatisch zu erkennen ob die deutsche oder englische Variante zum Einsatz kommt. \n" - "Zum ausprobieren könnt ihr ja mal `ddurduu ddurduulr ddr rluu ddruul` probieren.") \ No newline at end of file + "Zum ausprobieren könnt ihr ja mal `ddurduu ddurduulr ddr rluu ddruul` probieren.") diff --git a/app/tools.py b/app/tools.py index 0ee4869..0129f48 100644 --- a/app/tools.py +++ b/app/tools.py @@ -2309,6 +2309,8 @@ def adfgx_kodieren(eingabetext, pw): return ":red[Es es werden genau zwei durch Komma getrennte Passwörter benötigt!]" pw[0] = pw[0].strip() pw[1] = pw[1].strip() + if not pw[0].isalpha() or not pw[1].isalpha(): + return ":red[Passwörter dürfen nur aus Buchstaben bestehen!]" pw1az = "" for b in pw[0] + alpha_az: if b in alpha_az and b not in pw1az: @@ -2334,7 +2336,6 @@ def adfgx_kodieren(eingabetext, pw): ctext1az = "" for b in klartext: ctext1az += w_baz[b] - print(ctext1az) ctext1za = "" for b in klartext: ctext1za += w_bza[b] @@ -2370,7 +2371,8 @@ def adfgx_kodieren(eingabetext, pw): else: ausgabe_za += i[j + 1] z += 1 - ausgabetext = f":blue[Passwort 1:] {pw[0]} \n" + ausgabetext = f":blue[verwendeter Eingabetext:] {klartext} \n" + ausgabetext += f":blue[Passwort 1:] {pw[0]} \n" ausgabetext += f":blue[Passwort 2:] {pw2} \n \n" ausgabetext += f":blue[kodiert mit Variante A-Z:] \n{ausgabe_az} \n \n" ausgabetext += f":blue[kodiert mit Variante Z-A:] \n{ausgabe_za}" @@ -2390,6 +2392,7 @@ def adfgx_dekodieren(eingabetext, pw): alpha_za = "ZYXWVUTSRQPONMLKIHGFEDCBA" text = text.upper() text = text.replace("J", "I") + text = text.replace(" ", "") pw = pw.upper() pw = pw.replace("J", "I") pw = pw.split(",") @@ -2397,6 +2400,11 @@ def adfgx_dekodieren(eingabetext, pw): return ":red[Es es werden genau zwei durch Komma getrennte Passwörter benötigt!]" pw[0] = pw[0].strip() pw[1] = pw[1].strip() + if not pw[0].isalpha() or not pw[1].isalpha(): + return ":red[Passwörter dürfen nur aus Buchstaben bestehen!]" + for zeichen in text: + if zeichen not in "ADFGX": + return ":red[Im Eingabetext dürfen nur Leerzeichen und die Buchstaben ADFGX vorkommen!]" pw1az = "" for b in pw[0] + alpha_az: if b in alpha_az and b not in pw1az: @@ -2466,6 +2474,8 @@ def adfgvx_kodieren(eingabetext, pw): return ":red[Es es werden genau zwei durch Komma getrennte Passwörter benötigt!]" pw[0] = pw[0].strip() pw[1] = pw[1].strip() + if not pw[0].isalnum() or not pw[1].isalnum(): + return ":red[Passwörter dürfen nur aus Buchstaben und Ziffern bestehen!]" pw1az09 = "" for b in pw[0] + alpha_az09: if b in alpha_az09 and b not in pw1az09: @@ -2526,7 +2536,8 @@ def adfgvx_kodieren(eingabetext, pw): else: ausgabe_90za += i[j + 1] z += 1 - ausgabetext = f":blue[Passwort 1:] {pw[0]} \n" + ausgabetext = f":blue[verwendeter Eingabetext:] {klartext} \n" + ausgabetext += f":blue[Passwort 1:] {pw[0]} \n" ausgabetext += f":blue[Passwort 2:] {pw2} \n \n" ausgabetext += f":blue[kodiert mit Variante A-Z,0-9:] \n{ausgabe_az09} \n \n" ausgabetext += f":blue[kodiert mit Variante 9-0,Z-A:] \n{ausgabe_90za}" @@ -2546,12 +2557,18 @@ def adfgvx_dekodieren(eingabetext, pw): alpha_az09 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" alpha_90za = "9876543210ZYXWVUTSRQPONMLKJIHGFEDCBA" text = text.upper() + text = text.replace(" ", "") pw = pw.upper() pw = pw.split(",") if len(pw) != 2: return ":red[Es es werden genau zwei durch Komma getrennte Passwörter benötigt!]" pw[0] = pw[0].strip() pw[1] = pw[1].strip() + if not pw[0].isalnum() or not pw[1].isalnum(): + return ":red[Passwörter dürfen nur aus Buchstaben und Ziffern bestehen!]" + for zeichen in text: + if zeichen not in "ADFGVX": + return ":red[Im Eingabetext dürfen nur Leerzeichen und die Buchstaben ADFGVX vorkommen!]" pw1az09 = "" for b in pw[0] + alpha_az09: if b in alpha_az09 and b not in pw1az09: diff --git a/readme.md b/readme.md index ebb362a..aa78095 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,3 @@ Der einfachst Weg, um die App lokal laufen zu lassen, ist mit Docker, wobei ich - `docker compose up -d` bei allen 3 Varianten ist die App anschließend im Browser unter http://127.0.0.1:8501/ aufrufbar - -__HINWEIS: Beim ersten Aufruf der Adresse werden Special-Wörterbücher für RE-Morse und T9 generiert, -was ca. 15-30s in Anspruch nimmt und dadurch das Laden der Seite einmalig verzögert.__ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f2a518b..ca16185 100644 Binary files a/requirements.txt and b/requirements.txt differ