diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..73f92a8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+main.db
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cogs/verify.py b/cogs/verify.py
new file mode 100644
index 0000000..375670f
--- /dev/null
+++ b/cogs/verify.py
@@ -0,0 +1,109 @@
+import discord
+import orm
+from discord.ext import commands
+from utils import send_verification_code, VerifyCode, Student
+import config
+
+
+class VerifyCog(commands.Cog):
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @commands.slash_command()
+ async def verify(self, ctx: discord.ApplicationContext, *, code: str = None):
+ """Verifies or generates a verification code"""
+ guild = self.bot.get_guild(config.guilds[0])
+ # if ctx.guild is not None:
+ # return await ctx.respond("\N{cross mark} This command can only be run in my DMs!", ephemeral=True)
+
+ try:
+ student: Student = await Student.objects.get(user_id=ctx.author.id)
+ return await ctx.respond(f"\N{cross mark} You're already verified as {student.id}!", ephemeral=True)
+ except orm.NoMatch:
+ pass
+
+ if code is None:
+ class Modal(discord.ui.Modal):
+ def __init__(self):
+ super().__init__(
+ discord.ui.InputText(
+ custom_id="student_id",
+ label="What is your student ID",
+ placeholder="B...",
+ min_length=7,
+ max_length=7
+ ),
+ title="Enter your student ID number"
+ )
+
+ async def callback(self, interaction: discord.Interaction):
+ await interaction.response.defer()
+ if not self.children[0].value: # timed out
+ return
+ _code = await send_verification_code(
+ ctx.author,
+ self.children[0].value
+ )
+ __code = await VerifyCode.objects.create(
+ code=_code,
+ bind=ctx.author.id,
+ student_id=self.children[0].value
+ )
+ await interaction.followup.send(
+ "\N{white heavy check mark} Verification email sent to your college email "
+ f"({self.children[0].value}@my.leedscitycollege.ac.uk)\n"
+ f"Once you get that email, run this command again, with the first option being the 16"
+ f" character code.\n\n"
+ f">>> If you don't know how to access your email, go to , then "
+ f"sign in as `{self.children[0].value}@leedscitycollege.ac.uk` (notice there's no `my.` "
+ f"prefix to sign into gmail), and you should be greeted by your inbox. The default password "
+ f"is your birthday, !, and the first three letters of your first or last name"
+ f" (for example, `John Doe`, born on the 1st of february 2006, would be either "
+ f"`01022006!Joh` or `01022006!Doe`).",
+ ephemeral=True
+ )
+ return await ctx.send_modal(Modal())
+ else:
+ try:
+ existing: VerifyCode = await VerifyCode.objects.get(
+ code=code
+ )
+ except orm.NoMatch:
+ return await ctx.respond(
+ "\N{cross mark} Invalid or unknown verification code. Try again!",
+ ephemeral=True
+ )
+ else:
+ await Student.objects.create(
+ id=existing.student_id,
+ user_id=ctx.author.id
+ )
+ await existing.delete()
+ role = discord.utils.find(lambda r: r.name.lower() == "verified", guild.roles)
+ if role and role < guild.me.top_role:
+ member = await guild.fetch_member(ctx.author.id)
+ await member.add_roles(role, reason="Verified")
+ return await ctx.respond(
+ "\N{white heavy check mark} Verification complete!"
+ )
+
+ @commands.command(name="de-verify")
+ @commands.is_owner()
+ async def verification_del(self, ctx: commands.Context, *, user: discord.Member):
+ """Removes a user's verification status"""
+ await ctx.trigger_typing()
+ for code in await VerifyCode.objects.all(bind=user.id):
+ await code.delete()
+ usr = await Student.objects.first(user_id=user.id)
+ if usr:
+ await usr.delete()
+
+ role = discord.utils.find(lambda r: r.name.lower() == "verified", ctx.guild.roles)
+ if role and role < ctx.me.top_role:
+ await user.remove_roles(role, reason=f"De-verified by {ctx.author}")
+
+ return await ctx.reply(f"\N{white heavy check mark} De-verified {user}.")
+
+
+def setup(bot):
+ bot.add_cog(VerifyCog(bot))
diff --git a/main.py b/main.py
index 7b02667..e8babab 100644
--- a/main.py
+++ b/main.py
@@ -1,12 +1,16 @@
import discord
from discord.ext import commands
import config
+from utils import registry
bot = commands.Bot(
commands.when_mentioned_or("h!"),
- debug_guilds=config.guilds
+ debug_guilds=config.guilds,
+ allowed_mentions=discord.AllowedMentions.none()
)
+bot.load_extension("cogs.verify")
+bot.loop.run_until_complete(registry.create_all())
@bot.event
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..4291bba
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+py-cord==2.1.3
+aiosmtplib==1.1.7
+orm[sqlite]==0.3.1
+httpx==0.23.0
\ No newline at end of file
diff --git a/utils/__init__.py b/utils/__init__.py
new file mode 100644
index 0000000..9abc710
--- /dev/null
+++ b/utils/__init__.py
@@ -0,0 +1,2 @@
+from ._email import *
+from .db import *
diff --git a/utils/_email.py b/utils/_email.py
new file mode 100644
index 0000000..91ea924
--- /dev/null
+++ b/utils/_email.py
@@ -0,0 +1,57 @@
+import secrets
+
+import discord
+
+import config
+import aiosmtplib as smtp
+from email.message import EmailMessage
+
+gmail_cfg = {
+ "addr": "smtp.gmail.com",
+ "username": config.email,
+ "password": config.email_password,
+ "port": 465
+}
+
+
+async def send_verification_code(
+ user: discord.User,
+ student_number: str,
+ **kwargs
+) -> str:
+ """Sends a verification code, returning said verification code, to the student."""
+ code = secrets.token_hex(16)
+ text = f"Hey {user} ({student_number})! The code to join the hi^5 code is '{code}' - use " \
+ f"'/verify {code}' in the bot's DMs to continue \N{dancer}\n\n~nex"
+ msg = EmailMessage()
+ msg["From"] = gmail_cfg["username"]
+ msg["To"] = f"{student_number}@my.leedscitycollege.ac.uk"
+ msg["Subject"] = "Server Verification"
+ msg.set_content(text)
+
+ kwargs.setdefault(
+ "hostname", gmail_cfg["addr"]
+ )
+ kwargs.setdefault(
+ "port", gmail_cfg["port"]
+ )
+ kwargs.setdefault(
+ "use_tls", True
+ )
+ kwargs.setdefault(
+ "username", gmail_cfg["username"]
+ )
+ kwargs.setdefault(
+ "password", gmail_cfg["password"]
+ )
+ kwargs.setdefault(
+ "start_tls", not kwargs["use_tls"]
+ )
+
+ assert kwargs["start_tls"] != kwargs["use_tls"]
+
+ await smtp.send(
+ msg,
+ **kwargs
+ )
+ return code
diff --git a/utils/db.py b/utils/db.py
new file mode 100644
index 0000000..8b838ce
--- /dev/null
+++ b/utils/db.py
@@ -0,0 +1,38 @@
+import uuid
+from typing import TYPE_CHECKING
+
+import orm
+from databases import Database
+
+
+registry = orm.ModelRegistry(Database("sqlite:///main.db"))
+
+
+class VerifyCode(orm.Model):
+ registry = registry
+ tablename = "codes"
+ fields = {
+ "id": orm.Integer(primary_key=True),
+ "code": orm.String(min_length=8, max_length=64, unique=True),
+ "bind": orm.BigInteger(),
+ "student_id": orm.String(min_length=7, max_length=7)
+ }
+ if TYPE_CHECKING:
+ id: int
+ code: str
+ bind: int
+ student_id: str
+
+
+class Student(orm.Model):
+ registry = registry
+ tablename = "students"
+ fields = {
+ "entry_id": orm.UUID(primary_key=True, default=uuid.uuid4),
+ "id": orm.String(min_length=7, max_length=7, unique=True),
+ "user_id": orm.BigInteger(unique=True),
+ }
+ if TYPE_CHECKING:
+ entry_id: uuid.UUID
+ id: str
+ user_id: int