From 9002eaf807e33edc51d4e839f5c6f8175e2a33c7 Mon Sep 17 00:00:00 2001 From: Corban-Lee Jones Date: Wed, 2 Oct 2024 23:32:45 +0100 Subject: [PATCH] generate channels view --- apps/home/urls.py | 4 +- apps/home/views.py | 106 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/apps/home/urls.py b/apps/home/urls.py index be150db..8ae99a3 100644 --- a/apps/home/urls.py +++ b/apps/home/urls.py @@ -3,10 +3,10 @@ from django.urls import path from django.contrib.auth.decorators import login_required -from .views import IndexView, GuildsView, CheckBotPermissionView +from .views import IndexView, GuildsView, ChannelsView urlpatterns = [ path("", login_required(IndexView.as_view()), name="index"), path("generate-servers/", GuildsView.as_view(), name="generate-servers"), - path("bot-permissions/", CheckBotPermissionView.as_view(), name="bot-permissions") + path("generate-channels/", ChannelsView.as_view(), name="generate-channels"), ] diff --git a/apps/home/views.py b/apps/home/views.py index dd32d18..f197aee 100644 --- a/apps/home/views.py +++ b/apps/home/views.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- import json +import logging import httpx from django.conf import settings @@ -9,10 +10,13 @@ from asgiref.sync import sync_to_async from django.shortcuts import redirect from django.http import JsonResponse, HttpResponse from django.views.generic import TemplateView, View +from django.core.exceptions import PermissionDenied -from apps.home.models import Server +from apps.home.models import Server, DiscordChannel from apps.authentication.models import DiscordUser, ServerMember +log = logging.getLogger(__name__) + class IndexView(TemplateView): """ @@ -22,13 +26,22 @@ class IndexView(TemplateView): template_name = "home/index.html" +@sync_to_async +def get_user_access_token(user: DiscordUser) -> str: + return user.access_token + +@sync_to_async +def is_user_authenticated(user: DiscordUser) -> bool: + return user.is_authenticated + + class GuildsView(View): async def get(self, request, *args, **kwargs): - if not await self.is_user_authenticated(request.user): + if not await is_user_authenticated(request.user): return redirect("/oauth2/login") - access_token = await self.get_user_access_token(request.user) + access_token = await get_user_access_token(request.user) async with httpx.AsyncClient() as client: response = await client.get( @@ -51,16 +64,6 @@ class GuildsView(View): return JsonResponse(cleaned_guild_data, safe=False) - @staticmethod - @sync_to_async - def get_user_access_token(user: DiscordUser) -> str: - return user.access_token - - @staticmethod - @sync_to_async - def is_user_authenticated(user: DiscordUser) -> bool: - return user.is_authenticated - async def setup_server(self, user: DiscordUser, data: dict): is_owner = data["owner"] permissions = data["permissions"] @@ -99,7 +102,80 @@ class GuildsView(View): pass -class CheckBotPermissionView(View): +class ChannelsView(View): async def get(self, request, *args, **kwargs): - return JsonResponse({"test": ""}) + if not await is_user_authenticated(request.user): + return redirect("/oauth2/login") + + guild_id = request.GET.get("guild") + server = await Server.objects.aget(pk=guild_id) + + if not ServerMember.objects.filter(server=server, user=request.user).aexists(): + raise PermissionDenied("You aren't a member of this server.") + + channels_data, status = await self._get_channel_data(guild_id) + if status != 200: + return JsonResponse(channels_data, status=status, safe=False) + + cleaned_channels_data = await self._clean_channels_data(server, channels_data) + await self._cleanup_dead_channels(server, cleaned_channels_data) + + return JsonResponse(cleaned_channels_data, safe=False) + + async def _get_channel_data(self, guild_id: int) -> tuple[list[dict], int]: + """ + Returns the raw channel data and a response status code + from the Discord API. + """ + + async with httpx.AsyncClient() as client: + response = await client.get( + url=f"{settings.DISCORD_API_URL}/guilds/{guild_id}/channels", + headers={"Authorization": f"Bot {settings.BOT_TOKEN}"} + ) + return response.json(), response.status_code + + async def _clean_channels_data(self, server: Server, channels_data) -> list[dict]: + """ + Returns a sorted & cleaned list of channel data, also performs a + database setup for each channel to be stored. + """ + + cleaned_channels_data = [] + + for item in channels_data: + cleaned_data = await self._setup_channel(server, item) + if cleaned_data: + cleaned_channels_data.append(cleaned_data) + + cleaned_channels_data.sort(key=lambda ch: ch.get("position")) + return cleaned_channels_data + + async def _setup_channel(self, server: Server, data: dict) -> dict: + """ + Create or update an instance of DiscordChannel representing the given + `data` dictionary, returns the data or `NoneType` if `data['type'] != 0`. + """ + + if data.get("type") != 0: + return + + await DiscordChannel.objects.aupdate_or_create( + id=data["id"], + server=server, + defaults={ + "name": data["name"], + "is_nsfw": data["nsfw"] + } + ) + return data + + async def _cleanup_dead_channels(self, server: Server, channel_data: list[dict]): + """ + Deletes any unused instances of `DiscordChannel` against the given server. + """ + + channel_ids = [item["id"] for item in channel_data] + count, _ = await DiscordChannel.objects.exclude(server=server, id__in=channel_ids).adelete() + log.info("Deleted %s dead DiscordChannel object(s)", count)