diff --git a/app.py b/app.py index 637c47f..f9450db 100644 --- a/app.py +++ b/app.py @@ -1120,6 +1120,61 @@ def whatsapp_webhook(): pass continue + # ── Nutzer-Registrierung per WhatsApp ──────────────── + pending_codes = getattr(app, '_pending_codes', {}) + if msg_type == "text": + clean = content.strip().lower() + # "registrieren" → Code senden + if clean == "registrieren": + import random + code = str(random.randint(100000, 999999)) + pending_codes[sender_id] = code + app._pending_codes = pending_codes + if META_TOKEN: + try: + send_whatsapp_message(sender_id, + f"🔐 Dein Verifikationscode: *{code}*\n\n" + f"Antworte einfach mit: code {code}") + except Exception: + pass + continue + # "code 123456" → verifizieren + User anlegen + if clean.startswith("code ") and len(clean) >= 10: + entered = clean[5:].strip() + if sender_id in pending_codes and pending_codes[sender_id] == entered: + del pending_codes[sender_id] + app._pending_codes = pending_codes + # User anlegen + existing = db.execute( + "SELECT id FROM users WHERE phone=?", (sender_id,) + ).fetchone() + if not existing: + db.execute( + "INSERT INTO users (phone, name, verified) VALUES (?,?,1)", + (sender_id, sender_name), + ) + db.commit() + if META_TOKEN: + try: + send_whatsapp_message(sender_id, + "✅ Du bist jetzt registriert! Dein Profil: " + "https://chat.datenhimmel.work/profile") + except Exception: + pass + else: + if META_TOKEN: + try: + send_whatsapp_message(sender_id, + "❌ Falscher Code. Schick \"registrieren\" für einen neuen.") + except Exception: + pass + continue + + # ── Cross-Channel: User-Profil zuordnen ────────────── + user_row = db.execute( + "SELECT id, name FROM users WHERE phone=?", (sender_id,) + ).fetchone() + # Check for existing thread from this sender (5-min window) recent = db.execute( """SELECT id, content, media_path, halle FROM submissions @@ -1186,9 +1241,10 @@ def whatsapp_webhook(): cat = category_override or None db.execute( """INSERT INTO submissions - (channel, sender_id, sender_name, type, content, media_path, status, halle, category, spiel_titel) - VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?)""", - ("whatsapp", sender_id, sender_name, msg_type, content, media_path, halle, cat, spiel_titel), + (channel, sender_id, sender_name, type, content, media_path, status, halle, category, spiel_titel, user_id) + VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?, ?)""", + ("whatsapp", sender_id, sender_name, msg_type, content, media_path, halle, cat, spiel_titel, + user_row["id"] if user_row else None), ) # Auto-mark matching neuheiten as sold out if cat == "ausverkauft" and spiel_titel: @@ -2296,6 +2352,194 @@ def submission_delete(sub_id: int): """ +# ═══════════════════════════════════════════════════════════════════ +# Nutzerprofil-System (Cross-Channel Identity) +# ═══════════════════════════════════════════════════════════════════ + +import hashlib, secrets + +def _hash_password(password: str) -> str: + """SHA-256 mit Salt.""" + salt = secrets.token_hex(16) + h = hashlib.sha256((salt + password).encode()).hexdigest() + return f"{salt}${h}" + +def _check_password(password: str, stored: str) -> bool: + """Vergleicht Passwort mit gespeichertem Hash.""" + try: + salt, h = stored.split("$", 1) + return hashlib.sha256((salt + password).encode()).hexdigest() == h + except Exception: + return False + +def _create_session(db, user_id: int, channel: str = "web") -> str: + """Erstellt Session-Token (gültig 30 Tage).""" + token = secrets.token_urlsafe(32) + db.execute( + "INSERT INTO user_sessions (user_id, token, channel, expires_at) VALUES (?,?,?,datetime('now','+30 days'))", + (user_id, token, channel), + ) + db.commit() + return token + +def _get_user_from_session(db) -> dict | None: + """Liest User aus Session-Cookie.""" + token = request.cookies.get("bsn_session", "") + if not token: + return None + row = db.execute( + "SELECT u.* FROM users u JOIN user_sessions s ON u.id=s.user_id " + "WHERE s.token=? AND s.expires_at > datetime('now')", + (token,), + ).fetchone() + return dict(row) if row else None + +@app.route("/register", methods=["GET", "POST"]) +def user_register(): + db = get_db() + error = "" + if request.method == "POST": + phone = request.form.get("phone", "").strip() + name = request.form.get("name", "").strip() + password = request.form.get("password", "").strip() + if not phone or not name or len(password) < 4: + error = "Bitte Telefon, Name und Passwort (min. 4 Zeichen) angeben." + elif db.execute("SELECT id FROM users WHERE phone=?", (phone,)).fetchone(): + error = "Diese Telefonnummer ist bereits registriert." + else: + pw_hash = _hash_password(password) + db.execute( + "INSERT INTO users (phone, name, password_hash, verified) VALUES (?,?,?,1)", + (phone, name, pw_hash), + ) + db.commit() + user_id = db.execute("SELECT last_insert_rowid()").fetchone()[0] + token = _create_session(db, user_id) + resp = make_response(redirect("/profile")) + resp.set_cookie("bsn_session", token, max_age=30*86400, httponly=True) + return resp + return f""" + +Registrieren — BSN Community + +
+

📋 Registrieren

+{"
{}
".format(error) if error else ""} +
+ + + + +
+ +
""".format(request.form.get("phone","")) + +@app.route("/login", methods=["GET", "POST"]) +def user_login(): + db = get_db() + error = "" + if request.method == "POST": + phone = request.form.get("phone", "").strip() + password = request.form.get("password", "").strip() + user = db.execute("SELECT * FROM users WHERE phone=?", (phone,)).fetchone() + if not user or not _check_password(password, user["password_hash"] or ""): + error = "Telefon oder Passwort falsch." + else: + token = _create_session(db, user["id"]) + resp = make_response(redirect("/profile")) + resp.set_cookie("bsn_session", token, max_age=30*86400, httponly=True) + return resp + return f""" + +Login — BSN Community + +
+

🔐 Login

+{"
{}
".format(error) if error else ""} +
+ + + +
+ +
""".format(request.form.get("phone","")) + +@app.route("/logout") +def user_logout(): + token = request.cookies.get("bsn_session", "") + if token: + db = get_db() + db.execute("DELETE FROM user_sessions WHERE token=?", (token,)) + db.commit() + resp = make_response(redirect("/")) + resp.set_cookie("bsn_session", "", max_age=0) + return resp + +@app.route("/profile", methods=["GET", "POST"]) +def user_profile(): + db = get_db() + user = _get_user_from_session(db) + if not user: + return redirect("/login") + msg = "" + if request.method == "POST": + name = request.form.get("name", "").strip() + if name: + db.execute("UPDATE users SET name=? WHERE id=?", (name, user["id"])) + db.commit() + msg = "✅ Profil aktualisiert." + return f""" + +Profil — BSN Community + +
+

👤 Mein Profil

+{"
{}
".format(msg) if msg else ""} +
Telefon
{user['phone']}
+
+
Name
+
+ +
+ +
""" + + @app.route("/") def public_frontend(): """Public frontend showing published submissions — SPIEL Essen inspired.""" @@ -3268,6 +3512,9 @@ def public_frontend():