NDOJ/chat_box/views.py

531 lines
16 KiB
Python
Raw Normal View History

2020-01-27 20:37:52 +00:00
from django.utils.translation import gettext as _
2020-03-16 02:23:14 +00:00
from django.views.generic import ListView
2022-05-14 17:57:27 +00:00
from django.http import (
HttpResponse,
JsonResponse,
HttpResponseBadRequest,
HttpResponsePermanentRedirect,
HttpResponseRedirect,
)
2020-03-21 04:48:04 +00:00
from django.core.paginator import Paginator
2021-06-19 03:26:43 +00:00
from django.core.exceptions import PermissionDenied
2020-05-06 04:51:16 +00:00
from django.shortcuts import render
from django.forms.models import model_to_dict
2022-05-14 17:57:27 +00:00
from django.db.models import (
Case,
BooleanField,
When,
Q,
Subquery,
OuterRef,
Exists,
Count,
IntegerField,
2023-08-28 19:20:35 +00:00
F,
2022-05-14 17:57:27 +00:00
)
2021-11-21 04:23:03 +00:00
from django.db.models.functions import Coalesce
2020-06-08 20:11:07 +00:00
from django.utils import timezone
2021-06-19 03:26:43 +00:00
from django.contrib.auth.decorators import login_required
2021-11-21 04:23:03 +00:00
from django.urls import reverse
2020-01-27 20:37:52 +00:00
2021-06-19 03:26:43 +00:00
import datetime
2020-07-03 02:50:31 +00:00
2021-06-19 03:26:43 +00:00
from judge import event_poster as event
2020-03-21 04:48:04 +00:00
from judge.jinja2.gravatar import gravatar
2021-11-21 04:23:03 +00:00
from judge.models import Friend
from chat_box.models import Message, Profile, Room, UserRoom, Ignore
2023-08-28 19:20:35 +00:00
from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel
2021-11-21 04:23:03 +00:00
2020-03-21 04:48:04 +00:00
import json
2021-11-21 04:23:03 +00:00
2020-03-21 04:48:04 +00:00
2020-03-16 02:23:14 +00:00
class ChatView(ListView):
2022-05-14 17:57:27 +00:00
context_object_name = "message"
template_name = "chat/chat.html"
title = _("LQDOJ Chat")
2022-05-14 17:57:27 +00:00
2021-11-21 04:23:03 +00:00
def __init__(self):
super().__init__()
self.room_id = None
self.room = None
self.messages = None
2023-08-28 19:20:35 +00:00
self.first_page_size = 20 # only for first request
self.follow_up_page_size = 50
2020-03-21 04:48:04 +00:00
2020-05-06 04:51:16 +00:00
def get_queryset(self):
2021-06-19 03:26:43 +00:00
return self.messages
2020-05-06 04:51:16 +00:00
2023-02-18 21:12:33 +00:00
def has_next(self):
try:
msg = Message.objects.filter(room=self.room_id).earliest("id")
except Exception as e:
return False
return msg not in self.messages
2020-03-21 04:48:04 +00:00
def get(self, request, *args, **kwargs):
2022-05-14 17:57:27 +00:00
request_room = kwargs["room_id"]
2023-08-28 19:20:35 +00:00
page_size = self.follow_up_page_size
2023-02-18 21:12:33 +00:00
try:
last_id = int(request.GET.get("last_id"))
except Exception:
last_id = 1e15
2023-08-28 19:20:35 +00:00
page_size = self.first_page_size
2023-02-18 21:12:33 +00:00
only_messages = request.GET.get("only_messages")
2021-11-21 04:23:03 +00:00
if request_room:
try:
self.room = Room.objects.get(id=request_room)
if not can_access_room(request, self.room):
return HttpResponseBadRequest()
except Room.DoesNotExist:
return HttpResponseBadRequest()
else:
request_room = None
2023-02-18 21:12:33 +00:00
self.room_id = request_room
2023-08-28 19:20:35 +00:00
self.messages = (
Message.objects.filter(hidden=False, room=self.room_id, id__lt=last_id)
.select_related("author", "author__user")
.defer("author__about", "author__user_script")[:page_size]
)
2023-02-18 21:12:33 +00:00
if not only_messages:
2020-03-21 04:48:04 +00:00
return super().get(request, *args, **kwargs)
2022-05-14 17:57:27 +00:00
return render(
request,
"chat/message_list.html",
{
2023-02-18 21:12:33 +00:00
"object_list": self.messages,
"has_next": self.has_next(),
2022-05-14 17:57:27 +00:00
},
)
2020-03-19 05:13:55 +00:00
def get_context_data(self, **kwargs):
2020-03-19 22:51:56 +00:00
context = super().get_context_data(**kwargs)
2020-10-26 19:39:47 +00:00
2022-05-14 17:57:27 +00:00
context["title"] = self.title
context["last_msg"] = event.last()
2023-08-28 19:20:35 +00:00
context["status_sections"] = get_status_context(self.request.profile)
2022-05-14 17:57:27 +00:00
context["room"] = self.room_id
2023-02-18 21:12:33 +00:00
context["has_next"] = self.has_next()
2022-05-14 17:57:27 +00:00
context["unread_count_lobby"] = get_unread_count(None, self.request.profile)
2023-08-28 19:20:35 +00:00
context["chat_channel"] = encrypt_channel(
"chat_" + str(self.request.profile.id)
)
context["chat_lobby_channel"] = encrypt_channel("chat_lobby")
2021-11-21 04:23:03 +00:00
if self.room:
users_room = [self.room.user_one, self.room.user_two]
users_room.remove(self.request.profile)
2022-05-14 17:57:27 +00:00
context["other_user"] = users_room[0]
context["other_online"] = get_user_online_status(context["other_user"])
context["is_ignored"] = Ignore.is_ignored(
self.request.profile, context["other_user"]
)
2021-11-21 04:23:03 +00:00
else:
2022-05-14 17:57:27 +00:00
context["online_count"] = get_online_count()
context["message_template"] = {
"author": self.request.profile,
"id": "$id",
"time": timezone.now(),
"body": "$body",
}
2020-03-19 05:13:55 +00:00
return context
2020-05-05 18:17:42 +00:00
2021-06-19 03:26:43 +00:00
2020-05-05 18:17:42 +00:00
def delete_message(request):
2022-05-14 17:57:27 +00:00
ret = {"delete": "done"}
if request.method == "GET":
2023-03-16 04:08:13 +00:00
return HttpResponseBadRequest()
2020-05-05 18:17:42 +00:00
2023-03-16 04:08:13 +00:00
if not request.user.is_staff:
return HttpResponseBadRequest()
try:
messid = int(request.POST.get("message"))
mess = Message.objects.get(id=messid)
except:
return HttpResponseBadRequest()
mess.hidden = True
mess.save()
return JsonResponse(ret)
2022-05-14 17:57:27 +00:00
2023-03-16 04:08:13 +00:00
def mute_message(request):
ret = {"mute": "done"}
if request.method == "GET":
return HttpResponseBadRequest()
if not request.user.is_staff:
return HttpResponseBadRequest()
try:
messid = int(request.POST.get("message"))
mess = Message.objects.get(id=messid)
except:
return HttpResponseBadRequest()
mess.author.mute = True
mess.author.save()
Message.objects.filter(room=None, author=mess.author).update(hidden=True)
2022-05-14 17:57:27 +00:00
2020-05-22 17:16:42 +00:00
return JsonResponse(ret)
2021-06-19 03:26:43 +00:00
@login_required
def post_message(request):
2022-05-14 17:57:27 +00:00
ret = {"msg": "posted"}
if request.method != "POST":
2021-11-21 04:23:03 +00:00
return HttpResponseBadRequest()
2022-05-14 17:57:27 +00:00
if len(request.POST["body"]) > 5000:
2022-05-12 05:01:47 +00:00
return HttpResponseBadRequest()
2021-06-19 03:26:43 +00:00
2021-11-21 04:23:03 +00:00
room = None
2022-05-14 17:57:27 +00:00
if request.POST["room"]:
room = Room.objects.get(id=request.POST["room"])
2021-11-21 04:23:03 +00:00
if not can_access_room(request, room) or request.profile.mute:
return HttpResponseBadRequest()
2021-06-19 03:26:43 +00:00
2022-05-14 17:57:27 +00:00
new_message = Message(author=request.profile, body=request.POST["body"], room=room)
2021-06-19 03:26:43 +00:00
new_message.save()
2021-11-21 04:23:03 +00:00
if not room:
2022-05-14 17:57:27 +00:00
event.post(
2023-08-28 19:20:35 +00:00
encrypt_channel("chat_lobby"),
2022-05-14 17:57:27 +00:00
{
"type": "lobby",
"author_id": request.profile.id,
"message": new_message.id,
"room": "None",
"tmp_id": request.POST.get("tmp_id"),
},
)
2021-11-21 04:23:03 +00:00
else:
for user in room.users():
2022-05-14 17:57:27 +00:00
event.post(
2023-08-28 19:20:35 +00:00
encrypt_channel("chat_" + str(user.id)),
2022-05-14 17:57:27 +00:00
{
"type": "private",
"author_id": request.profile.id,
"message": new_message.id,
"room": room.id,
"tmp_id": request.POST.get("tmp_id"),
},
)
2023-08-28 19:20:35 +00:00
if user != request.profile:
UserRoom.objects.filter(user=user, room=room).update(
unread_count=F("unread_count") + 1
)
2022-05-14 17:57:27 +00:00
2021-06-19 03:26:43 +00:00
return JsonResponse(ret)
2021-11-21 04:23:03 +00:00
def can_access_room(request, room):
2022-05-14 17:57:27 +00:00
return (
not room or room.user_one == request.profile or room.user_two == request.profile
)
2021-11-21 04:23:03 +00:00
2021-06-19 03:26:43 +00:00
@login_required
def chat_message_ajax(request):
2022-05-14 17:57:27 +00:00
if request.method != "GET":
2021-06-19 03:26:43 +00:00
return HttpResponseBadRequest()
try:
2022-05-14 17:57:27 +00:00
message_id = request.GET["message"]
2021-06-19 03:26:43 +00:00
except KeyError:
return HttpResponseBadRequest()
try:
message = Message.objects.filter(hidden=False).get(id=message_id)
2021-11-21 04:23:03 +00:00
room = message.room
if room and not room.contain(request.profile):
2022-05-14 17:57:27 +00:00
return HttpResponse("Unauthorized", status=401)
2021-06-19 03:26:43 +00:00
except Message.DoesNotExist:
return HttpResponseBadRequest()
2022-05-14 17:57:27 +00:00
return render(
request,
"chat/message.html",
{
"message": message,
},
)
2021-06-19 03:26:43 +00:00
2021-11-21 04:23:03 +00:00
@login_required
def update_last_seen(request, **kwargs):
2022-05-14 17:57:27 +00:00
if "room_id" in kwargs:
room_id = kwargs["room_id"]
elif request.method == "GET":
room_id = request.GET.get("room")
elif request.method == "POST":
room_id = request.POST.get("room")
2021-11-21 04:23:03 +00:00
else:
return HttpResponseBadRequest()
try:
profile = request.profile
room = None
if room_id:
2023-08-28 19:20:35 +00:00
room = Room.objects.filter(id=int(room_id)).first()
2021-11-21 04:23:03 +00:00
except Room.DoesNotExist:
return HttpResponseBadRequest()
2021-06-19 03:26:43 +00:00
2021-11-21 04:23:03 +00:00
if room and not room.contain(profile):
return HttpResponseBadRequest()
user_room, _ = UserRoom.objects.get_or_create(user=profile, room=room)
user_room.last_seen = timezone.now()
2023-08-28 19:20:35 +00:00
user_room.unread_count = 0
2021-11-21 04:23:03 +00:00
user_room.save()
2022-05-14 17:57:27 +00:00
return JsonResponse({"msg": "updated"})
2021-11-21 04:23:03 +00:00
def get_online_count():
2023-08-28 19:20:35 +00:00
last_5_minutes = timezone.now() - timezone.timedelta(minutes=5)
return Profile.objects.filter(last_access__gte=last_5_minutes).count()
2021-06-19 03:26:43 +00:00
2021-11-21 04:23:03 +00:00
def get_user_online_status(user):
time_diff = timezone.now() - user.last_access
2023-08-28 19:20:35 +00:00
is_online = time_diff <= timezone.timedelta(minutes=5)
2021-11-21 04:23:03 +00:00
return is_online
def user_online_status_ajax(request):
2022-05-14 17:57:27 +00:00
if request.method != "GET":
2021-11-21 04:23:03 +00:00
return HttpResponseBadRequest()
2022-05-14 17:57:27 +00:00
user_id = request.GET.get("user")
2021-11-21 04:23:03 +00:00
if user_id:
try:
user_id = int(user_id)
user = Profile.objects.get(id=user_id)
except Exception as e:
return HttpResponseBadRequest()
is_online = get_user_online_status(user)
2022-05-14 17:57:27 +00:00
return render(
request,
"chat/user_online_status.html",
{
"other_user": user,
"other_online": is_online,
"is_ignored": Ignore.is_ignored(request.profile, user),
},
)
2021-11-21 04:23:03 +00:00
else:
2022-05-14 17:57:27 +00:00
return render(
request,
"chat/user_online_status.html",
{
"online_count": get_online_count(),
},
)
2021-11-21 04:23:03 +00:00
2023-08-28 19:20:35 +00:00
def get_online_status(profile, other_profile_ids, rooms=None):
if not other_profile_ids:
2021-11-21 04:23:03 +00:00
return None
2023-08-28 19:20:35 +00:00
joined_ids = ",".join([str(id) for id in other_profile_ids])
other_profiles = Profile.objects.raw(
f"SELECT * from judge_profile where id in ({joined_ids}) order by field(id,{joined_ids})"
)
last_5_minutes = timezone.now() - timezone.timedelta(minutes=5)
2021-06-19 03:26:43 +00:00
ret = []
2021-11-21 04:23:03 +00:00
if rooms:
2023-08-28 19:20:35 +00:00
unread_count = get_unread_count(rooms, profile)
2021-11-21 04:23:03 +00:00
count = {}
for i in unread_count:
2022-05-14 17:57:27 +00:00
count[i["other_user"]] = i["unread_count"]
2023-08-28 19:20:35 +00:00
for other_profile in other_profiles:
2021-06-19 03:26:43 +00:00
is_online = False
2023-08-28 19:20:35 +00:00
if other_profile.last_access >= last_5_minutes:
2021-06-19 03:26:43 +00:00
is_online = True
2023-08-28 19:20:35 +00:00
user_dict = {"user": other_profile, "is_online": is_online}
if rooms and other_profile.id in count:
user_dict["unread_count"] = count[other_profile.id]
user_dict["url"] = encrypt_url(profile.id, other_profile.id)
2021-11-21 04:23:03 +00:00
ret.append(user_dict)
2021-06-19 03:26:43 +00:00
return ret
2023-08-28 19:20:35 +00:00
def get_status_context(profile, include_ignored=False):
2021-11-21 04:23:03 +00:00
if include_ignored:
2023-08-28 19:20:35 +00:00
ignored_users = []
2021-11-21 04:23:03 +00:00
queryset = Profile.objects
else:
2023-08-28 19:20:35 +00:00
ignored_users = list(
Ignore.get_ignored_users(profile).values_list("id", flat=True)
)
2021-11-21 04:23:03 +00:00
queryset = Profile.objects.exclude(id__in=ignored_users)
2023-08-28 19:20:35 +00:00
last_5_minutes = timezone.now() - timezone.timedelta(minutes=5)
2022-05-14 17:57:27 +00:00
recent_profile = (
2023-08-28 19:20:35 +00:00
Room.objects.filter(Q(user_one=profile) | Q(user_two=profile))
2022-05-14 17:57:27 +00:00
.annotate(
2021-11-21 04:23:03 +00:00
last_msg_time=Subquery(
2022-05-14 17:57:27 +00:00
Message.objects.filter(room=OuterRef("pk")).values("time")[:1]
2021-11-21 04:23:03 +00:00
),
other_user=Case(
2023-08-28 19:20:35 +00:00
When(user_one=profile, then="user_two"),
2022-05-14 17:57:27 +00:00
default="user_one",
),
)
.filter(last_msg_time__isnull=False)
.exclude(other_user__in=ignored_users)
.order_by("-last_msg_time")
.values("other_user", "id")[:20]
)
2023-08-28 19:20:35 +00:00
recent_profile_ids = [str(i["other_user"]) for i in recent_profile]
2022-05-14 17:57:27 +00:00
recent_rooms = [int(i["id"]) for i in recent_profile]
friend_list = (
2023-08-28 19:20:35 +00:00
Friend.get_friend_profiles(profile)
.exclude(id__in=recent_profile_ids)
2022-05-14 17:57:27 +00:00
.exclude(id__in=ignored_users)
.order_by("-last_access")
2023-08-28 19:20:35 +00:00
.values_list("id", flat=True)
2022-05-14 17:57:27 +00:00
)
2023-08-28 19:20:35 +00:00
2022-05-14 17:57:27 +00:00
admin_list = (
queryset.filter(display_rank="admin")
.exclude(id__in=friend_list)
2023-08-28 19:20:35 +00:00
.exclude(id__in=recent_profile_ids)
.values_list("id", flat=True)
2022-05-14 17:57:27 +00:00
)
2023-08-28 19:20:35 +00:00
2022-05-14 17:57:27 +00:00
all_user_status = (
2023-08-28 19:20:35 +00:00
queryset.filter(last_access__gte=last_5_minutes)
2022-05-14 17:57:27 +00:00
.annotate(is_online=Case(default=True, output_field=BooleanField()))
.order_by("-rating")
.exclude(id__in=friend_list)
.exclude(id__in=admin_list)
2023-08-28 19:20:35 +00:00
.exclude(id__in=recent_profile_ids)
.values_list("id", flat=True)[:30]
2022-05-14 17:57:27 +00:00
)
2021-07-02 06:38:48 +00:00
return [
{
2022-05-14 17:57:27 +00:00
"title": "Recent",
2023-08-28 19:20:35 +00:00
"user_list": get_online_status(profile, recent_profile_ids, recent_rooms),
2021-07-02 06:38:48 +00:00
},
{
2022-05-14 17:57:27 +00:00
"title": "Following",
2023-08-28 19:20:35 +00:00
"user_list": get_online_status(profile, friend_list),
2021-11-21 04:23:03 +00:00
},
{
2022-05-14 17:57:27 +00:00
"title": "Admin",
2023-08-28 19:20:35 +00:00
"user_list": get_online_status(profile, admin_list),
2021-07-02 06:38:48 +00:00
},
{
2022-05-14 17:57:27 +00:00
"title": "Other",
2023-08-28 19:20:35 +00:00
"user_list": get_online_status(profile, all_user_status),
2021-07-02 06:38:48 +00:00
},
]
2021-06-19 03:26:43 +00:00
@login_required
def online_status_ajax(request):
2022-05-14 17:57:27 +00:00
return render(
request,
"chat/online_status.html",
{
2023-08-28 19:20:35 +00:00
"status_sections": get_status_context(request.profile),
2022-05-14 17:57:27 +00:00
"unread_count_lobby": get_unread_count(None, request.profile),
},
)
2021-11-21 04:23:03 +00:00
@login_required
def get_room(user_one, user_two):
if user_one.id > user_two.id:
user_one, user_two = user_two, user_one
room, created = Room.objects.get_or_create(user_one=user_one, user_two=user_two)
return room
@login_required
def get_or_create_room(request):
2022-05-14 17:57:27 +00:00
if request.method == "GET":
decrypted_other_id = request.GET.get("other")
elif request.method == "POST":
decrypted_other_id = request.POST.get("other")
2021-11-21 04:57:47 +00:00
else:
return HttpResponseBadRequest()
2021-11-21 04:23:03 +00:00
2021-11-21 04:57:47 +00:00
request_id, other_id = decrypt_url(decrypted_other_id)
2021-11-21 04:23:03 +00:00
if not other_id or not request_id or request_id != request.profile.id:
return HttpResponseBadRequest()
try:
other_user = Profile.objects.get(id=int(other_id))
except Exception:
return HttpResponseBadRequest()
user = request.profile
2022-05-14 17:57:27 +00:00
2021-11-21 04:23:03 +00:00
if not other_user or not user:
return HttpResponseBadRequest()
# TODO: each user can only create <= 300 rooms
room = get_room(other_user, user)
2021-11-23 03:54:48 +00:00
for u in [other_user, user]:
2022-01-30 14:35:20 +00:00
user_room, created = UserRoom.objects.get_or_create(user=u, room=room)
if created:
user_room.last_seen = timezone.now()
user_room.save()
2021-11-23 03:54:48 +00:00
2022-05-14 17:57:27 +00:00
if request.method == "GET":
return JsonResponse({"room": room.id, "other_user_id": other_user.id})
return HttpResponseRedirect(reverse("chat", kwargs={"room_id": room.id}))
2021-11-21 04:23:03 +00:00
def get_unread_count(rooms, user):
if rooms:
2023-08-28 19:20:35 +00:00
res = (
UserRoom.objects.filter(user=user, room__in=rooms, unread_count__gt=0)
.select_related("room__user_one", "room__user_two")
.values("unread_count", "room__user_one", "room__user_two")
2022-05-14 17:57:27 +00:00
)
2023-08-28 19:20:35 +00:00
for ur in res:
ur["other_user"] = (
ur["room__user_one"]
if ur["room__user_two"] == user.id
else ur["room__user_two"]
2022-05-14 17:57:27 +00:00
)
2023-08-28 19:20:35 +00:00
return res
2022-05-14 17:57:27 +00:00
else: # lobby
2023-08-28 19:20:35 +00:00
user_room = UserRoom.objects.filter(user=user, room__isnull=True).first()
if not user_room:
return 0
last_seen = user_room.last_seen
2022-05-14 17:57:27 +00:00
res = (
2023-08-28 19:20:35 +00:00
Message.objects.filter(room__isnull=True, time__gte=last_seen)
.exclude(author=user)
.exclude(hidden=True)
.count()
2022-05-14 17:57:27 +00:00
)
2021-12-21 00:55:26 +00:00
2023-08-28 19:20:35 +00:00
return res
2021-11-21 04:23:03 +00:00
@login_required
def toggle_ignore(request, **kwargs):
2022-05-14 17:57:27 +00:00
user_id = kwargs["user_id"]
2021-11-21 04:23:03 +00:00
if not user_id:
return HttpResponseBadRequest()
try:
other_user = Profile.objects.get(id=user_id)
except:
return HttpResponseBadRequest()
Ignore.toggle_ignore(request.profile, other_user)
2022-05-14 17:57:27 +00:00
next_url = request.GET.get("next", "/")
2021-11-22 03:01:35 +00:00
return HttpResponseRedirect(next_url)