transient.chat/blueprints/rooms.py

136 lines
4.4 KiB
Python

from flask import Blueprint, request, jsonify, Response
from datetime import datetime, timezone
from typing import Dict, Any, Tuple
import time
from blueprints.auth import verify_user_key
from models.state import (
room_users,
room_passwords,
authorized_room_users,
room_owners,
kicked_users,
nuked_rooms,
banned_ips,
banned_usernames,
user_ips,
video_state,
hash_room_password,
verify_room_password,
)
rooms_bp = Blueprint("rooms", __name__)
@rooms_bp.route("/rooms", methods=["GET", "POST"])
def get_or_create_rooms() -> Tuple[Response, int] | Response:
if request.method == "POST":
data: Dict[str, Any] = request.get_json() or {}
room_name: str = (data.get("name") or data.get("room", "")).strip()
password: str = str(data.get("password", "")).strip()
username: str = str(data.get("username", "")).strip()
key: str = str(data.get("key", "")).strip()
return handle_room_join_or_create(room_name, password, username, key)
# GET request: Return ONLY active rooms
active_rooms = []
all_users = set()
for room, users in list(room_users.items()):
user_count = len(users)
if user_count > 0:
all_users.update(users.keys())
has_wp = bool(
room in video_state
and (video_state[room].get("is_active") or video_state[room].get("url"))
)
active_rooms.append(
{
"name": room,
"users": user_count,
"has_watchparty": has_wp,
"type": "watchparty" if has_wp else "chat",
}
)
return jsonify(
{
"active_rooms": active_rooms,
"unique_users": len(all_users),
# Maintain backward compatibility aliases
"public": active_rooms,
"regional": [],
"topical": [],
}
)
@rooms_bp.route("/rooms/join", methods=["POST"])
def join_room() -> Tuple[Response, int] | Response:
data: Dict[str, Any] = request.get_json() or {}
room_name: str = (data.get("room") or data.get("name", "")).strip()
password: str = str(data.get("password", "")).strip()
username: str = str(data.get("username", "")).strip()
key: str = str(data.get("key", "")).strip()
return handle_room_join_or_create(room_name, password, username, key)
def handle_room_join_or_create(
room_name: str, password: str, username: str, key: str
) -> Tuple[Response, int] | Response:
if not room_name:
return jsonify({"error": "Room name is required"}), 400
if not password:
return jsonify({"error": "Room password is required for private encryption"}), 400
if not username or not key:
return jsonify({"error": "User credentials are required"}), 401
if not verify_user_key(username, key):
return jsonify({"error": "Invalid user credentials"}), 403
user_ip = request.remote_addr
if (
user_ip in banned_ips
or username in banned_usernames
or (username in user_ips and user_ips[username] in banned_ips)
):
return jsonify({"error": "User is banned"}), 403
if room_name in nuked_rooms:
if time.time() < nuked_rooms[room_name]:
return jsonify({"error": "Room is currently unavailable"}), 403
else:
nuked_rooms.pop(room_name, None)
if username in kicked_users.get(room_name, set()):
return jsonify({"error": "You were kicked from this room"}), 403
# On-demand room creation vs existing room verification
if room_name not in room_passwords:
# Create new on-demand encrypted room
room_passwords[room_name] = hash_room_password(password)
room_owners[room_name] = username
else:
# Verify password for existing room
if not verify_room_password(password, room_passwords[room_name]):
return jsonify({"error": "Incorrect room password"}), 401
# Authorize user session for this room
authorized_room_users.setdefault(room_name, set()).add(username)
# Register user presence
room_users.setdefault(room_name, {})[username] = datetime.now(timezone.utc).isoformat()
return jsonify(
{
"status": "ok",
"name": room_name,
"room": room_name,
"owner": room_owners.get(room_name),
"is_owner": room_owners.get(room_name) == username,
}
)