335 lines
11 KiB
Python
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 |