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 = _('Chat Box') 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() 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) @login_required def get_unread_boxes(request): if (request.method != 'GET'): return HttpResponseBadRequest() ignored_users = Ignore.get_ignored_users(request.profile) mess = Message.objects.filter(room=OuterRef('room'), time__gte=OuterRef('last_seen'))\ .exclude(author=request.profile)\ .exclude(author__in=ignored_users)\ .order_by().values('room')\ .annotate(unread_count=Count('pk')).values('unread_count') unread_boxes = UserRoom.objects\ .filter(user=request.profile, room__isnull=False)\ .annotate( unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), ).filter(unread_count__gte=1).count() return JsonResponse({'unread_boxes': unread_boxes})