robosats/chat/views.py

232 lines
8.3 KiB
Python
Raw Normal View History

from datetime import timedelta
2022-01-13 14:07:20 +03:00
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.contrib.auth.models import User
2022-10-25 21:04:12 +03:00
from django.utils import timezone
from drf_spectacular.utils import OpenApiParameter, extend_schema
2022-10-25 21:04:12 +03:00
from rest_framework import status, viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
2022-10-25 21:04:12 +03:00
from rest_framework.response import Response
from api.models import Order
from api.tasks import send_notification
2022-10-25 21:04:12 +03:00
from chat.models import ChatRoom, Message
from chat.serializers import ChatSerializer, InMessageSerializer, PostMessageSerializer
2022-10-20 12:56:10 +03:00
class ChatView(viewsets.ViewSet):
serializer_class = ChatSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
2022-10-20 12:56:10 +03:00
lookup_url_kwarg = ["order_id", "offset"]
2022-10-20 12:56:10 +03:00
queryset = Message.objects.filter(
order__status__in=[Order.Status.CHA, Order.Status.FSE]
)
@extend_schema(
request=ChatSerializer,
parameters=[
OpenApiParameter(
name="order_id", location=OpenApiParameter.QUERY, type=int
),
OpenApiParameter(name="offset", location=OpenApiParameter.QUERY, type=int),
],
)
def get(self, request, format=None):
"""
Returns chat messages for an order with an index higher than `offset`.
"""
order_id = request.GET.get("order_id", None)
offset = request.GET.get("offset", 0)
if order_id is None:
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "Order ID does not exist"},
status.HTTP_400_BAD_REQUEST,
2022-10-20 12:56:10 +03:00
)
order = Order.objects.get(id=order_id)
if not (request.user == order.maker or request.user == order.taker):
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "You are not participant in this order"},
status.HTTP_400_BAD_REQUEST,
)
if order.status not in [Order.Status.CHA, Order.Status.FSE]:
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "Order is not in chat status"},
status.HTTP_400_BAD_REQUEST,
)
queryset = Message.objects.filter(order=order, index__gt=offset)
2022-11-15 19:38:31 +03:00
chatroom, created = ChatRoom.objects.get_or_create(
id=order_id,
order=order,
room_group_name=f"chat_order_{order_id}",
defaults={
"maker": order.maker,
"maker_connected": order.maker == request.user,
"taker": order.taker,
"taker_connected": order.taker == request.user,
},
)
2022-10-20 12:56:10 +03:00
# is_peer_connected() mockup. Update connection status based on last time a GET request was sent
if chatroom.maker == request.user:
chatroom.taker_connected = order.taker.last_login > (
2022-10-20 12:56:10 +03:00
timezone.now() - timedelta(minutes=1)
)
chatroom.maker_connected = True
peer_connected = chatroom.taker_connected
peer_public_key = order.taker.robot.public_key
elif chatroom.taker == request.user:
chatroom.maker_connected = order.maker.last_login > (
2022-10-20 12:56:10 +03:00
timezone.now() - timedelta(minutes=1)
)
chatroom.taker_connected = True
peer_connected = chatroom.maker_connected
peer_public_key = order.maker.robot.public_key
2022-10-20 12:56:10 +03:00
chatroom.save(update_fields=["maker_connected", "taker_connected"])
messages = []
for message in queryset:
d = InMessageSerializer(message).data
# Re-serialize so the response is identical to the consumer message
data = {
2022-10-20 12:56:10 +03:00
"index": d["index"],
"time": d["created_at"],
"message": d["PGP_message"],
"nick": User.objects.get(id=d["sender"]).username,
}
messages.append(data)
Add turtle chat component Squashed commit of the following: commit f60870fcfe574dc4ab1343e25241b6ef7cc2721b Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Thu Nov 10 10:30:42 2022 -0800 Fix internal error when entering chat commit f1eeb49f2a86575eb2e85cdff20460276e71b806 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 10:08:22 2022 -0800 Fix final serializer commit d0c08ba6ad4378a9539c0be83b6f4f8b958b532e Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:44:57 2022 -0800 Chat API changes commit a66bf64edc06d936612db6bf75476b54e6a84334 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:28:29 2022 -0800 Fix param on post commit 60b18d13c2ec625497323371a2a6f64c9c911e47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 08:56:25 2022 -0800 Fix serializer commit 11212d30eeffde37e07d2e6e5c1fb36df46916ad Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 21:07:18 2022 +0100 CR 2 commit c82790cb81dd9919de97c39f2553974784ffe92d Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 20:09:18 2022 +0100 Fix commit 605a3b69a1fcf795e45b2acba1e12436f8545f8a Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 14:44:42 2022 +0100 CR commit 09776e9c8fa85c253f28c75361829dab5df4d978 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 18:12:29 2022 +0100 translations commit 432e4d23991164b164d2ab3e4f31790a992dc601 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 17:39:02 2022 +0100 Switch and better UX commit df6e476613006f6a861bab68f8a4261bc8f641e0 Author: KoalaSat <yv1vtrul@duck.com> Date: Tue Nov 1 18:20:01 2022 +0100 Unused code commit 5b8d6b4d32980e31bb1d682444b53df1a8e16c47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Mon Oct 31 09:20:20 2022 -0700 Add Chat Turtle Mode
2022-11-11 12:28:09 +03:00
response = {
"peer_connected": peer_connected,
"messages": messages,
"peer_pubkey": peer_public_key,
}
return Response(response, status.HTTP_200_OK)
@extend_schema(request=PostMessageSerializer, responses=ChatSerializer)
def post(self, request, format=None):
"""
Adds one new message to the chatroom. If `offset` is given, will return every new message as well.
"""
serializer = PostMessageSerializer(data=request.data)
2022-10-20 12:56:10 +03:00
# Return bad request if serializer is not valid
if not serializer.is_valid():
context = {"bad_request": "Invalid serializer"}
return Response(context, status=status.HTTP_400_BAD_REQUEST)
Add turtle chat component Squashed commit of the following: commit f60870fcfe574dc4ab1343e25241b6ef7cc2721b Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Thu Nov 10 10:30:42 2022 -0800 Fix internal error when entering chat commit f1eeb49f2a86575eb2e85cdff20460276e71b806 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 10:08:22 2022 -0800 Fix final serializer commit d0c08ba6ad4378a9539c0be83b6f4f8b958b532e Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:44:57 2022 -0800 Chat API changes commit a66bf64edc06d936612db6bf75476b54e6a84334 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:28:29 2022 -0800 Fix param on post commit 60b18d13c2ec625497323371a2a6f64c9c911e47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 08:56:25 2022 -0800 Fix serializer commit 11212d30eeffde37e07d2e6e5c1fb36df46916ad Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 21:07:18 2022 +0100 CR 2 commit c82790cb81dd9919de97c39f2553974784ffe92d Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 20:09:18 2022 +0100 Fix commit 605a3b69a1fcf795e45b2acba1e12436f8545f8a Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 14:44:42 2022 +0100 CR commit 09776e9c8fa85c253f28c75361829dab5df4d978 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 18:12:29 2022 +0100 translations commit 432e4d23991164b164d2ab3e4f31790a992dc601 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 17:39:02 2022 +0100 Switch and better UX commit df6e476613006f6a861bab68f8a4261bc8f641e0 Author: KoalaSat <yv1vtrul@duck.com> Date: Tue Nov 1 18:20:01 2022 +0100 Unused code commit 5b8d6b4d32980e31bb1d682444b53df1a8e16c47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Mon Oct 31 09:20:20 2022 -0700 Add Chat Turtle Mode
2022-11-11 12:28:09 +03:00
order_id = serializer.data.get("order_id")
if order_id is None:
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "Order ID does not exist"},
status.HTTP_400_BAD_REQUEST,
2022-10-20 12:56:10 +03:00
)
order = Order.objects.get(id=order_id)
if not (request.user == order.maker or request.user == order.taker):
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "You are not participant in this order"},
status.HTTP_400_BAD_REQUEST,
)
if order.status not in [Order.Status.CHA, Order.Status.FSE]:
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "Order is not in chat status"},
status.HTTP_400_BAD_REQUEST,
)
if order.maker == request.user:
sender = order.maker
receiver = order.taker
elif order.taker == request.user:
sender = order.taker
receiver = order.maker
chatroom, _ = ChatRoom.objects.get_or_create(
2022-10-20 12:56:10 +03:00
id=order_id,
order=order,
room_group_name=f"chat_order_{order_id}",
defaults={
"maker": order.maker,
"maker_connected": order.maker == request.user,
"taker": order.taker,
"taker_connected": order.taker == request.user,
},
)
last_index = Message.objects.filter(order=order, chatroom=chatroom).count()
new_message = Message.objects.create(
2022-10-20 12:56:10 +03:00
index=last_index + 1,
PGP_message=serializer.data.get("PGP_message"),
order=order,
chatroom=chatroom,
sender=sender,
receiver=receiver,
2022-10-20 12:56:10 +03:00
)
# send Telegram notification for new message (if conditions apply)
send_notification.delay(
chat_message_id=new_message.id, message="new_chat_message"
)
# Send websocket message
if chatroom.maker == request.user:
peer_connected = chatroom.taker_connected
peer_public_key = order.taker.robot.public_key
elif chatroom.taker == request.user:
peer_connected = chatroom.maker_connected
peer_public_key = order.maker.robot.public_key
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
f"chat_order_{order_id}",
{
2022-10-20 12:56:10 +03:00
"type": "PGP_message",
"index": new_message.index,
"message": new_message.PGP_message,
"time": str(new_message.created_at),
"nick": new_message.sender.username,
"peer_connected": peer_connected,
},
)
# if offset is given, reply with messages
offset = serializer.data.get("offset", None)
2022-11-15 19:38:31 +03:00
if offset or offset == 0:
queryset = Message.objects.filter(order=order, index__gt=offset)
messages = []
for message in queryset:
d = InMessageSerializer(message).data
# Re-serialize so the response is identical to the consumer message
data = {
2022-10-20 12:56:10 +03:00
"index": d["index"],
"time": d["created_at"],
"message": d["PGP_message"],
"nick": User.objects.get(id=d["sender"]).username,
}
messages.append(data)
response = {
"peer_connected": peer_connected,
"messages": messages,
"peer_pubkey": peer_public_key,
}
else:
response = {}
return Response(response, status.HTTP_200_OK)