-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from redstone-squid/verify
Implement linking of minecraft account to discord account
- Loading branch information
Showing
11 changed files
with
254 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
"""Simple FastAPI server to generate verification codes for users.""" | ||
import os | ||
import random | ||
|
||
from fastapi import FastAPI, HTTPException | ||
|
||
from database.database import DatabaseManager | ||
|
||
app = FastAPI() | ||
|
||
|
||
@app.get("/verify") | ||
async def get_verification_code(uuid: str, super_duper_secret: str) -> int: | ||
"""Generate a verification code for a user.""" | ||
if super_duper_secret != os.environ["SYNERGY_SECRET"]: | ||
raise HTTPException(status_code=401, detail="Unauthorized") | ||
|
||
code = random.randint(100000, 999999) | ||
await DatabaseManager().table("verification_codes").insert({"minecraft_uuid": uuid, "code": code}).execute() | ||
return code | ||
|
||
|
||
if __name__ == "__main__": | ||
import asyncio | ||
import uvicorn | ||
from dotenv import load_dotenv | ||
load_dotenv() | ||
|
||
asyncio.run(DatabaseManager.setup()) | ||
uvicorn.run(app, host="0.0.0.0", port=3000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
"""A cog for verifying minecraft accounts.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
from discord.ext.commands import Cog, hybrid_command, Context | ||
|
||
from bot.submission.ui import ConfirmationView | ||
from database.user import link_minecraft_account, unlink_minecraft_account | ||
|
||
if TYPE_CHECKING: | ||
from bot.main import RedstoneSquid | ||
|
||
class VerifyCog(Cog, name="verify"): | ||
def __init__(self, bot: RedstoneSquid): | ||
self.bot = bot | ||
|
||
@hybrid_command() | ||
async def link(self, ctx: Context, code: str): | ||
"""Link your minecraft account.""" | ||
if await link_minecraft_account(ctx.author.id, code): | ||
await ctx.send("Your discord account has been linked with your minecraft account.") | ||
else: | ||
await ctx.send("Invalid code. Please generate a new code and try again.") | ||
|
||
@hybrid_command() | ||
async def unlink(self, ctx: Context): | ||
"""Unlink your minecraft account.""" | ||
view = ConfirmationView() | ||
await ctx.send("Are you sure you want to unlink your minecraft account?", view=view) | ||
|
||
await view.wait() | ||
if view.value: | ||
if await unlink_minecraft_account(ctx.author.id): | ||
await ctx.send("Your discord account has been unlinked from your minecraft account.") | ||
else: | ||
await ctx.send("An error occurred while unlinking your account. Please try again later.") | ||
|
||
|
||
async def setup(bot: RedstoneSquid): | ||
"""Called by discord.py when the cog is added to the bot via bot.load_extension.""" | ||
await bot.add_cog(VerifyCog(bot)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
CREATE TABLE users ( | ||
id SERIAL PRIMARY KEY, | ||
discord_id BIGINT, | ||
minecraft_uuid UUID, | ||
ign TEXT, | ||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() | ||
); | ||
|
||
INSERT INTO users(ign) | ||
select build_creators.creator_ign | ||
from build_creators | ||
where build_creators.creator_ign not in (select ign from users); | ||
|
||
ALTER TABLE build_creators ADD COLUMN user_id INT REFERENCES users(id); | ||
|
||
UPDATE build_creators | ||
SET user_id = users.id | ||
FROM users | ||
WHERE build_creators.creator_ign = users.ign; | ||
|
||
ALTER TABLE build_creators DROP COLUMN creator_ign; | ||
ALTER TABLE build_creators ADD PRIMARY KEY (build_id, user_id); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CREATE TABLE verification_codes ( | ||
id SMALLSERIAL PRIMARY KEY, | ||
minecraft_uuid UUID NOT NULL, | ||
code TEXT NOT NULL, | ||
created TIMESTAMP DEFAULT now() NOT NULL, | ||
expires TIMESTAMP DEFAULT now() + INTERVAL '10 minutes' NOT NULL | ||
); | ||
|
||
ALTER TABLE users ALTER COLUMN created_at SET DATA TYPE timestamp; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
"""Handles user data and operations.""" | ||
import requests | ||
|
||
from utils import utcnow | ||
from database import DatabaseManager | ||
|
||
|
||
async def add_user(user_id: int = None, ign: str = None) -> int: | ||
"""Add a user to the database. | ||
Args: | ||
user_id: The user's Discord ID. | ||
ign: The user's in-game name. | ||
Returns: | ||
The ID of the new user. | ||
""" | ||
if user_id is None and ign is None: | ||
raise ValueError("No user data provided.") | ||
|
||
db = DatabaseManager() | ||
response = await db.table("users").insert({"discord_id": user_id, "ign": ign}).execute() | ||
return response.data[0]["id"] | ||
|
||
|
||
async def link_minecraft_account(user_id: int, code: str) -> bool: | ||
"""Using a verification code, link a user's Discord account with their Minecraft account. | ||
Args: | ||
user_id: The user's Discord ID. | ||
code: The verification code. | ||
Returns: | ||
True if the code is valid and the accounts are linked, False otherwise. | ||
""" | ||
db = DatabaseManager() | ||
|
||
response = await db.table("verification_codes").select("minecraft_uuid").eq("code", code).gt("expires", utcnow()).execute() | ||
if not response.data: | ||
return False | ||
minecraft_uuid = response.data[0]["minecraft_uuid"] | ||
|
||
# TODO: This currently does not check if the ign is already in use without a UUID or discord ID given. | ||
response = await db.table("users").update({"minecraft_uuid": minecraft_uuid, "ign": get_minecraft_username(minecraft_uuid)}).eq("discord_id", user_id).execute() | ||
if not response.data: | ||
await db.table("users").insert({"discord_id": user_id, "minecraft_uuid": minecraft_uuid, "ign": get_minecraft_username(minecraft_uuid)}).execute() | ||
return True | ||
|
||
|
||
async def unlink_minecraft_account(user_id: int) -> bool: | ||
"""Unlink a user's Minecraft account from their Discord account. | ||
Args: | ||
user_id: The user's Discord ID. | ||
Returns: | ||
True if the accounts were successfully unlinked, False otherwise. | ||
""" | ||
db = DatabaseManager() | ||
await db.table("users").update({"minecraft_uuid": None}).eq("discord_id", user_id).execute() | ||
return True | ||
|
||
|
||
def get_minecraft_username(user_uuid: str) -> str: | ||
"""Get a user's Minecraft username from their UUID. | ||
Args: | ||
user_uuid: The user's Minecraft UUID. | ||
Returns: | ||
The user's Minecraft username. | ||
""" | ||
# https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape | ||
response = requests.get(f"https://sessionserver.mojang.com/session/minecraft/profile/{user_uuid}") | ||
return response.json()["name"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,5 @@ supabase-py-async | |
python-dotenv | ||
jishaku | ||
requests-toolbelt | ||
fastapi | ||
uvicorn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters