2026-10-06 02:36:54 +00:00
|
|
|
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
|
2026-05-12 23:50:49 +00:00
|
|
|
from models.state import (
|
|
|
|
|
room_users,
|
2026-10-06 02:36:54 +00:00
|
|
|
room_passwords,
|
|
|
|
|
authorized_room_users,
|
|
|
|
|
room_owners,
|
|
|
|
|
kicked_users,
|
|
|
|
|
nuked_rooms,
|
|
|
|
|
banned_ips,
|
|
|
|
|
banned_usernames,
|
|
|
|
|
user_ips,
|
2026-05-12 23:50:49 +00:00
|
|
|
video_state,
|
2026-10-06 02:36:54 +00:00
|
|
|
hash_room_password,
|
|
|
|
|
verify_room_password,
|
2026-05-12 23:50:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
rooms_bp = Blueprint("rooms", __name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@rooms_bp.route("/rooms", methods=["GET", "POST"])
|
2026-10-06 02:36:54 +00:00
|
|
|
def get_or_create_rooms() -> Tuple[Response, int] | Response:
|
2026-05-12 23:50:49 +00:00
|
|
|
if request.method == "POST":
|
2026-10-06 02:36:54 +00:00
|
|
|
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()
|
2026-05-12 23:50:49 +00:00
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
return handle_room_join_or_create(room_name, password, username, key)
|
2026-05-12 23:50:49 +00:00
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
# GET request: Return ONLY active rooms
|
|
|
|
|
active_rooms = []
|
|
|
|
|
all_users = set()
|
2026-05-12 23:50:49 +00:00
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
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(
|
2026-05-12 23:50:49 +00:00
|
|
|
{
|
|
|
|
|
"name": room,
|
2026-10-06 02:36:54 +00:00
|
|
|
"users": user_count,
|
|
|
|
|
"has_watchparty": has_wp,
|
|
|
|
|
"type": "watchparty" if has_wp else "chat",
|
2026-05-12 23:50:49 +00:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return jsonify(
|
|
|
|
|
{
|
2026-10-06 02:36:54 +00:00
|
|
|
"active_rooms": active_rooms,
|
2026-05-12 23:50:49 +00:00
|
|
|
"unique_users": len(all_users),
|
2026-10-06 02:36:54 +00:00
|
|
|
# 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,
|
2026-05-12 23:50:49 +00:00
|
|
|
}
|
|
|
|
|
)
|