import json from channels.generic.websocket import AsyncWebsocketConsumer from channels.db import database_sync_to_async from django.utils import timezone class ChannelConsumer(AsyncWebsocketConsumer): async def connect(self): self.channel_id = self.scope['url_route']['kwargs']['channel_id'] self.group_name = f'channel_{self.channel_id}' self.user = self.scope['user'] if not self.user.is_authenticated: await self.close() return await self.channel_layer.group_add(self.group_name, self.channel_name) await self.set_online(True) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard(self.group_name, self.channel_name) await self.set_online(False) async def receive(self, text_data): data = json.loads(text_data) content = data.get('message', '').strip() if not content: return msg = await self.save_message(content) await self.channel_layer.group_send( self.group_name, { 'type': 'chat_message', 'message': content, 'sender': self.user.username, 'sender_id': self.user.id, 'message_id': msg.id, 'timestamp': msg.timestamp.strftime('%H:%M'), 'avatar': await self.get_avatar(), 'role': await self.get_role(), } ) async def chat_message(self, event): await self.send(text_data=json.dumps(event)) @database_sync_to_async def save_message(self, content): from chat.models import Message, Channel channel = Channel.objects.get(id=self.channel_id) return Message.objects.create(channel=channel, sender=self.user, content=content) @database_sync_to_async def set_online(self, status): try: self.user.profile.online = status self.user.profile.save(update_fields=['online']) except Exception: pass @database_sync_to_async def get_avatar(self): try: av = self.user.profile.avatar return av.url if av else None except Exception: return None @database_sync_to_async def get_role(self): try: return self.user.profile.role except Exception: return 'user' class DMConsumer(AsyncWebsocketConsumer): async def connect(self): self.user = self.scope['user'] self.other_username = self.scope['url_route']['kwargs']['username'] if not self.user.is_authenticated: await self.close() return other_id = await self.get_other_id() if other_id is None: await self.close() return ids = sorted([self.user.id, other_id]) self.group_name = f'dm_{ids[0]}_{ids[1]}' await self.channel_layer.group_add(self.group_name, self.channel_name) await self.set_online(True) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard(self.group_name, self.channel_name) await self.set_online(False) async def receive(self, text_data): data = json.loads(text_data) content = data.get('message', '').strip() if not content: return msg = await self.save_dm(content) await self.channel_layer.group_send( self.group_name, { 'type': 'dm_message', 'message': content, 'sender': self.user.username, 'sender_id': self.user.id, 'message_id': msg.id, 'timestamp': msg.timestamp.strftime('%H:%M'), 'avatar': await self.get_avatar(), } ) async def dm_message(self, event): await self.send(text_data=json.dumps(event)) @database_sync_to_async def get_other_id(self): from django.contrib.auth.models import User try: return User.objects.get(username=self.other_username).id except User.DoesNotExist: return None @database_sync_to_async def save_dm(self, content): from django.contrib.auth.models import User from chat.models import DirectMessage other = User.objects.get(username=self.other_username) return DirectMessage.objects.create(sender=self.user, receiver=other, content=content) @database_sync_to_async def set_online(self, status): try: self.user.profile.online = status self.user.profile.save(update_fields=['online']) except Exception: pass @database_sync_to_async def get_avatar(self): try: av = self.user.profile.avatar return av.url if av else None except Exception: return None class VoiceSignalingConsumer(AsyncWebsocketConsumer): async def connect(self): self.channel_id = self.scope['url_route']['kwargs']['channel_id'] self.group_name = f'voice_{self.channel_id}' self.user = self.scope['user'] if not self.user.is_authenticated: await self.close() return # Add to voice channel group await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() # Update voice channel in DB await self.set_voice_channel(self.channel_id) # Broadcast to voice channel group for WebRTC signaling await self.channel_layer.group_send( self.group_name, { 'type': 'voice_user_joined', 'user_id': self.user.id, 'username': self.user.username, 'avatar': await self.get_avatar(), } ) # Broadcast to global updates group to update sidebars for everyone await self.channel_layer.group_send( 'global_updates', { 'type': 'voice_presence_change', 'user_id': self.user.id, 'username': self.user.username, 'channel_id': int(self.channel_id), 'action': 'joined', 'avatar': await self.get_avatar(), } ) async def disconnect(self, close_code): # Update voice channel in DB await self.set_voice_channel(None) # Broadcast user left to voice group await self.channel_layer.group_send( self.group_name, { 'type': 'voice_user_left', 'user_id': self.user.id, 'username': self.user.username, } ) # Broadcast user left to global updates group await self.channel_layer.group_send( 'global_updates', { 'type': 'voice_presence_change', 'user_id': self.user.id, 'username': self.user.username, 'channel_id': int(self.channel_id), 'action': 'left', } ) await self.channel_layer.group_discard(self.group_name, self.channel_name) async def receive(self, text_data): data = json.loads(text_data) action = data.get('action') if action == 'signal': target_id = data.get('target_id') signal_data = data.get('data') # Relay signal to the target user await self.channel_layer.group_send( self.group_name, { 'type': 'voice_signal_relay', 'sender_id': self.user.id, 'sender_username': self.user.username, 'target_id': target_id, 'data': signal_data, } ) async def voice_user_joined(self, event): if event['user_id'] != self.user.id: await self.send(text_data=json.dumps(event)) async def voice_user_left(self, event): if event['user_id'] != self.user.id: await self.send(text_data=json.dumps(event)) async def voice_signal_relay(self, event): if event['target_id'] == self.user.id: await self.send(text_data=json.dumps(event)) @database_sync_to_async def get_avatar(self): try: av = self.user.profile.avatar return av.url if av else None except Exception: return None @database_sync_to_async def set_voice_channel(self, channel_id): try: if channel_id: from chat.models import Channel channel = Channel.objects.get(id=channel_id) self.user.profile.current_voice_channel = channel else: self.user.profile.current_voice_channel = None self.user.profile.save(update_fields=['current_voice_channel']) except Exception as e: print("Error saving voice channel:", e) class GlobalConsumer(AsyncWebsocketConsumer): async def connect(self): self.user = self.scope['user'] if not self.user.is_authenticated: await self.close() return self.group_name = 'global_updates' await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() await self.set_online(True) await self.channel_layer.group_send( self.group_name, { 'type': 'user_presence_change', 'user_id': self.user.id, 'username': self.user.username, 'online': True } ) async def disconnect(self, close_code): await self.channel_layer.group_discard(self.group_name, self.channel_name) await self.set_online(False) await self.channel_layer.group_send( self.group_name, { 'type': 'user_presence_change', 'user_id': self.user.id, 'username': self.user.username, 'online': False } ) async def user_presence_change(self, event): await self.send(text_data=json.dumps(event)) async def voice_presence_change(self, event): await self.send(text_data=json.dumps(event)) @database_sync_to_async def set_online(self, status): try: self.user.profile.online = status self.user.profile.save(update_fields=['online']) except Exception: pass