from channels.generic.websocket import AsyncWebsocketConsumer import json import asyncio from ..models import QuizGame, QuizGameParticipant from channels.db import database_sync_to_async import redis # Dictionary to track inactive check tasks and active connections per game game_check_tasks = {} game_connections = {} class LobbyConsumer(AsyncWebsocketConsumer): r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) async def connect(self): self.join_code = self.scope['url_route']['kwargs']['join_code'] self.room_group_name = f'lobby_{self.join_code}' # Get participant ID from session self.participant_id = self.scope['session'].get('participant_id') # Track connection if self.join_code not in game_connections: game_connections[self.join_code] = set() game_connections[self.join_code].add(self.channel_name) # Join room group await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() # Send initial participants list await self.update_participants() async def disconnect(self, close_code): # Leave room group await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) # Remove from connection tracking if self.join_code in game_connections: game_connections[self.join_code].discard(self.channel_name) if not game_connections[self.join_code]: # Last connection closed del game_connections[self.join_code] if self.join_code in game_check_tasks: # Cancel the inactive check task game_check_tasks[self.join_code].cancel() del game_check_tasks[self.join_code] async def receive(self, text_data): data = json.loads(text_data) message_type = data.get('type') print(f'Received message type: {message_type}') if message_type == 'ping': # Respond with pong to keep connection alive await self.send(text_data=json.dumps({ 'type': 'pong' })) return elif message_type in ['leave_game', 'kick_player']: participant_id = data.get('participant_id') if participant_id: try: # Get participant info before deletion participant = await database_sync_to_async(QuizGameParticipant.objects.get)(participant_id=participant_id) display_name = participant.display_name # For kick_player, verify that the request comes from the host if message_type == 'kick_player': game = await database_sync_to_async(lambda: participant.quiz_game)() # Get host_id from message host_id = data.get('host_id') print(f'Kick attempt - Game host_id: {game.host_id}, Message host_id: {host_id}') if not host_id: print('No host_id in message') return if str(game.host_id) != str(host_id): print(f'Host ID mismatch: {game.host_id} != {host_id}') return # Also verify against cookie as backup cookie_host_id = self.scope.get('cookies', {}).get('host_id') if not cookie_host_id or str(cookie_host_id) != str(host_id): print(f'Cookie host_id mismatch: {cookie_host_id} != {host_id}') return # Delete the participant await database_sync_to_async(participant.delete)() # Notify other players await self.channel_layer.group_send( self.room_group_name, { 'type': 'player_left', 'player_name': display_name, 'was_kicked': message_type == 'kick_player', 'participant_id': participant_id } ) # Update participants list await self.update_participants() # If it's the kicked player's connection, send them home if participant_id == self.participant_id: await self.send(text_data=json.dumps({ 'type': 'redirect', 'url': '/' })) except QuizGameParticipant.DoesNotExist: pass elif message_type == 'update_participants': await self.update_participants() elif message_type == 'start_game': host_id = data.get('host_id') if host_id: success = await self.start_game(host_id) if success: await self.channel_layer.group_send( self.room_group_name, { 'type': 'game_state_update', 'action': 'start_game', 'redirect_url': f'/play/game/{self.join_code}/question' } ) async def get_participants_list(self): try: from django.utils import timezone from datetime import timedelta game = await database_sync_to_async(QuizGame.objects.get)(join_code=self.join_code) participants = await database_sync_to_async(lambda: list( game.participants .values('participant_id', 'display_name') ))() # Zustand laden try: r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) # Alte Teilnehmer aus Redis holen data = r.get(f"last_participants_{self.room_group_name}") prev_names = set(json.loads(data)) if data else set() # Aktuelle Teilnehmer aus dem Argument extrahieren current_names = set(p['display_name'] for p in participants) # Neue Teilnehmer bestimmen new_participants = current_names - prev_names # Benachrichtigung für neue Teilnehmer versenden for name in new_participants: await self.channel_layer.group_send( self.room_group_name, { 'type': 'player_joined', 'player_name': name } ) # Teilnehmerliste in Redis aktualisieren r.set(f"last_participants_{self.room_group_name}", json.dumps(list(current_names))) except Exception as e: print(f"Error in send_join_notifications: {e}") self.last_participants = participants return participants except QuizGame.DoesNotExist: return [] async def update_participants(self): participants = await self.get_participants_list() await self.channel_layer.group_send( self.room_group_name, { 'type': 'participants_list_update', 'participants': participants } ) async def participants_list_update(self, event): await self.send(text_data=json.dumps({ 'type': 'participants_list_update', 'participants': event['participants'] })) @database_sync_to_async def start_game(self, host_id): try: game = QuizGame.objects.get(join_code=self.join_code) if str(game.host_id) == str(host_id): from django.utils import timezone game.current_state = 'question' game.question_start_time = timezone.now() game.save() return True except QuizGame.DoesNotExist: pass return False async def game_state_update(self, event): await self.send(text_data=json.dumps({ 'type': 'game_state_update', 'action': event['action'], 'redirect_url': event.get('redirect_url') })) async def player_joined(self, event): await self.send(text_data=json.dumps({ 'type': 'player_joined', 'player_name': event['player_name'] })) async def player_left(self, event): message = { 'type': 'player_left', 'player_name': event['player_name'], 'was_kicked': event.get('was_kicked', False) } if event.get('was_kicked') and event.get('participant_id'): message['participant_id'] = event['participant_id'] await self.send(text_data=json.dumps(message))