diff options
author | 2021-03-16 16:09:47 +0300 | |
---|---|---|
committer | 2021-03-16 16:09:47 +0300 | |
commit | 094d125ec6e9719937f1be5fabe3ebaefbdc0e73 (patch) | |
tree | 5ec67192e191ccc5fb10ad56b182b026320ff4b8 | |
parent | Adds Discord Request Helper (diff) |
Moves Webhook & Role Helper To Discord File
Moves the webhook helper and the role assignment helper to the discord
file to gather all discord helpers in one location.
Signed-off-by: Hassan Abouelela <[email protected]>
-rw-r--r-- | backend/constants.py | 2 | ||||
-rw-r--r-- | backend/discord.py | 76 | ||||
-rw-r--r-- | backend/routes/forms/submit.py | 99 |
3 files changed, 80 insertions, 97 deletions
diff --git a/backend/constants.py b/backend/constants.py index 7ea4519..8a32816 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -62,8 +62,6 @@ REQUIRED_QUESTION_TYPE_DATA = { }, } -DISCORD_API_BASE_URL = "https://discord.com/api/v8" - class FormFeatures(Enum): """Lists form features. Read more in SCHEMA.md.""" diff --git a/backend/discord.py b/backend/discord.py index 08b8e07..560ab69 100644 --- a/backend/discord.py +++ b/backend/discord.py @@ -1,6 +1,5 @@ """Various utilities for working with the Discord API.""" import asyncio -import typing from urllib import parse import httpx @@ -72,3 +71,78 @@ async def fetch_user_details(bearer_token: str) -> dict: r = await make_request("GET", "users/@me", headers={"Authorization": f"Bearer {bearer_token}"}) r.raise_for_status() return r.json() + + +async def send_submission_webhook( + form: Form, + response: FormResponse, + request_user: Request.user +) -> None: + """Helper to send a submission message to a discord webhook.""" + # Stop if webhook is not available + if form.webhook is None: + raise ValueError("Got empty webhook.") + + try: + mention = request_user.discord_mention + except AttributeError: + mention = "User" + + user = response.user + + # Build Embed + embed = { + "title": "New Form Response", + "description": f"{mention} submitted a response to `{form.name}`.", + "url": f"{constants.FRONTEND_URL}/path_to_view_form/{response.id}", # noqa: E501 # TODO: Enter Form View URL + "timestamp": response.timestamp, + "color": 7506394, + } + + # Add author to embed + if request_user.is_authenticated: + embed["author"] = {"name": request_user.display_name} + + if user and user.avatar: + url = f"https://cdn.discordapp.com/avatars/{user.id}/{user.avatar}.png" + embed["author"]["icon_url"] = url + + # Build Hook + hook = { + "embeds": [embed], + "allowed_mentions": {"parse": ["users", "roles"]}, + "username": form.name or "Python Discord Forms" + } + + # Set hook message + message = form.webhook.message + if message: + # Available variables, see SCHEMA.md + ctx = { + "user": mention, + "response_id": response.id, + "form": form.name, + "form_id": form.id, + "time": response.timestamp, + } + + for key in ctx: + message = message.replace(f"{{{key}}}", str(ctx[key])) + + hook["content"] = message.replace("_USER_MENTION_", mention) + + # Post hook + await make_request("POST", form.webhook.url, hook) + + +async def assign_role(form: Form, request_user: User) -> None: + """Assigns Discord role to user when user submitted response.""" + if not form.discord_role: + raise ValueError("Got empty Discord role ID.") + + url = ( + f"guilds/{constants.DISCORD_GUILD}" + f"/members/{request_user.payload['id']}/roles/{form.discord_role}" + ) + + await make_request("PUT", url) diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 4d15ab7..d0c7a87 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -2,7 +2,6 @@ Submit a form. """ -import asyncio import binascii import datetime import hashlib @@ -17,7 +16,7 @@ from starlette.background import BackgroundTasks from starlette.requests import Request from starlette.responses import JSONResponse -from backend import constants +from backend import constants, discord from backend.authentication.user import User from backend.models import Form, FormResponse from backend.route import Route @@ -30,10 +29,6 @@ HCAPTCHA_HEADERS = { "Content-Type": "application/x-www-form-urlencoded" } -DISCORD_HEADERS = { - "Authorization": f"Bot {constants.DISCORD_BOT_TOKEN}" -} - class SubmissionResponse(BaseModel): form: Form @@ -88,7 +83,8 @@ class SubmitForm(Route): return response - async def submit(self, request: Request) -> JSONResponse: + @staticmethod + async def submit(request: Request) -> JSONResponse: """Helper method for handling submission logic.""" data = await request.json() data["timestamp"] = None @@ -188,7 +184,7 @@ class SubmitForm(Route): tasks = BackgroundTasks() if constants.FormFeatures.WEBHOOK_ENABLED.value in form.features: tasks.add_task( - self.send_submission_webhook, + discord.send_submission_webhook, form=form, response=response_obj, request_user=request.user @@ -196,7 +192,7 @@ class SubmitForm(Route): if constants.FormFeatures.ASSIGN_ROLE.value in form.features: tasks.add_task( - self.assign_role, + discord.assign_role, form=form, request_user=request.user ) @@ -210,88 +206,3 @@ class SubmitForm(Route): return JSONResponse({ "error": "Open form not found" }, status_code=404) - - @staticmethod - async def send_submission_webhook( - form: Form, - response: FormResponse, - request_user: User - ) -> None: - """Helper to send a submission message to a discord webhook.""" - # Stop if webhook is not available - if form.webhook is None: - raise ValueError("Got empty webhook.") - - try: - mention = request_user.discord_mention - except AttributeError: - mention = "User" - - user = response.user - - # Build Embed - embed = { - "title": "New Form Response", - "description": f"{mention} submitted a response to `{form.name}`.", - "url": f"{constants.FRONTEND_URL}/path_to_view_form/{response.id}", # noqa # TODO: Enter Form View URL - "timestamp": response.timestamp, - "color": 7506394, - } - - # Add author to embed - if request_user.is_authenticated: - embed["author"] = {"name": request_user.display_name} - - if user and user.avatar: - url = f"https://cdn.discordapp.com/avatars/{user.id}/{user.avatar}.png" - embed["author"]["icon_url"] = url - - # Build Hook - hook = { - "embeds": [embed], - "allowed_mentions": {"parse": ["users", "roles"]}, - "username": form.name or "Python Discord Forms" - } - - # Set hook message - message = form.webhook.message - if message: - # Available variables, see SCHEMA.md - ctx = { - "user": mention, - "response_id": response.id, - "form": form.name, - "form_id": form.id, - "time": response.timestamp, - } - - for key in ctx: - message = message.replace(f"{{{key}}}", str(ctx[key])) - - hook["content"] = message.replace("_USER_MENTION_", mention) - - # Post hook - async with httpx.AsyncClient() as client: - r = await client.post(form.webhook.url, json=hook) - r.raise_for_status() - - @staticmethod - async def assign_role(form: Form, request_user: User) -> None: - """Assigns Discord role to user when user submitted response.""" - if not form.discord_role: - raise ValueError("Got empty Discord role ID.") - - url = ( - f"{constants.DISCORD_API_BASE_URL}/guilds/{constants.DISCORD_GUILD}" - f"/members/{request_user.payload['id']}/roles/{form.discord_role}" - ) - - async with httpx.AsyncClient() as client: - resp = await client.put(url, headers=DISCORD_HEADERS) - # Handle Rate Limits - while resp.status_code == 429: - retry_after = float(resp.headers["X-Ratelimit-Reset-After"]) - await asyncio.sleep(retry_after) - resp = await client.put(url, headers=DISCORD_HEADERS) - - resp.raise_for_status() |