from django.utils.translation import gettext as _ from django.views.generic import ListView from django.http import ( HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect, HttpResponseRedirect, ) from django.core.paginator import Paginator from django.core.exceptions import PermissionDenied from django.shortcuts import render from django.forms.models import model_to_dict from django.db.models import ( Case, BooleanField, When, Q, Subquery, OuterRef, Exists, Count, IntegerField, ) from django.db.models.functions import Coalesce from django.utils import timezone from django.contrib.auth.decorators import login_required from django.urls import reverse import datetime from judge import event_poster as event from judge.jinja2.gravatar import gravatar from judge.models import Friend from chat_box.models import Message, Profile, Room, UserRoom, Ignore from chat_box.utils import encrypt_url, decrypt_url import json class ChatView(ListView): context_object_name = "message" template_name = "chat/chat.html" title = _("LQDOJ Chat") def __init__(self): super().__init__() self.room_id = None self.room = None self.paginate_by = 50 self.messages = None self.paginator = None def get_queryset(self): return self.messages def get(self, request, *args, **kwargs): request_room = kwargs["room_id"] page = request.GET.get("page") 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 if request_room != self.room_id or not self.messages: self.room_id = request_room self.messages = Message.objects.filter(hidden=False, room=self.room_id) self.paginator = Paginator(self.messages, self.paginate_by) if page == None: update_last_seen(request, **kwargs) return super().get(request, *args, **kwargs) cur_page = self.paginator.get_page(page) return render( request, "chat/message_list.html", { "object_list": cur_page.object_list, "num_pages": self.paginator.num_pages, }, ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = self.title context["last_msg"] = event.last() context["status_sections"] = get_status_context(self.request) context["room"] = self.room_id context["unread_count_lobby"] = get_unread_count(None, self.request.profile) if self.room: users_room = [self.room.user_one, self.room.user_two] users_room.remove(self.request.profile) 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"] ) else: context["online_count"] = get_online_count() context["message_template"] = { "author": self.request.profile, "id": "$id", "time": timezone.now(), "body": "$body", } return context def delete_message(request): ret = {"delete": "done"} if request.method == "GET": return JsonResponse(ret) if request.user.is_staff: try: messid = int(request.POST.get("message")) mess = Message.objects.get(id=messid) except: return HttpResponseBadRequest() mess.hidden = True mess.save() return JsonResponse(ret) return JsonResponse(ret) @login_required def post_message(request): ret = {"msg": "posted"} if request.method != "POST": return HttpResponseBadRequest() if len(request.POST["body"]) > 5000: return HttpResponseBadRequest() room = None if request.POST["room"]: room = Room.objects.get(id=request.POST["room"]) if not can_access_room(request, room) or request.profile.mute: return HttpResponseBadRequest() new_message = Message(author=request.profile, body=request.POST["body"], room=room) new_message.save() if not room: event.post( "chat_lobby", { "type": "lobby", "author_id": request.profile.id, "message": new_message.id, "room": "None", "tmp_id": request.POST.get("tmp_id"), }, ) else: for user in room.users(): event.post( "chat_" + str(user.id), { "type": "private", "author_id": request.profile.id, "message": new_message.id, "room": room.id, "tmp_id": request.POST.get("tmp_id"), }, ) return JsonResponse(ret) def can_access_room(request, room): return ( not room or room.user_one == request.profile or room.user_two == request.profile ) @login_required def chat_message_ajax(request): if request.method != "GET": return HttpResponseBadRequest() try: message_id = request.GET["message"] except KeyError: return HttpResponseBadRequest() try: message = Message.objects.filter(hidden=False).get(id=message_id) room = message.room if room and not room.contain(request.profile): return HttpResponse("Unauthorized", status=401) except Message.DoesNotExist: return HttpResponseBadRequest() return render( request, "chat/message.html", { "message": message, }, ) @login_required def update_last_seen(request, **kwargs): 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") else: return HttpResponseBadRequest() try: profile = request.profile room = None if room_id: room = Room.objects.get(id=int(room_id)) except Room.DoesNotExist: return HttpResponseBadRequest() except Exception as e: return HttpResponseBadRequest() 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() user_room.save() return JsonResponse({"msg": "updated"}) def get_online_count(): last_two_minutes = timezone.now() - timezone.timedelta(minutes=2) return Profile.objects.filter(last_access__gte=last_two_minutes).count() def get_user_online_status(user): time_diff = timezone.now() - user.last_access is_online = time_diff <= timezone.timedelta(minutes=2) return is_online def user_online_status_ajax(request): if request.method != "GET": return HttpResponseBadRequest() user_id = request.GET.get("user") 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) return render( request, "chat/user_online_status.html", { "other_user": user, "other_online": is_online, "is_ignored": Ignore.is_ignored(request.profile, user), }, ) else: return render( request, "chat/user_online_status.html", { "online_count": get_online_count(), }, ) def get_online_status(request_user, queryset, rooms=None): if not queryset: return None last_two_minutes = timezone.now() - timezone.timedelta(minutes=2) ret = [] if rooms: unread_count = get_unread_count(rooms, request_user) count = {} for i in unread_count: count[i["other_user"]] = i["unread_count"] for user in queryset: is_online = False if user.last_access >= last_two_minutes: is_online = True user_dict = {"user": user, "is_online": is_online} if rooms and user.id in count: user_dict["unread_count"] = count[user.id] user_dict["url"] = encrypt_url(request_user.id, user.id) ret.append(user_dict) return ret def get_status_context(request, include_ignored=False): if include_ignored: ignored_users = Profile.objects.none() queryset = Profile.objects else: ignored_users = Ignore.get_ignored_users(request.profile) queryset = Profile.objects.exclude(id__in=ignored_users) last_two_minutes = timezone.now() - timezone.timedelta(minutes=2) recent_profile = ( Room.objects.filter(Q(user_one=request.profile) | Q(user_two=request.profile)) .annotate( last_msg_time=Subquery( Message.objects.filter(room=OuterRef("pk")).values("time")[:1] ), other_user=Case( When(user_one=request.profile, then="user_two"), 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] ) recent_profile_id = [str(i["other_user"]) for i in recent_profile] joined_id = ",".join(recent_profile_id) recent_rooms = [int(i["id"]) for i in recent_profile] recent_list = None if joined_id: recent_list = Profile.objects.raw( f"SELECT * from judge_profile where id in ({joined_id}) order by field(id,{joined_id})" ) friend_list = ( Friend.get_friend_profiles(request.profile) .exclude(id__in=recent_profile_id) .exclude(id__in=ignored_users) .order_by("-last_access") ) admin_list = ( queryset.filter(display_rank="admin") .exclude(id__in=friend_list) .exclude(id__in=recent_profile_id) ) all_user_status = ( queryset.filter(display_rank="user", last_access__gte=last_two_minutes) .annotate(is_online=Case(default=True, output_field=BooleanField())) .order_by("-rating") .exclude(id__in=friend_list) .exclude(id__in=admin_list) .exclude(id__in=recent_profile_id)[:30] ) return [ { "title": "Recent", "user_list": get_online_status(request.profile, recent_list, recent_rooms), }, { "title": "Following", "user_list": get_online_status(request.profile, friend_list), }, { "title": "Admin", "user_list": get_online_status(request.profile, admin_list), }, { "title": "Other", "user_list": get_online_status(request.profile, all_user_status), }, ] @login_required def online_status_ajax(request): return render( request, "chat/online_status.html", { "status_sections": get_status_context(request), "unread_count_lobby": get_unread_count(None, request.profile), }, ) @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): if request.method == "GET": decrypted_other_id = request.GET.get("other") elif request.method == "POST": decrypted_other_id = request.POST.get("other") else: return HttpResponseBadRequest() request_id, other_id = decrypt_url(decrypted_other_id) 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 if not other_user or not user: return HttpResponseBadRequest() # TODO: each user can only create <= 300 rooms room = get_room(other_user, user) for u in [other_user, user]: user_room, created = UserRoom.objects.get_or_create(user=u, room=room) if created: user_room.last_seen = timezone.now() user_room.save() if request.method == "GET": return JsonResponse({"room": room.id, "other_user_id": other_user.id}) return HttpResponseRedirect(reverse("chat", kwargs={"room_id": room.id})) def get_unread_count(rooms, user): if rooms: mess = ( Message.objects.filter( room=OuterRef("room"), time__gte=OuterRef("last_seen") ) .exclude(author=user) .order_by() .values("room") .annotate(unread_count=Count("pk")) .values("unread_count") ) return ( UserRoom.objects.filter(user=user, room__in=rooms) .annotate( unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), other_user=Case( When(room__user_one=user, then="room__user_two"), default="room__user_one", ), ) .filter(unread_count__gte=1) .values("other_user", "unread_count") ) else: # lobby mess = ( Message.objects.filter(room__isnull=True, time__gte=OuterRef("last_seen")) .exclude(author=user) .order_by() .values("room") .annotate(unread_count=Count("pk")) .values("unread_count") ) res = ( UserRoom.objects.filter(user=user, room__isnull=True) .annotate( unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), ) .values_list("unread_count", flat=True) ) return res[0] if len(res) else 0 @login_required def toggle_ignore(request, **kwargs): user_id = kwargs["user_id"] 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) next_url = request.GET.get("next", "/") return HttpResponseRedirect(next_url)