furry/chat/consumers.py

335 lines
11 KiB
Python

"""
WebSocket Consumers für Live-Chat
"""
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from django.utils import timezone
from .models import ChatRoom, ChatMessage, UserOnlineStatus, ChatNotification
from django.contrib.auth.models import User
class ChatConsumer(AsyncWebsocketConsumer):
"""WebSocket Consumer für Chat-Funktionalität"""
async def connect(self):
"""WebSocket-Verbindung herstellen"""
self.room_id = self.scope['url_route']['kwargs']['room_id']
self.room_group_name = f'chat_{self.room_id}'
self.user = self.scope['user']
# Prüfen ob User Zugriff auf Chat-Raum hat
if await self.can_access_room():
# Zur Chat-Gruppe beitreten
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
# Online-Status aktualisieren
await self.update_online_status(True)
await self.accept()
# System-Nachricht senden
await self.send_system_message(f"{self.user.username} ist dem Chat beigetreten")
# Ungelesene Nachrichten als gelesen markieren
await self.mark_messages_as_read()
else:
await self.close()
async def disconnect(self, close_code):
"""WebSocket-Verbindung trennen"""
# Online-Status aktualisieren
await self.update_online_status(False)
# System-Nachricht senden
await self.send_system_message(f"{self.user.username} hat den Chat verlassen")
# Aus Chat-Gruppe austreten
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
"""Nachricht vom Client empfangen"""
try:
data = json.loads(text_data)
message_type = data.get('type', 'message')
if message_type == 'message':
await self.handle_message(data)
elif message_type == 'typing':
await self.handle_typing(data)
elif message_type == 'read':
await self.handle_read_messages()
elif message_type == 'file_upload':
await self.handle_file_upload(data)
except json.JSONDecodeError:
await self.send_error_message("Ungültiges JSON-Format")
except Exception as e:
await self.send_error_message(f"Fehler: {str(e)}")
async def handle_message(self, data):
"""Chat-Nachricht verarbeiten"""
content = data.get('content', '').strip()
message_type = data.get('message_type', 'text')
if not content and message_type == 'text':
return
# Nachricht in Datenbank speichern
message = await self.save_message(content, message_type, data.get('file_url'))
# Nachricht an alle im Chat senden
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': {
'id': str(message.id),
'sender': message.sender.username,
'sender_id': message.sender.id,
'content': message.content,
'message_type': message.message_type,
'file_url': message.file.url if message.file else None,
'image_url': message.image.url if message.image else None,
'created_at': message.created_at.isoformat(),
'is_system': message.is_system_message,
}
}
)
# Benachrichtigung an andere User senden
await self.send_notification_to_others(message)
async def handle_typing(self, data):
"""Typing-Indicator verarbeiten"""
is_typing = data.get('typing', False)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'typing_indicator',
'user': self.user.username,
'user_id': self.user.id,
'typing': is_typing
}
)
async def handle_read_messages(self):
"""Nachrichten als gelesen markieren"""
await self.mark_messages_as_read()
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'messages_read',
'user_id': self.user.id
}
)
async def handle_file_upload(self, data):
"""Datei-Upload verarbeiten"""
file_url = data.get('file_url')
file_type = data.get('file_type', 'file')
if file_type == 'image':
message_type = 'image'
else:
message_type = 'file'
message = await self.save_message(
f"Datei hochgeladen: {data.get('filename', 'Unbekannt')}",
message_type,
file_url
)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': {
'id': str(message.id),
'sender': message.sender.username,
'sender_id': message.sender.id,
'content': message.content,
'message_type': message.message_type,
'file_url': file_url,
'filename': data.get('filename'),
'created_at': message.created_at.isoformat(),
'is_system': False,
}
}
)
async def chat_message(self, event):
"""Chat-Nachricht an Client senden"""
await self.send(text_data=json.dumps({
'type': 'message',
'message': event['message']
}))
async def typing_indicator(self, event):
"""Typing-Indicator an Client senden"""
await self.send(text_data=json.dumps({
'type': 'typing',
'user': event['user'],
'user_id': event['user_id'],
'typing': event['typing']
}))
async def messages_read(self, event):
"""Nachrichten-als-gelesen-Status senden"""
await self.send(text_data=json.dumps({
'type': 'read',
'user_id': event['user_id']
}))
async def system_message(self, event):
"""System-Nachricht an Client senden"""
await self.send(text_data=json.dumps({
'type': 'system',
'message': event['message']
}))
async def error_message(self, event):
"""Fehler-Nachricht an Client senden"""
await self.send(text_data=json.dumps({
'type': 'error',
'message': event['message']
}))
# Database Operations
@database_sync_to_async
def can_access_room(self):
"""Prüfen ob User Zugriff auf Chat-Raum hat"""
try:
room = ChatRoom.objects.get(id=self.room_id)
return self.user == room.customer or self.user == room.admin or self.user.is_staff
except ChatRoom.DoesNotExist:
return False
@database_sync_to_async
def save_message(self, content, message_type, file_url=None):
"""Nachricht in Datenbank speichern"""
room = ChatRoom.objects.get(id=self.room_id)
message = ChatMessage.objects.create(
room=room,
sender=self.user,
content=content,
message_type=message_type,
is_system_message=False
)
return message
@database_sync_to_async
def update_online_status(self, is_online):
"""Online-Status aktualisieren"""
status, created = UserOnlineStatus.objects.get_or_create(user=self.user)
status.is_online = is_online
status.current_room = ChatRoom.objects.get(id=self.room_id) if is_online else None
status.save()
@database_sync_to_async
def mark_messages_as_read(self):
"""Nachrichten als gelesen markieren"""
room = ChatRoom.objects.get(id=self.room_id)
room.mark_messages_as_read(self.user)
@database_sync_to_async
def send_system_message(self, content):
"""System-Nachricht senden"""
room = ChatRoom.objects.get(id=self.room_id)
message = ChatMessage.objects.create(
room=room,
sender=self.user,
content=content,
message_type='system',
is_system_message=True
)
return message
@database_sync_to_async
def send_notification_to_others(self, message):
"""Benachrichtigung an andere User senden"""
room = message.room
other_users = room.get_other_users(message.sender)
for user in other_users:
ChatNotification.objects.create(
user=user,
room=room,
notification_type='new_message',
title=f'Neue Nachricht von {message.sender.username}',
message=f'{message.content[:100]}...' if len(message.content) > 100 else message.content
)
class NotificationConsumer(AsyncWebsocketConsumer):
"""WebSocket Consumer für Benachrichtigungen"""
async def connect(self):
"""WebSocket-Verbindung herstellen"""
self.user = self.scope['user']
if self.user.is_authenticated:
self.notification_group_name = f'notifications_{self.user.id}'
# Zur Benachrichtigungs-Gruppe beitreten
await self.channel_layer.group_add(
self.notification_group_name,
self.channel_name
)
await self.accept()
else:
await self.close()
async def disconnect(self, close_code):
"""WebSocket-Verbindung trennen"""
if hasattr(self, 'notification_group_name'):
await self.channel_layer.group_discard(
self.notification_group_name,
self.channel_name
)
async def receive(self, text_data):
"""Nachricht vom Client empfangen"""
try:
data = json.loads(text_data)
action = data.get('action')
if action == 'mark_read':
await self.mark_notification_read(data.get('notification_id'))
except json.JSONDecodeError:
await self.send_error_message("Ungültiges JSON-Format")
async def notification_message(self, event):
"""Benachrichtigung an Client senden"""
await self.send(text_data=json.dumps({
'type': 'notification',
'notification': event['notification']
}))
@database_sync_to_async
def mark_notification_read(self, notification_id):
"""Benachrichtigung als gelesen markieren"""
try:
notification = ChatNotification.objects.get(
id=notification_id,
user=self.user
)
notification.read_at = timezone.now()
notification.save()
except ChatNotification.DoesNotExist:
pass