Blackify code (formatting)

This commit is contained in:
nex 2022-10-30 16:31:38 +00:00
parent c2e5e71270
commit a41670b437
11 changed files with 144 additions and 243 deletions

View file

@ -8,16 +8,13 @@ from discord.ext import commands, tasks
import config import config
from utils import Assignments, Tutors, simple_embed_paginator, get_or_none, Student, hyperlink, console from utils import Assignments, Tutors, simple_embed_paginator, get_or_none, Student, hyperlink, console
BOOL_EMOJI = { BOOL_EMOJI = {True: "\N{white heavy check mark}", False: "\N{cross mark}"}
True: "\N{white heavy check mark}",
False: "\N{cross mark}"
}
TUTOR_OPTION = discord.Option( TUTOR_OPTION = discord.Option(
str, str,
"The tutor who assigned the project", "The tutor who assigned the project",
default=None, default=None,
choices=[x.title() for x in dir(Tutors) if not x.startswith("__") and x not in ("name", "value")] choices=[x.title() for x in dir(Tutors) if not x.startswith("__") and x not in ("name", "value")],
) )
__MARK_AS_OPTION_OPTIONS = ("unfinished", "finished", "unsubmitted", "submitted") __MARK_AS_OPTION_OPTIONS = ("unfinished", "finished", "unsubmitted", "submitted")
MARK_AS_OPTION = discord.Option( MARK_AS_OPTION = discord.Option(
@ -29,7 +26,7 @@ MARK_AS_OPTION = discord.Option(
value=__MARK_AS_OPTION_OPTIONS.index(x), value=__MARK_AS_OPTION_OPTIONS.index(x),
) )
for x in __MARK_AS_OPTION_OPTIONS for x in __MARK_AS_OPTION_OPTIONS
] ],
) )
@ -39,12 +36,8 @@ class TutorSelector(discord.ui.View):
@discord.ui.select( @discord.ui.select(
placeholder="Select a tutor name", placeholder="Select a tutor name",
options=[ options=[
discord.SelectOption( discord.SelectOption(label=x.title(), value=x.upper()) for x in [y.name for y in TUTOR_OPTION.choices]
label=x.title(), ],
value=x.upper()
)
for x in [y.name for y in TUTOR_OPTION.choices]
]
) )
async def select_tutor(self, select: discord.ui.Select, interaction2: discord.Interaction): async def select_tutor(self, select: discord.ui.Select, interaction2: discord.Interaction):
await interaction2.response.defer(invisible=True) await interaction2.response.defer(invisible=True)
@ -56,13 +49,10 @@ async def assignment_autocomplete(ctx: discord.AutocompleteContext) -> list[str]
if not ctx.value: if not ctx.value:
results: list[Assignments] = await Assignments.objects.order_by("-due_by").limit(7).all() results: list[Assignments] = await Assignments.objects.order_by("-due_by").limit(7).all()
else: else:
results: list[Assignments] = await Assignments.objects.filter( results: list[Assignments] = (
title__icontains=ctx.value await Assignments.objects.filter(title__icontains=ctx.value).limit(30).order_by("-entry_id").all()
).limit(30).order_by("-entry_id").all() )
return [ return [textwrap.shorten(f"{x.entry_id}: {x.title}", 100, placeholder="...") for x in results]
textwrap.shorten(f"{x.entry_id}: {x.title}", 100, placeholder="...")
for x in results
]
# noinspection DuplicatedCode # noinspection DuplicatedCode
@ -80,16 +70,10 @@ class AssignmentsCog(commands.Cog):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
try: try:
view_command = "</{0.name} view:{0.id}>".format( view_command = "</{0.name} view:{0.id}>".format(
self.bot.get_application_command( self.bot.get_application_command("assignments", type=discord.SlashCommandGroup)
"assignments",
type=discord.SlashCommandGroup
)
) )
edit_command = "</{0.name} edit:{0.id}>".format( edit_command = "</{0.name} edit:{0.id}>".format(
self.bot.get_application_command( self.bot.get_application_command("assignments", type=discord.SlashCommandGroup)
"assignments",
type=discord.SlashCommandGroup
)
) )
except AttributeError: except AttributeError:
view_command = "`/assignments view`" view_command = "`/assignments view`"
@ -100,10 +84,12 @@ class AssignmentsCog(commands.Cog):
if not general.can_send(): if not general.can_send():
return return
msg_format = "@everyone {reminder_name} reminder for project {project_title} for **{project_tutor}**!\n" \ msg_format = (
"Run '%s {project_title}' to view information on the assignment.\n" \ "@everyone {reminder_name} reminder for project {project_title} for **{project_tutor}**!\n"
"*You can mark this assignment as complete with '%s {project_title}', which will prevent" \ "Run '%s {project_title}' to view information on the assignment.\n"
" further reminders.*" % (view_command, edit_command) "*You can mark this assignment as complete with '%s {project_title}', which will prevent"
" further reminders.*" % (view_command, edit_command)
)
now = datetime.datetime.now() now = datetime.datetime.now()
assignments: list[Assignments] = await Assignments.objects.filter(submitted=False).all() assignments: list[Assignments] = await Assignments.objects.filter(submitted=False).all()
@ -126,9 +112,9 @@ class AssignmentsCog(commands.Cog):
msg_format.format( msg_format.format(
reminder_name=reminder_name, reminder_name=reminder_name,
project_title=textwrap.shorten(assignment.title, 100, placeholder="..."), project_title=textwrap.shorten(assignment.title, 100, placeholder="..."),
project_tutor=assignment.tutor.name.title() project_tutor=assignment.tutor.name.title(),
), ),
allowed_mentions=allowed_mentions allowed_mentions=allowed_mentions,
) )
except discord.HTTPException: except discord.HTTPException:
pass pass
@ -142,9 +128,9 @@ class AssignmentsCog(commands.Cog):
msg_format.format( msg_format.format(
reminder_name=reminder_name, reminder_name=reminder_name,
project_title=textwrap.shorten(assignment.title, 100, placeholder="..."), project_title=textwrap.shorten(assignment.title, 100, placeholder="..."),
project_tutor=assignment.tutor.name.title() project_tutor=assignment.tutor.name.title(),
), ),
allowed_mentions=allowed_mentions allowed_mentions=allowed_mentions,
) )
except discord.HTTPException: except discord.HTTPException:
pass pass
@ -156,7 +142,7 @@ class AssignmentsCog(commands.Cog):
embed = discord.Embed( embed = discord.Embed(
title=f"Assignment #{assignment.entry_id}", title=f"Assignment #{assignment.entry_id}",
description=f"**Title:**\n>>> {assignment.title}", description=f"**Title:**\n>>> {assignment.title}",
colour=discord.Colour.random() colour=discord.Colour.random(),
) )
if assignment.classroom: if assignment.classroom:
@ -172,37 +158,29 @@ class AssignmentsCog(commands.Cog):
embed.add_field(name="Classroom URL:", value=classroom, inline=False), embed.add_field(name="Classroom URL:", value=classroom, inline=False),
embed.add_field(name="Shared Document URL:", value=shared_doc) embed.add_field(name="Shared Document URL:", value=shared_doc)
embed.add_field(name="Tutor:", value=assignment.tutor.name.title(), inline=False) embed.add_field(name="Tutor:", value=assignment.tutor.name.title(), inline=False)
user_id = getattr(assignment.created_by, 'user_id', assignment.entry_id) user_id = getattr(assignment.created_by, "user_id", assignment.entry_id)
embed.add_field( embed.add_field(name="Created:", value=f"<t:{assignment.created_at:.0f}:R> by <@{user_id}>", inline=False)
name="Created:",
value=f"<t:{assignment.created_at:.0f}:R> by <@{user_id}>",
inline=False
)
embed.add_field( embed.add_field(
name="Due:", name="Due:",
value=f"<t:{assignment.due_by:.0f}:R> " value=f"<t:{assignment.due_by:.0f}:R> "
f"(finished: {BOOL_EMOJI[assignment.finished]} | Submitted: {BOOL_EMOJI[assignment.submitted]})", f"(finished: {BOOL_EMOJI[assignment.finished]} | Submitted: {BOOL_EMOJI[assignment.submitted]})",
inline=False inline=False,
) )
if assignment.reminders: if assignment.reminders:
embed.set_footer(text="Reminders sent: " + ", ".join(assignment.reminders)) embed.set_footer(text="Reminders sent: " + ", ".join(assignment.reminders))
return embed return embed
assignments_command = discord.SlashCommandGroup( assignments_command = discord.SlashCommandGroup("assignments", "Assignment/project management", guild_only=True)
"assignments",
"Assignment/project management",
guild_only=True
)
@assignments_command.command(name="list") @assignments_command.command(name="list")
async def list_assignments( async def list_assignments(
self, self,
ctx: discord.ApplicationContext, ctx: discord.ApplicationContext,
limit: int = 20, limit: int = 20,
upcoming_only: bool = True, upcoming_only: bool = True,
tutor_name: TUTOR_OPTION = None, tutor_name: TUTOR_OPTION = None,
unfinished_only: bool = False, unfinished_only: bool = False,
unsubmitted_only: bool = False unsubmitted_only: bool = False,
): ):
"""Lists assignments.""" """Lists assignments."""
tutor_name: Optional[str] tutor_name: Optional[str]
@ -231,13 +209,9 @@ class AssignmentsCog(commands.Cog):
) )
embeds = simple_embed_paginator(lines, assert_ten=True, colour=ctx.author.colour) embeds = simple_embed_paginator(lines, assert_ten=True, colour=ctx.author.colour)
embeds = embeds or [ embeds = embeds or [discord.Embed(description="No projects match the provided criteria.")]
discord.Embed(description="No projects match the provided criteria.")
]
return await ctx.respond( return await ctx.respond(embeds=embeds)
embeds=embeds
)
@assignments_command.command(name="add") @assignments_command.command(name="add")
async def create_assignment(self, ctx: discord.ApplicationContext): async def create_assignment(self, ctx: discord.ApplicationContext):
@ -255,7 +229,7 @@ class AssignmentsCog(commands.Cog):
"classroom": None, "classroom": None,
"shared_doc": None, "shared_doc": None,
"due_by": None, "due_by": None,
"tutor": None "tutor": None,
} }
super().__init__( super().__init__(
discord.ui.InputText( discord.ui.InputText(
@ -263,7 +237,7 @@ class AssignmentsCog(commands.Cog):
label="Assignment Title", label="Assignment Title",
min_length=2, min_length=2,
max_length=2000, max_length=2000,
value=self.create_kwargs["title"] value=self.create_kwargs["title"],
), ),
discord.ui.InputText( discord.ui.InputText(
custom_id="classroom", custom_id="classroom",
@ -271,7 +245,7 @@ class AssignmentsCog(commands.Cog):
max_length=4000, max_length=4000,
required=False, required=False,
placeholder="Optional, can be added later.", placeholder="Optional, can be added later.",
value=self.create_kwargs["classroom"] value=self.create_kwargs["classroom"],
), ),
discord.ui.InputText( discord.ui.InputText(
custom_id="shared_doc", custom_id="shared_doc",
@ -279,7 +253,7 @@ class AssignmentsCog(commands.Cog):
max_length=4000, max_length=4000,
required=False, required=False,
placeholder="Google docs, slides, powerpoint, etc. Optional.", placeholder="Google docs, slides, powerpoint, etc. Optional.",
value=self.create_kwargs["shared_doc"] value=self.create_kwargs["shared_doc"],
), ),
discord.ui.InputText( discord.ui.InputText(
custom_id="due_by", custom_id="due_by",
@ -289,11 +263,12 @@ class AssignmentsCog(commands.Cog):
placeholder="dd/mm/yy hh:mm".upper(), placeholder="dd/mm/yy hh:mm".upper(),
value=( value=(
self.create_kwargs["due_by"].strftime("%d/%m/%y %H:%M") self.create_kwargs["due_by"].strftime("%d/%m/%y %H:%M")
if self.create_kwargs["due_by"] else None if self.create_kwargs["due_by"]
) else None
),
), ),
title="Add an assignment", title="Add an assignment",
timeout=300 timeout=300,
) )
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
@ -304,9 +279,10 @@ class AssignmentsCog(commands.Cog):
try: try:
self.create_kwargs["due_by"] = datetime.datetime.strptime( self.create_kwargs["due_by"] = datetime.datetime.strptime(
self.children[3].value, self.children[3].value,
"%d/%m/%y %H:%M" if len(self.children[3].value) == 14 else "%d/%m/%Y %H:%M" "%d/%m/%y %H:%M" if len(self.children[3].value) == 14 else "%d/%m/%Y %H:%M",
) )
except ValueError: except ValueError:
class TryAgainView(discord.ui.View): class TryAgainView(discord.ui.View):
def __init__(self, kw): def __init__(self, kw):
self._mod = None self._mod = None
@ -327,10 +303,7 @@ class AssignmentsCog(commands.Cog):
self.stop() self.stop()
v = TryAgainView(self.create_kwargs) v = TryAgainView(self.create_kwargs)
msg = await interaction.followup.send( msg = await interaction.followup.send("\N{cross mark} Failed to parse date - try again?", view=v)
"\N{cross mark} Failed to parse date - try again?",
view=v
)
await v.wait() await v.wait()
if v.modal: if v.modal:
self.create_kwargs = v.modal.create_kwargs self.create_kwargs = v.modal.create_kwargs
@ -338,10 +311,7 @@ class AssignmentsCog(commands.Cog):
return return
else: else:
view = TutorSelector() view = TutorSelector()
msg = await interaction.followup.send( msg = await interaction.followup.send("Which tutor assigned this project?", view=view)
"Which tutor assigned this project?",
view=view
)
await view.wait() await view.wait()
self.create_kwargs["tutor"] = view.value self.create_kwargs["tutor"] = view.value
@ -353,25 +323,18 @@ class AssignmentsCog(commands.Cog):
await modal.wait() await modal.wait()
if not modal.msg: if not modal.msg:
return return
await modal.msg.edit( await modal.msg.edit(content="Creating assignment...", view=None)
content="Creating assignment...",
view=None
)
try: try:
modal.create_kwargs["due_by"] = modal.create_kwargs["due_by"].timestamp() modal.create_kwargs["due_by"] = modal.create_kwargs["due_by"].timestamp()
await Assignments.objects.create(**modal.create_kwargs) await Assignments.objects.create(**modal.create_kwargs)
except sqlite3.Error as e: except sqlite3.Error as e:
return await modal.msg.edit(content="SQL Error: %s.\nAssignment not saved." % e) return await modal.msg.edit(content="SQL Error: %s.\nAssignment not saved." % e)
else: else:
return await modal.msg.edit( return await modal.msg.edit(content=f"\N{white heavy check mark} Created assignment!")
content=f"\N{white heavy check mark} Created assignment!"
)
@assignments_command.command(name="view") @assignments_command.command(name="view")
async def get_assignment( async def get_assignment(
self, self, ctx: discord.ApplicationContext, title: discord.Option(str, autocomplete=assignment_autocomplete)
ctx: discord.ApplicationContext,
title: discord.Option(str, autocomplete=assignment_autocomplete)
): ):
"""Views an assignment's details""" """Views an assignment's details"""
try: try:
@ -389,9 +352,7 @@ class AssignmentsCog(commands.Cog):
@assignments_command.command(name="edit") @assignments_command.command(name="edit")
async def edit_assignment( async def edit_assignment(
self, self, ctx: discord.ApplicationContext, title: discord.Option(str, autocomplete=assignment_autocomplete)
ctx: discord.ApplicationContext,
title: discord.Option(str, autocomplete=assignment_autocomplete)
): ):
"""Edits an assignment""" """Edits an assignment"""
try: try:
@ -432,15 +393,14 @@ class AssignmentsCog(commands.Cog):
min_length=2, min_length=2,
max_length=4000, max_length=4000,
), ),
title="Update assignment title" title="Update assignment title",
) )
async def callback(self, _interaction: discord.Interaction): async def callback(self, _interaction: discord.Interaction):
await _interaction.response.defer() await _interaction.response.defer()
await assignment.update(title=self.children[0].value) await assignment.update(title=self.children[0].value)
await _interaction.followup.send( await _interaction.followup.send(
"\N{white heavy check mark} Changed assignment title!", "\N{white heavy check mark} Changed assignment title!", delete_after=5
delete_after=5
) )
self.stop() self.stop()
@ -460,7 +420,7 @@ class AssignmentsCog(commands.Cog):
required=False, required=False,
max_length=4000, max_length=4000,
), ),
title="Update Classroom url" title="Update Classroom url",
) )
async def callback(self, _interaction: discord.Interaction): async def callback(self, _interaction: discord.Interaction):
@ -468,8 +428,7 @@ class AssignmentsCog(commands.Cog):
try: try:
await assignment.update(classroom=self.children[0].value) await assignment.update(classroom=self.children[0].value)
await _interaction.followup.send( await _interaction.followup.send(
"\N{white heavy check mark} Changed classroom URL!", "\N{white heavy check mark} Changed classroom URL!", delete_after=5
delete_after=5
) )
except sqlite3.Error: except sqlite3.Error:
await _interaction.followup.send( await _interaction.followup.send(
@ -494,7 +453,7 @@ class AssignmentsCog(commands.Cog):
required=False, required=False,
max_length=4000, max_length=4000,
), ),
title="Update shared document url" title="Update shared document url",
) )
async def callback(self, _interaction: discord.Interaction): async def callback(self, _interaction: discord.Interaction):
@ -502,8 +461,7 @@ class AssignmentsCog(commands.Cog):
try: try:
await assignment.update(shared_doc=self.children[0].value) await assignment.update(shared_doc=self.children[0].value)
await _interaction.followup.send( await _interaction.followup.send(
"\N{white heavy check mark} Changed shared doc URL!", "\N{white heavy check mark} Changed shared doc URL!", delete_after=5
delete_after=5
) )
except sqlite3.Error: except sqlite3.Error:
await _interaction.followup.send( await _interaction.followup.send(
@ -521,14 +479,12 @@ class AssignmentsCog(commands.Cog):
await interaction.response.defer() await interaction.response.defer()
view = TutorSelector() view = TutorSelector()
msg: discord.WebhookMessage = await interaction.followup.send( msg: discord.WebhookMessage = await interaction.followup.send(
"Which tutor assigned this project?", "Which tutor assigned this project?", view=view
view=view
) )
await view.wait() await view.wait()
await assignment.update(tutor=view.value) await assignment.update(tutor=view.value)
await msg.edit( await msg.edit(
content=f"\N{white heavy check mark} Changed tutor to {view.value.name.title()}", content=f"\N{white heavy check mark} Changed tutor to {view.value.name.title()}", view=None
view=None
) )
await msg.delete(delay=5) await msg.delete(delay=5)
await self.update_display(interaction) await self.update_display(interaction)
@ -544,9 +500,9 @@ class AssignmentsCog(commands.Cog):
placeholder=self.date.strftime("%d/%m/%y %H:%M"), placeholder=self.date.strftime("%d/%m/%y %H:%M"),
value=self.date.strftime("%d/%m/%y %H:%M"), value=self.date.strftime("%d/%m/%y %H:%M"),
min_length=14, min_length=14,
max_length=16 max_length=16,
), ),
title="Change due by date" title="Change due by date",
) )
async def callback(self, _interaction: discord.Interaction): async def callback(self, _interaction: discord.Interaction):
@ -554,7 +510,7 @@ class AssignmentsCog(commands.Cog):
try: try:
new = datetime.datetime.strptime( new = datetime.datetime.strptime(
self.children[1].value, self.children[1].value,
"%d/%m/%y %H:%M" if len(self.children[1].value) == 14 else "%d/%m/%Y %H:%M" "%d/%m/%y %H:%M" if len(self.children[1].value) == 14 else "%d/%m/%Y %H:%M",
) )
except ValueError: except ValueError:
await _interaction.followup.send( await _interaction.followup.send(
@ -566,15 +522,13 @@ class AssignmentsCog(commands.Cog):
try: try:
await assignment.update(due_by=new.timestamp(), reminders=[]) await assignment.update(due_by=new.timestamp(), reminders=[])
await _interaction.followup.send( await _interaction.followup.send(
"\N{white heavy check mark} Changed due by date & reset reminders.", "\N{white heavy check mark} Changed due by date & reset reminders.", delete_after=5
delete_after=5
) )
except sqlite3.Error: except sqlite3.Error:
await _interaction.followup.send( await _interaction.followup.send("\N{cross mark} Failed to apply changes.")
"\N{cross mark} Failed to apply changes."
)
finally: finally:
self.stop() self.stop()
await interaction.response.send_modal(UpdateDateModal()) await interaction.response.send_modal(UpdateDateModal())
await self.update_display(interaction) await self.update_display(interaction)
@ -599,7 +553,7 @@ class AssignmentsCog(commands.Cog):
if assignment.finished is False and assignment.submitted is False: if assignment.finished is False and assignment.submitted is False:
return await interaction.followup.send( return await interaction.followup.send(
"\N{cross mark} You cannot mark an assignment as submitted if it is not marked as complete!", "\N{cross mark} You cannot mark an assignment as submitted if it is not marked as complete!",
delete_after=10 delete_after=10,
) )
await assignment.update(submitted=not assignment.submitted) await assignment.update(submitted=not assignment.submitted)
await self.update_display(interaction) await self.update_display(interaction)
@ -607,7 +561,7 @@ class AssignmentsCog(commands.Cog):
"\N{white heavy check mark} Assignment is now marked as {}submitted.".format( "\N{white heavy check mark} Assignment is now marked as {}submitted.".format(
"in" if assignment.submitted is False else "" "in" if assignment.submitted is False else ""
), ),
delete_after=5 delete_after=5,
) )
@discord.ui.button(label="Save & Exit") @discord.ui.button(label="Save & Exit")
@ -621,8 +575,7 @@ class AssignmentsCog(commands.Cog):
await interaction.response.defer(ephemeral=True) await interaction.response.defer(ephemeral=True)
await assignment.created_by.load() await assignment.created_by.load()
await interaction.followup.send( await interaction.followup.send(
embed=AssignmentsCog.generate_assignment_embed(assignment), embed=AssignmentsCog.generate_assignment_embed(assignment), ephemeral=True
ephemeral=True
) )
await self.update_display(interaction) await self.update_display(interaction)
@ -630,9 +583,7 @@ class AssignmentsCog(commands.Cog):
@assignments_command.command(name="remove") @assignments_command.command(name="remove")
async def remove_assignment( async def remove_assignment(
self, self, ctx: discord.ApplicationContext, title: discord.Option(str, autocomplete=assignment_autocomplete)
ctx: discord.ApplicationContext,
title: discord.Option(str, autocomplete=assignment_autocomplete)
): ):
"""Edits an assignment""" """Edits an assignment"""
try: try:

View file

@ -37,11 +37,7 @@ class Events(commands.Cog):
if self.bot.activity.name == text: if self.bot.activity.name == text:
return return
await self.bot.change_presence( await self.bot.change_presence(
activity=discord.Activity( activity=discord.Activity(name=text, type=discord.ActivityType.playing), status=status
name=text,
type=discord.ActivityType.playing
),
status=status
) )
else: else:
await self.bot.change_presence() await self.bot.change_presence()
@ -71,9 +67,7 @@ class Events(commands.Cog):
channel: discord.TextChannel = discord.utils.get(member.guild.text_channels, name="general") channel: discord.TextChannel = discord.utils.get(member.guild.text_channels, name="general")
if channel and channel.can_send(): if channel and channel.can_send():
await channel.send( await channel.send(f"{LTR} {member.mention} {f'({student.id})' if student else '(pending verification)'}")
f"{LTR} {member.mention} {f'({student.id})' if student else '(pending verification)'}"
)
@commands.Cog.listener() @commands.Cog.listener()
async def on_member_remove(self, member: discord.Member): async def on_member_remove(self, member: discord.Member):
@ -83,9 +77,7 @@ class Events(commands.Cog):
student: Optional[Student] = await get_or_none(Student, user_id=member.id) student: Optional[Student] = await get_or_none(Student, user_id=member.id)
channel: discord.TextChannel = discord.utils.get(member.guild.text_channels, name="general") channel: discord.TextChannel = discord.utils.get(member.guild.text_channels, name="general")
if channel and channel.can_send(): if channel and channel.can_send():
await channel.send( await channel.send(f"{RTL} {member.mention} {f'({student.id})' if student else '(pending verification)'}")
f"{RTL} {member.mention} {f'({student.id})' if student else '(pending verification)'}"
)
@commands.Cog.listener() @commands.Cog.listener()
async def on_message(self, message: discord.Message): async def on_message(self, message: discord.Message):

View file

@ -26,8 +26,7 @@ class Mod(commands.Cog):
await member.ban(reason=f"Banned ID {ban.student_id} by {ctx.author}") await member.ban(reason=f"Banned ID {ban.student_id} by {ctx.author}")
return await ctx.respond( return await ctx.respond(
f"\N{white heavy check mark} Banned {ban.student_id} (and {member.mention})", f"\N{white heavy check mark} Banned {ban.student_id} (and {member.mention})", ephemeral=True
ephemeral=True
) )
@commands.slash_command(name="unban-student-number") @commands.slash_command(name="unban-student-number")
@ -45,18 +44,13 @@ class Mod(commands.Cog):
return await ctx.respond(f"\N{white heavy check mark} Unbanned {student_id}. No user to unban.") return await ctx.respond(f"\N{white heavy check mark} Unbanned {student_id}. No user to unban.")
else: else:
try: try:
await ctx.guild.unban( await ctx.guild.unban(discord.Object(user_id), reason=f"Unbanned by {ctx.author}")
discord.Object(user_id),
reason=f"Unbanned by {ctx.author}"
)
except discord.HTTPException as e: except discord.HTTPException as e:
return await ctx.respond( return await ctx.respond(
f"\N{white heavy check mark} Unbanned {student_id}. Failed to unban {user_id} - HTTP {e.status}." f"\N{white heavy check mark} Unbanned {student_id}. Failed to unban {user_id} - HTTP {e.status}."
) )
else: else:
return await ctx.respond( return await ctx.respond(f"\N{white heavy check mark} Unbanned {student_id}. Unbanned {user_id}.")
f"\N{white heavy check mark} Unbanned {student_id}. Unbanned {user_id}."
)
def setup(bot): def setup(bot):

View file

@ -32,11 +32,7 @@ class TimeTableCog(commands.Cog):
start_date = datetime.strptime(dates["start"], "%d/%m/%Y") start_date = datetime.strptime(dates["start"], "%d/%m/%Y")
end_date = datetime.strptime(dates["end"], "%d/%m/%Y") end_date = datetime.strptime(dates["end"], "%d/%m/%Y")
if date.timestamp() <= end_date.timestamp() and date.timestamp() >= start_date.timestamp(): if date.timestamp() <= end_date.timestamp() and date.timestamp() >= start_date.timestamp():
return { return {"name": name, "start": start_date, "end": end_date}
"name": name,
"start": start_date,
"end": end_date
}
def cog_unload(self): def cog_unload(self):
self.update_status.stop() self.update_status.stop()
@ -92,7 +88,9 @@ class TimeTableCog(commands.Cog):
if lesson is None: if lesson is None:
# Loop until we find the next day when it isn't the weekend, and we aren't on break. # Loop until we find the next day when it isn't the weekend, and we aren't on break.
next_available_date = date.replace(hour=0, minute=0, second=0) next_available_date = date.replace(hour=0, minute=0, second=0)
while self.are_on_break(next_available_date) or not self.timetable.get(next_available_date.strftime("%A").lower()): while self.are_on_break(next_available_date) or not self.timetable.get(
next_available_date.strftime("%A").lower()
):
next_available_date += timedelta(days=1) next_available_date += timedelta(days=1)
if next_available_date.year >= 2024: if next_available_date.year >= 2024:
raise RuntimeError("Failed to fetch absolute next lesson") raise RuntimeError("Failed to fetch absolute next lesson")
@ -103,60 +101,60 @@ class TimeTableCog(commands.Cog):
return lesson return lesson
async def update_timetable_message( async def update_timetable_message(
self, self,
message: Union[discord.Message, discord.ApplicationContext], message: Union[discord.Message, discord.ApplicationContext],
date: datetime = None, date: datetime = None,
*, *,
no_prefix: bool = False, no_prefix: bool = False,
): ):
date = date or datetime.now() date = date or datetime.now()
_break = self.are_on_break(date) _break = self.are_on_break(date)
if _break: if _break:
next_lesson = self.next_lesson(_break["end"] + timedelta(days=1, hours=7)) next_lesson = self.next_lesson(_break["end"] + timedelta(days=1, hours=7))
next_lesson = next_lesson or { next_lesson = next_lesson or {"name": "Unknown", "tutor": "Unknown", "room": "Unknown"}
"name": "Unknown", text = (
"tutor": "Unknown", "[tt] On break {!r} from {} until {}. Break ends {}, and the first lesson back is "
"room": "Unknown" "{lesson[name]!r} with {lesson[tutor]} in {lesson[room]}.".format(
} _break["name"],
text = "[tt] On break {!r} from {} until {}. Break ends {}, and the first lesson back is " \ discord.utils.format_dt(_break["start"], "d"),
"{lesson[name]!r} with {lesson[tutor]} in {lesson[room]}.".format( discord.utils.format_dt(_break["end"], "d"),
_break["name"], discord.utils.format_dt(_break["end"], "R"),
discord.utils.format_dt(_break["start"], "d"), lesson=next_lesson,
discord.utils.format_dt(_break["end"], "d"), )
discord.utils.format_dt(_break["end"], "R"),
lesson=next_lesson
) )
else: else:
lesson = self.current_lesson(date) lesson = self.current_lesson(date)
if not lesson: if not lesson:
next_lesson = self.next_lesson(date) next_lesson = self.next_lesson(date)
if not next_lesson: if not next_lesson:
next_lesson = await asyncio.to_thread( next_lesson = await asyncio.to_thread(self.absolute_next_lesson, new_method=True)
self.absolute_next_lesson,
new_method=True
)
next_lesson = next_lesson or { next_lesson = next_lesson or {
"name": "unknown", "name": "unknown",
"tutor": "unknown", "tutor": "unknown",
"room": "unknown", "room": "unknown",
"start_datetime": datetime.max "start_datetime": datetime.max,
} }
text = "[tt] No more lessons today!\n" \ text = (
f"[tt] Next Lesson: {next_lesson['name']!r} with {next_lesson['tutor']} in " \ "[tt] No more lessons today!\n"
f"{next_lesson['room']} - " \ f"[tt] Next Lesson: {next_lesson['name']!r} with {next_lesson['tutor']} in "
f"Starts {discord.utils.format_dt(next_lesson['start_datetime'], 'R')}" f"{next_lesson['room']} - "
f"Starts {discord.utils.format_dt(next_lesson['start_datetime'], 'R')}"
)
else: else:
text = f"[tt] Next Lesson: {next_lesson['name']!r} with {next_lesson['tutor']} in " \ text = (
f"{next_lesson['room']} - Starts {discord.utils.format_dt(next_lesson['start_datetime'], 'R')}" f"[tt] Next Lesson: {next_lesson['name']!r} with {next_lesson['tutor']} in "
f"{next_lesson['room']} - Starts {discord.utils.format_dt(next_lesson['start_datetime'], 'R')}"
)
else: else:
text = f"[tt] Current Lesson: {lesson['name']!r} with {lesson['tutor']} in {lesson['room']} - " \ text = (
f"ends {discord.utils.format_dt(lesson['end_datetime'], 'R')}" f"[tt] Current Lesson: {lesson['name']!r} with {lesson['tutor']} in {lesson['room']} - "
f"ends {discord.utils.format_dt(lesson['end_datetime'], 'R')}"
)
next_lesson = self.next_lesson(date) next_lesson = self.next_lesson(date)
if next_lesson: if next_lesson:
text += "\n[tt] Next lesson: {0[name]!r} with {0[tutor]} in {0[room]} - starts {1}".format( text += "\n[tt] Next lesson: {0[name]!r} with {0[tutor]} in {0[room]} - starts {1}".format(
next_lesson, next_lesson, discord.utils.format_dt(next_lesson["start_datetime"], "R")
discord.utils.format_dt(next_lesson["start_datetime"], 'R')
) )
if no_prefix: if no_prefix:
@ -215,9 +213,11 @@ class TimeTableCog(commands.Cog):
for lesson in lessons: for lesson in lessons:
start_datetime = date.replace(hour=lesson["start"][0], minute=lesson["start"][1]) start_datetime = date.replace(hour=lesson["start"][0], minute=lesson["start"][1])
end_datetime = date.replace(hour=lesson["end"][0], minute=lesson["end"][1]) end_datetime = date.replace(hour=lesson["end"][0], minute=lesson["end"][1])
text = f"{discord.utils.format_dt(start_datetime, 't')} to {discord.utils.format_dt(end_datetime, 't')}" \ text = (
f":\n> Lesson Name: {lesson['name']!r}\n" \ f"{discord.utils.format_dt(start_datetime, 't')} to {discord.utils.format_dt(end_datetime, 't')}"
f"> Tutor: **{lesson['tutor']}**\n> Room: `{lesson['room']}`" f":\n> Lesson Name: {lesson['name']!r}\n"
f"> Tutor: **{lesson['tutor']}**\n> Room: `{lesson['room']}`"
)
blocks.append(text) blocks.append(text)
await ctx.respond("\n\n".join(blocks)) await ctx.respond("\n\n".join(blocks))

View file

@ -63,13 +63,13 @@ class VerifyCog(commands.Cog):
return await ctx.respond( return await ctx.respond(
f"{member.mention}'s B number is saved as {student.id!r}.", f"{member.mention}'s B number is saved as {student.id!r}.",
ephemeral=True, ephemeral=True,
allowed_mentions=discord.AllowedMentions.none() allowed_mentions=discord.AllowedMentions.none(),
) )
except orm.NoMatch: except orm.NoMatch:
return await ctx.respond( return await ctx.respond(
f"{member.mention} has no saved B number.", f"{member.mention} has no saved B number.",
ephemeral=True, ephemeral=True,
allowed_mentions=discord.AllowedMentions.none() allowed_mentions=discord.AllowedMentions.none(),
) )
@commands.command(name="rebind") @commands.command(name="rebind")

11
main.py
View file

@ -8,17 +8,10 @@ bot = commands.Bot(
commands.when_mentioned_or("h!"), commands.when_mentioned_or("h!"),
debug_guilds=config.guilds, debug_guilds=config.guilds,
allowed_mentions=discord.AllowedMentions.none(), allowed_mentions=discord.AllowedMentions.none(),
intents=discord.Intents.default() + discord.Intents.members intents=discord.Intents.default() + discord.Intents.members,
) )
extensions = [ extensions = ["jishaku", "cogs.verify", "cogs.mod", "cogs.events", "cogs.assignments", "cogs.timetable"]
"jishaku",
"cogs.verify",
"cogs.mod",
"cogs.events",
"cogs.assignments",
"cogs.timetable"
]
for ext in extensions: for ext in extensions:
bot.load_extension(ext) bot.load_extension(ext)
console.log(f"Loaded extension [green]{ext}") console.log(f"Loaded extension [green]{ext}")

View file

@ -16,11 +16,10 @@ def is_sane_time(time: list[int, int]) -> Union[bool, AssertionError]:
assert minute in range(0, 60), "Invalid minute range - must be between (inclusive) 0 & 59" assert minute in range(0, 60), "Invalid minute range - must be between (inclusive) 0 & 59"
if minute % 15 != 0: if minute % 15 != 0:
warnings.warn( warnings.warn(
UserWarning( UserWarning("Time '%s:%s' is probably not a valid timetable time, as lessons are every 15 minutes.")
"Time '%s:%s' is probably not a valid timetable time, as lessons are every 15 minutes."
)
) )
return True return True
try: try:
return inner() return inner()
except AssertionError as e: except AssertionError as e:

View file

@ -8,11 +8,7 @@ from .views import *
def simple_embed_paginator( def simple_embed_paginator(
lines: list[str], lines: list[str], *, assert_ten: bool = False, empty_is_none: bool = True, **kwargs
*,
assert_ten: bool = False,
empty_is_none: bool = True,
**kwargs
) -> Optional[list[discord.Embed]]: ) -> Optional[list[discord.Embed]]:
"""Paginates x lines into x embeds.""" """Paginates x lines into x embeds."""
if not lines and empty_is_none is True: if not lines and empty_is_none is True:

View file

@ -19,6 +19,7 @@ class _FakeUser:
def __str__(self): def __str__(self):
import random import random
return f"{random.choice(self.names)}#{str(random.randint(1, 9999)).zfill(4)}" return f"{random.choice(self.names)}#{str(random.randint(1, 9999)).zfill(4)}"

View file

@ -21,17 +21,9 @@ class Tutors(IntEnum):
os.chdir(Path(__file__).parent.parent) os.chdir(Path(__file__).parent.parent)
__all__ = [ __all__ = ["registry", "get_or_none", "VerifyCode", "Student", "BannedStudentID", "Assignments", "Tutors"]
"registry",
"get_or_none",
"VerifyCode",
"Student",
"BannedStudentID",
"Assignments",
"Tutors"
]
T = TypeVar('T') T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True) T_co = TypeVar("T_co", covariant=True)
@ -83,7 +75,7 @@ class BannedStudentID(orm.Model):
"entry_id": orm.UUID(primary_key=True, default=uuid.uuid4), "entry_id": orm.UUID(primary_key=True, default=uuid.uuid4),
"student_id": orm.String(min_length=7, max_length=7, unique=True), "student_id": orm.String(min_length=7, max_length=7, unique=True),
"associated_account": orm.BigInteger(default=None), "associated_account": orm.BigInteger(default=None),
"banned_at_timestamp": orm.Float(default=lambda: datetime.datetime.utcnow().timestamp()) "banned_at_timestamp": orm.Float(default=lambda: datetime.datetime.utcnow().timestamp()),
} }
if TYPE_CHECKING: if TYPE_CHECKING:
entry_id: uuid.UUID entry_id: uuid.UUID
@ -105,7 +97,7 @@ class Assignments(orm.Model):
"tutor": orm.Enum(Tutors), "tutor": orm.Enum(Tutors),
"reminders": orm.JSON(default=[]), "reminders": orm.JSON(default=[]),
"finished": orm.Boolean(default=False), "finished": orm.Boolean(default=False),
"submitted": orm.Boolean(default=False) "submitted": orm.Boolean(default=False),
} }
if TYPE_CHECKING: if TYPE_CHECKING:
entry_id: int entry_id: int

View file

@ -12,11 +12,7 @@ class VerifyView(discord.ui.View):
self.ctx = ctx self.ctx = ctx
super().__init__(timeout=300, disable_on_timeout=True) super().__init__(timeout=300, disable_on_timeout=True)
@discord.ui.button( @discord.ui.button(label="I have a verification code!", emoji="\U0001f4e7", custom_id="have")
label="I have a verification code!",
emoji="\U0001f4e7",
custom_id="have"
)
async def have(self, _, interaction1: discord.Interaction): async def have(self, _, interaction1: discord.Interaction):
class Modal(discord.ui.Modal): class Modal(discord.ui.Modal):
def __init__(self): def __init__(self):
@ -25,8 +21,8 @@ class VerifyView(discord.ui.View):
custom_id="code", custom_id="code",
label="Verification Code", label="Verification Code",
placeholder="e.g: " + secrets.token_hex(TOKEN_LENGTH), placeholder="e.g: " + secrets.token_hex(TOKEN_LENGTH),
min_length=TOKEN_LENGTH*2, min_length=TOKEN_LENGTH * 2,
max_length=TOKEN_LENGTH*2, max_length=TOKEN_LENGTH * 2,
), ),
title="Enter the verification code in your inbox", title="Enter the verification code in your inbox",
) )
@ -51,7 +47,7 @@ class VerifyView(discord.ui.View):
self.stop() self.stop()
return await interaction.user.ban( return await interaction.user.ban(
reason=f"Attempted to verify with banned student ID {ban.student_id}" reason=f"Attempted to verify with banned student ID {ban.student_id}"
f" (originally associated with account {ban.associated_account})" f" (originally associated with account {ban.associated_account})"
) )
await Student.objects.create(id=existing.student_id, user_id=interaction.user.id) await Student.objects.create(id=existing.student_id, user_id=interaction.user.id)
await existing.delete() await existing.delete()
@ -62,8 +58,7 @@ class VerifyView(discord.ui.View):
console.log(f"[green]{interaction.user} verified ({interaction.user.id}/{existing.student_id})") console.log(f"[green]{interaction.user} verified ({interaction.user.id}/{existing.student_id})")
self.stop() self.stop()
return await interaction.followup.send( return await interaction.followup.send(
"\N{white heavy check mark} Verification complete!", "\N{white heavy check mark} Verification complete!", ephemeral=True
ephemeral=True
) )
await interaction1.response.send_modal(Modal()) await interaction1.response.send_modal(Modal())
@ -71,10 +66,7 @@ class VerifyView(discord.ui.View):
await interaction1.edit_original_response(view=self) await interaction1.edit_original_response(view=self)
await interaction1.delete_original_response(delay=1) await interaction1.delete_original_response(delay=1)
@discord.ui.button( @discord.ui.button(label="Send me a verification code.", emoji="\U0001f4e5")
label="Send me a verification code.",
emoji="\U0001f4e5"
)
async def send(self, btn: discord.ui.Button, interaction1: discord.Interaction): async def send(self, btn: discord.ui.Button, interaction1: discord.Interaction):
class Modal(discord.ui.Modal): class Modal(discord.ui.Modal):
def __init__(self): def __init__(self):
@ -87,7 +79,7 @@ class VerifyView(discord.ui.View):
max_length=7, max_length=7,
), ),
title="Enter your student ID number", title="Enter your student ID number",
timeout=120 timeout=120,
) )
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
@ -98,17 +90,13 @@ class VerifyView(discord.ui.View):
if not re.match(r"^B\d{6}$", st): if not re.match(r"^B\d{6}$", st):
btn.disabled = False btn.disabled = False
return await interaction.followup.send( return await interaction.followup.send("\N{cross mark} Invalid student ID.", delete_after=60)
"\N{cross mark} Invalid student ID.",
delete_after=60
)
ex = await get_or_none(Student, id=st) ex = await get_or_none(Student, id=st)
if ex: if ex:
btn.disabled = False btn.disabled = False
return await interaction.followup.send( return await interaction.followup.send(
"\N{cross mark} Student ID is already associated.", "\N{cross mark} Student ID is already associated.", delete_after=60
delete_after=60
) )
try: try:
@ -117,8 +105,9 @@ class VerifyView(discord.ui.View):
return await interaction.followup.send(f"\N{cross mark} Failed to send email - {e}. Try again?") return await interaction.followup.send(f"\N{cross mark} Failed to send email - {e}. Try again?")
console.log(f"Sending verification email to {interaction.user} ({interaction.user.id}/{st})...") console.log(f"Sending verification email to {interaction.user} ({interaction.user.id}/{st})...")
__code = await VerifyCode.objects.create(code=_code, bind=interaction.id, student_id=st) __code = await VerifyCode.objects.create(code=_code, bind=interaction.id, student_id=st)
console.log(f"[green]Sent verification email to {interaction.user} ({interaction.user.id}/{st}): " console.log(
f"{_code!r}") f"[green]Sent verification email to {interaction.user} ({interaction.user.id}/{st}): " f"{_code!r}"
)
await interaction.followup.send( await interaction.followup.send(
"\N{white heavy check mark} Verification email sent to your college email " "\N{white heavy check mark} Verification email sent to your college email "
f"({st}@my.leedscitycollege.ac.uk)\n" f"({st}@my.leedscitycollege.ac.uk)\n"
@ -140,14 +129,9 @@ class VerifyView(discord.ui.View):
await modal.wait() await modal.wait()
await interaction1.edit_original_response(view=self) await interaction1.edit_original_response(view=self)
@discord.ui.button( @discord.ui.button(label="Why do I need a verification code?", emoji="\U0001f616")
label="Why do I need a verification code?",
emoji="\U0001f616"
)
async def why(self, _, interaction: discord.Interaction): async def why(self, _, interaction: discord.Interaction):
await interaction.response.defer( await interaction.response.defer(ephemeral=True)
ephemeral=True
)
await interaction.followup.send( await interaction.followup.send(
"In order to access this server, you need to enter your student ID.\n" "In order to access this server, you need to enter your student ID.\n"
"We require this to make sure only **students** in our course can access the server.\n" "We require this to make sure only **students** in our course can access the server.\n"
@ -155,6 +139,5 @@ class VerifyView(discord.ui.View):
"This is not invading your privacy, your B number is publicly visible, as it is the start of your email," "This is not invading your privacy, your B number is publicly visible, as it is the start of your email,"
" plus can be found on google chat.", " plus can be found on google chat.",
ephemeral=True, ephemeral=True,
delete_after=60 delete_after=60,
) )