diff --git a/GameManager1_1.png b/GameManager1_1.png new file mode 100644 index 0000000..412b7a1 Binary files /dev/null and b/GameManager1_1.png differ diff --git a/README.md b/README.md index 5b12bec..2fb92a9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ No more confusion about whether a key is redeemed, gifted, or still unused – n You can even gift your keys via a unique 24-hour website link – just mark a game as "Gifted" and copy the link from your overview. (HTTPS recommended) -![Screenshot](GameManager.png) +![Screenshot](GameManager1_1.png) --- diff --git a/setup.sh b/setup.sh index 086c419..8725f51 100644 --- a/setup.sh +++ b/setup.sh @@ -144,18 +144,18 @@ DEFAULT_LANGUAGE="en" SUPPORTED_LANGUAGES="de,en" # Timezone -TZ=Europe/Berlin +TZ="Europe/Berlin" # Security -FORCE_HTTPS=False -SESSION_COOKIE_SECURE=auto +FORCE_HTTPS="False" +SESSION_COOKIE_SECURE="auto" CSRF_ENABLED="True" # Account registration REGISTRATION_ENABLED="True" # checking interval if keys have to be redeemed before a specific date -CHECK_EXPIRING_KEYS_INTERVAL_HOURS=6 +CHECK_EXPIRING_KEYS_INTERVAL_HOURS="6" # Want to check prices? Here you are! ITAD_API_KEY="your-secret-key-here" @@ -170,7 +170,7 @@ APPRISE_URLS="" #matrixs://TOKEN@matrix.org/!ROOM_ID" # Redis URL -REDIS_URL=redis://redis:6379/0 +REDIS_URL="redis://redis:6379/0" # Enable Debug (e.g. for VS Code) FLASK_DEBUG=1 @@ -253,6 +253,7 @@ from sqlalchemy.engine import Engine from sqlalchemy.exc import IntegrityError, LegacyAPIWarning from sqlalchemy.orm import joinedload from werkzeug.security import check_password_hash, generate_password_hash +from werkzeug.middleware.proxy_fix import ProxyFix from wtforms import SelectField, StringField, TextAreaField, validators # Config @@ -281,6 +282,11 @@ app.jinja_env.globals['getattr'] = getattr def not_found_error(error): return render_template('404.html'), 404 +app.wsgi_app = ProxyFix( + app.wsgi_app, + x_proto=1, # Trust X-Forwarded-Proto Header + x_host=1 # Trust X-Forwarded-Host Header +) # UNIX-Systems (Linux, Docker) try: @@ -1162,7 +1168,7 @@ def generate_redeem(game_id): ) db.session.add(new_token) db.session.commit() - redeem_url = url_for('redeem', token=token, _external=True) + redeem_url = url_for('redeem', token=token, _external=True, _scheme='https') message = translate( 'Redeem link generated: {url}', url=redeem_url @@ -1186,22 +1192,25 @@ def redeem_page(token): redeem_token.used = True db.session.commit() - # which Plattform - if game.platform == "steam" or game.steam_appid: + if game.platform == 'steam': platform_link = 'https://store.steampowered.com/account/registerkey?key=' - platform_label = "Steam" - elif game.platform == "gog": + platform_name = 'Steam' + elif game.platform == 'gog': platform_link = 'https://www.gog.com/redeem/' - platform_label = "GOG" - elif game.platform == "xbox": + platform_name = 'GOG' + elif game.platform == 'xbox': platform_link = 'https://redeem.microsoft.com/' - platform_label = "XBOX" - elif game.platform == "playstation": - platform_link = 'https://store.playstation.com/redeem' - platform_label = "PlayStation" + platform_name = 'Xbox' + elif game.platform == 'playstation': + platform_link = 'https://redeem.playstation.com/' + platform_name = 'PlayStation' + elif game.platform == 'switch': + platform_link = 'https://ec.nintendo.com/redeem/' + platform_name = 'Nintendo Switch' else: - platform_link = '#' - platform_label = game.platform.capitalize() if game.platform else "Unknown" + # Fallback für benutzerdefinierte Keys + platform_link = '' + platform_name = 'Key' return render_template( 'redeem.html', @@ -1209,9 +1218,10 @@ def redeem_page(token): redeem_token=redeem_token, expires_timestamp=int(expires_utc.timestamp() * 1000), platform_link=platform_link, - platform_label=platform_label + platform_name=platform_name ) + @app.route('/admin/users') @login_required @admin_required @@ -1524,6 +1534,7 @@ cat < templates/base.html {{ _('Game Key Manager') }} + @@ -1565,26 +1576,34 @@ cat < templates/base.html - + + {% if current_user.is_authenticated %} + + {% if current_user.is_admin %} + ⚙️ {{ _('Admin') }} + 📜 {{ _('Audit Logs') }} + {% endif %} + 🔒 {{ _('Password') }} + 🚪 {{ _('Logout') }} + {% endif %} +
@@ -1652,16 +1671,7 @@ HTML_END cat <<'HTML_END' > templates/index.html {% extends "base.html" %} {% block content %} - - + {% if games %}
@@ -1669,7 +1679,7 @@ cat <<'HTML_END' > templates/index.html - + @@ -1685,15 +1695,15 @@ cat <<'HTML_END' > templates/index.html {% if game.steam_appid %} Steam Header {% elif game.url and 'gog.com' in game.url %} GOG Logo templates/index.html - +
{{ _('Cover') }} {{ _('Name') }}{{ _('Key') }}{{ _('Key') }} {{ _('Status') }} {{ _('Created') }} {{ _('Redeem by') }} {{ game.name }}{{ game.steam_key }}{{ game.steam_key }} {% if game.status == 'nicht eingelöst' %} {{ _('Not redeemed') }} @@ -1752,8 +1762,8 @@ cat <<'HTML_END' > templates/index.html {% if game.status == 'geschenkt' %} - - - `; + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(data.url) + .then(() => { + // Succcess ?? maybe + flashContainer.innerHTML = ` + + `; + }) + .catch(err => { + flashContainer.innerHTML = ` + + `; + }); + } else { + alert("Clipboard API is not supported in your browser or context."); + } } + } catch (error) { // Fehlermeldung mit übersetztem Text flashContainer.innerHTML = ` @@ -1814,10 +1838,29 @@ document.querySelectorAll('.generate-redeem').forEach(btn => { }); }); + {% else %}
{{ _('No games yet') }}
{% endif %} {% endblock %} + HTML_END # Login Template @@ -2287,11 +2330,17 @@ cat <<'HTML_END' > templates/redeem.html

{{ _('Your Key:') }}

{{ game.steam_key }} + {% if platform_link %} - {{ _('Redeem now on') }} {{ platform_label }} + class="btn btn-primary btn-lg mb-3" + target="_blank"> + {{ _('Redeem now on') }} {{ platform_name }} + {% else %} +
+ {{ _('Your key:') }} {{ game.steam_key }} +
+ {% endif %}
{{ _('This page will expire in') }} @@ -2299,9 +2348,9 @@ cat <<'HTML_END' > templates/redeem.html
+ class="progress-bar bg-danger" + role="progressbar" + style="width: 100%">
@@ -2554,6 +2603,7 @@ cat < templates/admin_audit_logs.html HTML_END # Error Sites +# 403 Tempate cat < templates/403.html {% extends "base.html" %} {% block content %} @@ -2582,6 +2632,7 @@ cat < templates/403.html {% endblock %} HTML_END +# 404 Template cat < templates/404.html {% extends "base.html" %} {% block content %} @@ -2778,6 +2829,28 @@ td.font-monospace { overflow-wrap: break-word; } +.key-col.hidden { + display: none !important; +} + +@media (max-width: 768px) { + .key-col { + display: none; + } +} + +.navbar .btn, +.navbar .dropdown-toggle, +.navbar .nav-link { + min-height: 40px; + line-height: 1.5 !important; + padding-top: 6px; + padding-bottom: 6px; + display: flex; + align-items: center; + font-size: 0.95em; +} + .alert-error { background-color: #f8d7da; border-color: #f5c6cb; color: #721c24; } .alert-success { background-color: #d4edda; border-color: #c3e6cb; color: #155724; } .alert-info { background: #d9edf7; color: #31708f; } diff --git a/steam-gift-manager/.env b/steam-gift-manager/.env index f3e5d25..d1955a1 100644 --- a/steam-gift-manager/.env +++ b/steam-gift-manager/.env @@ -1,25 +1,25 @@ # Flask-Configuration -SECRET_KEY="" -REDEEM_SECRET="" -WTF_CSRF_SECRET_KEY="" +SECRET_KEY="1dc3d95006f7466670ac2d705ce43dc4a5ad8e2189dbe539" +REDEEM_SECRET="a50a961667ded234b1e59532ab7e27e1" +WTF_CSRF_SECRET_KEY="845ae46bd1bea30311e98df232d78b4e" # Language Settings DEFAULT_LANGUAGE="en" SUPPORTED_LANGUAGES="de,en" # Timezone -TZ=Europe/Berlin +TZ="Europe/Berlin" # Security -FORCE_HTTPS=False -SESSION_COOKIE_SECURE=auto +FORCE_HTTPS="False" +SESSION_COOKIE_SECURE="auto" CSRF_ENABLED="True" # Account registration REGISTRATION_ENABLED="True" # checking interval if keys have to be redeemed before a specific date -CHECK_EXPIRING_KEYS_INTERVAL_HOURS=6 +CHECK_EXPIRING_KEYS_INTERVAL_HOURS="6" # Want to check prices? Here you are! ITAD_API_KEY="your-secret-key-here" @@ -34,7 +34,7 @@ APPRISE_URLS="" #matrixs://TOKEN@matrix.org/!ROOM_ID" # Redis URL -REDIS_URL=redis://redis:6379/0 +REDIS_URL="redis://redis:6379/0" # Enable Debug (e.g. for VS Code) FLASK_DEBUG=1 diff --git a/steam-gift-manager/app.py b/steam-gift-manager/app.py index 3dbe756..a866da5 100644 --- a/steam-gift-manager/app.py +++ b/steam-gift-manager/app.py @@ -72,6 +72,7 @@ from sqlalchemy.engine import Engine from sqlalchemy.exc import IntegrityError, LegacyAPIWarning from sqlalchemy.orm import joinedload from werkzeug.security import check_password_hash, generate_password_hash +from werkzeug.middleware.proxy_fix import ProxyFix from wtforms import SelectField, StringField, TextAreaField, validators # Config @@ -100,6 +101,11 @@ app.jinja_env.globals['getattr'] = getattr def not_found_error(error): return render_template('404.html'), 404 +app.wsgi_app = ProxyFix( + app.wsgi_app, + x_proto=1, # Trust X-Forwarded-Proto Header + x_host=1 # Trust X-Forwarded-Host Header +) # UNIX-Systems (Linux, Docker) try: @@ -981,7 +987,7 @@ def generate_redeem(game_id): ) db.session.add(new_token) db.session.commit() - redeem_url = url_for('redeem', token=token, _external=True) + redeem_url = url_for('redeem', token=token, _external=True, _scheme='https') message = translate( 'Redeem link generated: {url}', url=redeem_url @@ -1005,22 +1011,25 @@ def redeem_page(token): redeem_token.used = True db.session.commit() - # which Plattform - if game.platform == "steam" or game.steam_appid: + if game.platform == 'steam': platform_link = 'https://store.steampowered.com/account/registerkey?key=' - platform_label = "Steam" - elif game.platform == "gog": + platform_name = 'Steam' + elif game.platform == 'gog': platform_link = 'https://www.gog.com/redeem/' - platform_label = "GOG" - elif game.platform == "xbox": + platform_name = 'GOG' + elif game.platform == 'xbox': platform_link = 'https://redeem.microsoft.com/' - platform_label = "XBOX" - elif game.platform == "playstation": - platform_link = 'https://store.playstation.com/redeem' - platform_label = "PlayStation" + platform_name = 'Xbox' + elif game.platform == 'playstation': + platform_link = 'https://redeem.playstation.com/' + platform_name = 'PlayStation' + elif game.platform == 'switch': + platform_link = 'https://ec.nintendo.com/redeem/' + platform_name = 'Nintendo Switch' else: - platform_link = '#' - platform_label = game.platform.capitalize() if game.platform else "Unknown" + # Fallback für benutzerdefinierte Keys + platform_link = '' + platform_name = 'Key' return render_template( 'redeem.html', @@ -1028,9 +1037,10 @@ def redeem_page(token): redeem_token=redeem_token, expires_timestamp=int(expires_utc.timestamp() * 1000), platform_link=platform_link, - platform_label=platform_label + platform_name=platform_name ) + @app.route('/admin/users') @login_required @admin_required diff --git a/steam-gift-manager/static/style.css b/steam-gift-manager/static/style.css index f5088d8..6511a14 100644 --- a/steam-gift-manager/static/style.css +++ b/steam-gift-manager/static/style.css @@ -164,6 +164,28 @@ td.font-monospace { overflow-wrap: break-word; } +.key-col.hidden { + display: none !important; +} + +@media (max-width: 768px) { + .key-col { + display: none; + } +} + +.navbar .btn, +.navbar .dropdown-toggle, +.navbar .nav-link { + min-height: 40px; + line-height: 1.5 !important; + padding-top: 6px; + padding-bottom: 6px; + display: flex; + align-items: center; + font-size: 0.95em; +} + .alert-error { background-color: #f8d7da; border-color: #f5c6cb; color: #721c24; } .alert-success { background-color: #d4edda; border-color: #c3e6cb; color: #155724; } .alert-info { background: #d9edf7; color: #31708f; } diff --git a/steam-gift-manager/templates/base.html b/steam-gift-manager/templates/base.html index 9781a06..cc78a87 100644 --- a/steam-gift-manager/templates/base.html +++ b/steam-gift-manager/templates/base.html @@ -8,6 +8,7 @@ {{ _('Game Key Manager') }} + @@ -49,26 +50,34 @@ - + + {% if current_user.is_authenticated %} + + {% if current_user.is_admin %} + ⚙️ {{ _('Admin') }} + 📜 {{ _('Audit Logs') }} + {% endif %} + 🔒 {{ _('Password') }} + 🚪 {{ _('Logout') }} + {% endif %} +
diff --git a/steam-gift-manager/templates/index.html b/steam-gift-manager/templates/index.html index ea383e8..faa450e 100644 --- a/steam-gift-manager/templates/index.html +++ b/steam-gift-manager/templates/index.html @@ -1,15 +1,6 @@ {% extends "base.html" %} {% block content %} - - + {% if games %}
@@ -17,7 +8,7 @@ - + @@ -33,15 +24,15 @@ {% if game.steam_appid %} Steam Header {% elif game.url and 'gog.com' in game.url %} GOG Logo - +
{{ _('Cover') }} {{ _('Name') }}{{ _('Key') }}{{ _('Key') }} {{ _('Status') }} {{ _('Created') }} {{ _('Redeem by') }}{{ game.name }}{{ game.steam_key }}{{ game.steam_key }} {% if game.status == 'nicht eingelöst' %} {{ _('Not redeemed') }} @@ -100,8 +91,8 @@ {% if game.status == 'geschenkt' %} - - - `; + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(data.url) + .then(() => { + // Succcess ?? maybe + flashContainer.innerHTML = ` + + `; + }) + .catch(err => { + flashContainer.innerHTML = ` + + `; + }); + } else { + alert("Clipboard API is not supported in your browser or context."); + } } + } catch (error) { // Fehlermeldung mit übersetztem Text flashContainer.innerHTML = ` @@ -162,7 +167,26 @@ document.querySelectorAll('.generate-redeem').forEach(btn => { }); }); + {% else %}
{{ _('No games yet') }}
{% endif %} {% endblock %} + diff --git a/steam-gift-manager/templates/redeem.html b/steam-gift-manager/templates/redeem.html index 2892a4c..c169167 100644 --- a/steam-gift-manager/templates/redeem.html +++ b/steam-gift-manager/templates/redeem.html @@ -16,11 +16,17 @@

{{ _('Your Key:') }}

{{ game.steam_key }} + {% if platform_link %} - {{ _('Redeem now on') }} {{ platform_label }} + class="btn btn-primary btn-lg mb-3" + target="_blank"> + {{ _('Redeem now on') }} {{ platform_name }} + {% else %} +
+ {{ _('Your key:') }} {{ game.steam_key }} +
+ {% endif %}
{{ _('This page will expire in') }} @@ -28,9 +34,9 @@
+ class="progress-bar bg-danger" + role="progressbar" + style="width: 100%">
diff --git a/steam-gift-manager/translations/de.json b/steam-gift-manager/translations/de.json index 171d981..752d7e3 100644 --- a/steam-gift-manager/translations/de.json +++ b/steam-gift-manager/translations/de.json @@ -11,6 +11,7 @@ "Cancel": "Abbrechen", "Change Password": "Passwort ändern", "Change password form": "Passwort ändern Formular", + "Clipboard error": "Ablagefehler", "Confirm New Password": "Neues Passwort bestätigen", "Confirm Password": "Passwort bestätigen", "Copied!": "Kopiert!", @@ -28,6 +29,7 @@ "Expires at": "Läuft ab am", "Export CSV": "CSV exportieren", "Externe Daten": "Externe Daten", + "External Data": "Externe Daten", "For GOG games: Enter the Steam AppID here to enable price tracking.": "Für GOG-Spiele: Gib hier die Steam AppID ein, um die Preisüberwachung zu aktivieren.", "Game Description": "Spielbeschreibung", "Game Key": "Spielschlüssel", @@ -36,6 +38,7 @@ "Gifted": "Verschenkt", "Hist. Low": "Historischer Tiefstpreis", "Import": "Importieren", + "Import/Export": "Import/Export", "Import CSV": "CSV importieren", "Import Games": "Spiele importieren", "Key": "Schlüssel", @@ -74,6 +77,7 @@ "Select CSV file": "CSV-Datei auswählen", "Shop": "Shop", "Shop URL": "Shop-URL", + "Show/Hide Keys": "Zeige/Verstecke Keys", "Sorry, you are not allowed to access this page.": "Du bist nicht berechtigt, diese Seite zu betreten.", "Spiel bearbeiten": "Spiel bearbeiten", "Status": "Status", @@ -90,5 +94,6 @@ "Release Date:": "Veröffentlichung:", "View Details": "Details anzeigen", "View on IsThereAnyDeal": "Auf IsThereAnyDeal ansehen", - "Your Key:": "Dein Schlüssel:" -} + "Your Key:": "Dein Key:", + "Your key:": "Dein Key" +} \ No newline at end of file diff --git a/steam-gift-manager/translations/en.json b/steam-gift-manager/translations/en.json index a0ae9a5..6facd36 100644 --- a/steam-gift-manager/translations/en.json +++ b/steam-gift-manager/translations/en.json @@ -3,7 +3,6 @@ "Action": "", "Actions": "", "Add Game": "", - "Add New Game": "", "Admin": "", "Already have an account? Login!": "", "Audit Logs": "", @@ -11,6 +10,7 @@ "Cancel": "", "Change Password": "", "Change password form": "", + "Clipboard error": "", "Confirm New Password": "", "Confirm Password": "", "Copied!": "", @@ -36,13 +36,13 @@ "Hist. Low": "", "Import": "", "Import CSV": "", + "Import/Export": "", "Import Games": "", "Key": "", "Link copied": "", "Login": "", "Login form": "", "Logout": "", - "My Games": "", "Name": "", "New Password": "", "Next": "", @@ -75,6 +75,7 @@ "Select CSV file": "", "Shop": "", "Shop URL": "", + "Show/Hide Keys": "", "Sorry, you are not allowed to access this page.": "", "Spiel bearbeiten": "", "Status": "", @@ -90,5 +91,6 @@ "Username": "", "View Details": "", "View on IsThereAnyDeal": "", + "Your key:": "", "Your Key:": "" }