mirror of https://github.com/stijndcl/didier
Compare commits
No commits in common. "b26421b8752c261ff16cc4423651cd1d241e051f" and "94de47082b263fa10577639ab0d8b08961b697d3" have entirely different histories.
b26421b875
...
94de47082b
|
|
@ -1,39 +0,0 @@
|
|||
"""Deadlines
|
||||
|
||||
Revision ID: 08d21b2d1a0a
|
||||
Revises: 3962636f3a3d
|
||||
Create Date: 2022-08-12 23:44:13.947011
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "08d21b2d1a0a"
|
||||
down_revision = "3962636f3a3d"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"deadlines",
|
||||
sa.Column("deadline_id", sa.Integer(), nullable=False),
|
||||
sa.Column("course_id", sa.Integer(), nullable=True),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("deadline", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["course_id"],
|
||||
["ufora_courses.course_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("deadline_id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("deadlines")
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
"""Add custom links
|
||||
|
||||
Revision ID: 3962636f3a3d
|
||||
Revises: 346b408c362a
|
||||
Create Date: 2022-08-10 00:54:05.668255
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "3962636f3a3d"
|
||||
down_revision = "346b408c362a"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"links",
|
||||
sa.Column("link_id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("url", sa.Text(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("link_id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("links")
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from dateutil.parser import parse
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from database.schemas.relational import Deadline, UforaCourse
|
||||
|
||||
__all__ = ["add_deadline", "get_deadlines"]
|
||||
|
||||
|
||||
async def add_deadline(session: AsyncSession, course_id: int, name: str, date_str: str):
|
||||
"""Add a new deadline"""
|
||||
date_dt = parse(date_str, dayfirst=True).replace(tzinfo=ZoneInfo("Europe/Brussels"))
|
||||
|
||||
# If we only have a day, assume it's the end of the day
|
||||
if date_dt.hour == date_dt.minute == date_dt.second == 0:
|
||||
date_dt.replace(hour=23, minute=59, second=59)
|
||||
|
||||
deadline = Deadline(course_id=course_id, name=name, deadline=date_dt)
|
||||
session.add(deadline)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_deadlines(session: AsyncSession, *, course: Optional[UforaCourse] = None) -> list[Deadline]:
|
||||
"""Get a list of all deadlines that are currently known
|
||||
|
||||
This includes deadlines that have passed already
|
||||
"""
|
||||
statement = select(Deadline)
|
||||
|
||||
if course is not None:
|
||||
statement = statement.where(Deadline.course_id == course.course_id)
|
||||
|
||||
statement = statement.options(selectinload(Deadline.course))
|
||||
return (await session.execute(statement)).scalars().all()
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
from typing import Optional
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.exceptions import NoResultFoundException
|
||||
from database.schemas.relational import Link
|
||||
|
||||
__all__ = ["add_link", "edit_link", "get_all_links", "get_link_by_name"]
|
||||
|
||||
|
||||
async def get_all_links(session: AsyncSession) -> list[Link]:
|
||||
"""Get a list of all links"""
|
||||
statement = select(Link)
|
||||
return (await session.execute(statement)).scalars().all()
|
||||
|
||||
|
||||
async def add_link(session: AsyncSession, name: str, url: str) -> Link:
|
||||
"""Add a new link into the database"""
|
||||
if name.islower():
|
||||
name = name.capitalize()
|
||||
|
||||
instance = Link(name=name, url=url)
|
||||
session.add(instance)
|
||||
await session.commit()
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
async def get_link_by_name(session: AsyncSession, name: str) -> Optional[Link]:
|
||||
"""Get a link by its name"""
|
||||
statement = select(Link).where(func.lower(Link.name) == name.lower())
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
|
||||
async def edit_link(session: AsyncSession, name: str, new_url: str):
|
||||
"""Edit an existing link"""
|
||||
link: Optional[Link] = await get_link_by_name(session, name)
|
||||
|
||||
if link is None:
|
||||
raise NoResultFoundException
|
||||
|
||||
link.url = new_url
|
||||
session.add(link)
|
||||
await session.commit()
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from .constraints import DuplicateInsertException
|
||||
from .currency import DoubleNightly, NotEnoughDinks
|
||||
from .not_found import NoResultFoundException
|
||||
|
||||
__all__ = ["DuplicateInsertException", "DoubleNightly", "NotEnoughDinks", "NoResultFoundException"]
|
||||
|
|
@ -28,8 +28,6 @@ __all__ = [
|
|||
"CustomCommand",
|
||||
"CustomCommandAlias",
|
||||
"DadJoke",
|
||||
"Deadline",
|
||||
"Link",
|
||||
"NightlyData",
|
||||
"Task",
|
||||
"UforaAnnouncement",
|
||||
|
|
@ -111,29 +109,6 @@ class DadJoke(Base):
|
|||
joke: str = Column(Text, nullable=False)
|
||||
|
||||
|
||||
class Deadline(Base):
|
||||
"""A deadline for a university project"""
|
||||
|
||||
__tablename__ = "deadlines"
|
||||
|
||||
deadline_id: int = Column(Integer, primary_key=True)
|
||||
course_id: int = Column(Integer, ForeignKey("ufora_courses.course_id"))
|
||||
name: str = Column(Text, nullable=False)
|
||||
deadline: datetime = Column(DateTime(timezone=True), nullable=False)
|
||||
|
||||
course: UforaCourse = relationship("UforaCourse", back_populates="deadlines", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class Link(Base):
|
||||
"""Useful links that go useful places"""
|
||||
|
||||
__tablename__ = "links"
|
||||
|
||||
link_id: int = Column(Integer, primary_key=True)
|
||||
name: str = Column(Text, nullable=False, unique=True)
|
||||
url: str = Column(Text, nullable=False)
|
||||
|
||||
|
||||
class NightlyData(Base):
|
||||
"""Data for a user's Nightly stats"""
|
||||
|
||||
|
|
@ -174,9 +149,6 @@ class UforaCourse(Base):
|
|||
aliases: list[UforaCourseAlias] = relationship(
|
||||
"UforaCourseAlias", back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
||||
)
|
||||
deadlines: list[Deadline] = relationship(
|
||||
"Deadline", back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
||||
)
|
||||
|
||||
|
||||
class UforaCourseAlias(Base):
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from discord import app_commands
|
||||
from overrides import overrides
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.crud import links, ufora_courses, wordle
|
||||
from database.crud import ufora_courses, wordle
|
||||
from database.mongo_types import MongoDatabase
|
||||
|
||||
__all__ = ["CacheManager", "LinkCache", "UforaCourseCache"]
|
||||
__all__ = ["CacheManager", "UforaCourseCache"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -36,29 +35,18 @@ class DatabaseCache(ABC, Generic[T]):
|
|||
self.data.clear()
|
||||
|
||||
@abstractmethod
|
||||
async def refresh(self, database_session: T):
|
||||
"""Refresh the data stored in this cache"""
|
||||
|
||||
async def invalidate(self, database_session: T):
|
||||
"""Invalidate the data stored in this cache"""
|
||||
await self.refresh(database_session)
|
||||
|
||||
def get_autocomplete_suggestions(self, query: str) -> list[app_commands.Choice[str]]:
|
||||
def get_autocomplete_suggestions(self, query: str):
|
||||
"""Filter the cache to find everything that matches the search query"""
|
||||
query = query.lower()
|
||||
# Return the original (non-transformed) version of the data for pretty display in Discord
|
||||
suggestions = [self.data[index] for index, value in enumerate(self.data_transformed) if query in value]
|
||||
|
||||
return [app_commands.Choice(name=suggestion, value=suggestion.lower()) for suggestion in suggestions]
|
||||
|
||||
|
||||
class LinkCache(DatabaseCache[AsyncSession]):
|
||||
"""Cache to store the names of links"""
|
||||
|
||||
@overrides
|
||||
async def invalidate(self, database_session: AsyncSession):
|
||||
self.clear()
|
||||
|
||||
all_links = await links.get_all_links(database_session)
|
||||
self.data = list(map(lambda l: l.name, all_links))
|
||||
self.data.sort()
|
||||
self.data_transformed = list(map(str.lower, self.data))
|
||||
return [self.data[index] for index, value in enumerate(self.data_transformed) if query in value]
|
||||
|
||||
|
||||
class UforaCourseCache(DatabaseCache[AsyncSession]):
|
||||
|
|
@ -73,10 +61,11 @@ class UforaCourseCache(DatabaseCache[AsyncSession]):
|
|||
super().clear()
|
||||
|
||||
@overrides
|
||||
async def invalidate(self, database_session: AsyncSession):
|
||||
async def refresh(self, database_session: AsyncSession):
|
||||
self.clear()
|
||||
|
||||
courses = await ufora_courses.get_all_courses(database_session)
|
||||
|
||||
self.data = list(map(lambda c: c.name, courses))
|
||||
|
||||
# Load the aliases
|
||||
|
|
@ -89,7 +78,7 @@ class UforaCourseCache(DatabaseCache[AsyncSession]):
|
|||
self.data_transformed = list(map(str.lower, self.data))
|
||||
|
||||
@overrides
|
||||
def get_autocomplete_suggestions(self, query: str) -> list[app_commands.Choice[str]]:
|
||||
def get_autocomplete_suggestions(self, query: str):
|
||||
query = query.lower()
|
||||
results = set()
|
||||
|
||||
|
|
@ -102,14 +91,13 @@ class UforaCourseCache(DatabaseCache[AsyncSession]):
|
|||
if query in alias:
|
||||
results.add(course)
|
||||
|
||||
suggestions = sorted(list(results))
|
||||
return [app_commands.Choice(name=suggestion, value=suggestion.lower()) for suggestion in suggestions]
|
||||
return sorted(list(results))
|
||||
|
||||
|
||||
class WordleCache(DatabaseCache[MongoDatabase]):
|
||||
"""Cache to store the current daily Wordle word"""
|
||||
|
||||
async def invalidate(self, database_session: MongoDatabase):
|
||||
async def refresh(self, database_session: MongoDatabase):
|
||||
word = await wordle.get_daily_word(database_session)
|
||||
if word is not None:
|
||||
self.data = [word]
|
||||
|
|
@ -118,17 +106,14 @@ class WordleCache(DatabaseCache[MongoDatabase]):
|
|||
class CacheManager:
|
||||
"""Class that keeps track of all caches"""
|
||||
|
||||
links: LinkCache
|
||||
ufora_courses: UforaCourseCache
|
||||
wordle_word: WordleCache
|
||||
|
||||
def __init__(self):
|
||||
self.links = LinkCache()
|
||||
self.ufora_courses = UforaCourseCache()
|
||||
self.wordle_word = WordleCache()
|
||||
|
||||
async def initialize_caches(self, postgres_session: AsyncSession, mongo_db: MongoDatabase):
|
||||
"""Initialize the contents of all caches"""
|
||||
await self.links.invalidate(postgres_session)
|
||||
await self.ufora_courses.invalidate(postgres_session)
|
||||
await self.wordle_word.invalidate(mongo_db)
|
||||
await self.ufora_courses.refresh(postgres_session)
|
||||
await self.wordle_word.refresh(mongo_db)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
|
||||
from database.crud.links import get_link_by_name
|
||||
from database.schemas.relational import Link
|
||||
from didier import Didier
|
||||
from didier.data.apis import urban_dictionary
|
||||
from didier.data.embeds.google import GoogleSearch
|
||||
|
|
@ -39,35 +34,6 @@ class Other(commands.Cog):
|
|||
embed = GoogleSearch(results).to_embed()
|
||||
await ctx.reply(embed=embed, mention_author=False)
|
||||
|
||||
async def _get_link(self, name: str) -> Optional[Link]:
|
||||
async with self.client.postgres_session as session:
|
||||
return await get_link_by_name(session, name.lower())
|
||||
|
||||
@commands.command(name="Link", aliases=["Links"], usage="[Name]")
|
||||
async def link_msg(self, ctx: commands.Context, name: str):
|
||||
"""Message command to get the link to something"""
|
||||
link = await self._get_link(name)
|
||||
if link is None:
|
||||
return await ctx.reply(f"Found no links matching `{name}`.", mention_author=False)
|
||||
|
||||
target_message = await self.client.get_reply_target(ctx)
|
||||
await target_message.reply(link.url, mention_author=False)
|
||||
|
||||
@app_commands.command(name="link", description="Get the link to something")
|
||||
@app_commands.describe(name="The name of the link")
|
||||
async def link_slash(self, interaction: discord.Interaction, name: str):
|
||||
"""Slash command to get the link to something"""
|
||||
link = await self._get_link(name)
|
||||
if link is None:
|
||||
return await interaction.response.send_message(f"Found no links matching `{name}`.", ephemeral=True)
|
||||
|
||||
return await interaction.response.send_message(link.url)
|
||||
|
||||
@link_slash.autocomplete("name")
|
||||
async def _link_name_autocomplete(self, _: discord.Interaction, current: str) -> list[app_commands.Choice[str]]:
|
||||
"""Autocompletion for the 'name'-parameter"""
|
||||
return self.client.database_caches.links.get_autocomplete_suggestions(current)
|
||||
|
||||
|
||||
async def setup(client: Didier):
|
||||
"""Load the cog"""
|
||||
|
|
|
|||
|
|
@ -5,18 +5,12 @@ from discord import app_commands
|
|||
from discord.ext import commands
|
||||
|
||||
import settings
|
||||
from database.crud import custom_commands, links, ufora_courses
|
||||
from database.crud import custom_commands
|
||||
from database.exceptions.constraints import DuplicateInsertException
|
||||
from database.exceptions.not_found import NoResultFoundException
|
||||
from didier import Didier
|
||||
from didier.utils.discord.flags.owner import EditCustomFlags, SyncOptionFlags
|
||||
from didier.views.modals import (
|
||||
AddDadJoke,
|
||||
AddDeadline,
|
||||
AddLink,
|
||||
CreateCustomCommand,
|
||||
EditCustomCommand,
|
||||
)
|
||||
from didier.views.modals import AddDadJoke, CreateCustomCommand, EditCustomCommand
|
||||
|
||||
|
||||
class Owner(commands.Cog):
|
||||
|
|
@ -86,6 +80,17 @@ class Owner(commands.Cog):
|
|||
async def add_msg(self, ctx: commands.Context):
|
||||
"""Command group for [add X] message commands"""
|
||||
|
||||
@add_msg.command(name="Custom")
|
||||
async def add_custom_msg(self, ctx: commands.Context, name: str, *, response: str):
|
||||
"""Add a new custom command"""
|
||||
async with self.client.postgres_session as session:
|
||||
try:
|
||||
await custom_commands.create_command(session, name, response)
|
||||
await self.client.confirm_message(ctx.message)
|
||||
except DuplicateInsertException:
|
||||
await ctx.reply("There is already a command with this name.")
|
||||
await self.client.reject_message(ctx.message)
|
||||
|
||||
@add_msg.command(name="Alias")
|
||||
async def add_alias_msg(self, ctx: commands.Context, command: str, alias: str):
|
||||
"""Add a new alias for a custom command"""
|
||||
|
|
@ -100,26 +105,6 @@ class Owner(commands.Cog):
|
|||
await ctx.reply("There is already a command with this name.")
|
||||
await self.client.reject_message(ctx.message)
|
||||
|
||||
@add_msg.command(name="Custom")
|
||||
async def add_custom_msg(self, ctx: commands.Context, name: str, *, response: str):
|
||||
"""Add a new custom command"""
|
||||
async with self.client.postgres_session as session:
|
||||
try:
|
||||
await custom_commands.create_command(session, name, response)
|
||||
await self.client.confirm_message(ctx.message)
|
||||
except DuplicateInsertException:
|
||||
await ctx.reply("There is already a command with this name.")
|
||||
await self.client.reject_message(ctx.message)
|
||||
|
||||
@add_msg.command(name="Link")
|
||||
async def add_link_msg(self, ctx: commands.Context, name: str, url: str):
|
||||
"""Add a new link"""
|
||||
async with self.client.postgres_session as session:
|
||||
await links.add_link(session, name, url)
|
||||
await self.client.database_caches.links.invalidate(session)
|
||||
|
||||
await self.client.confirm_message(ctx.message)
|
||||
|
||||
@add_slash.command(name="custom", description="Add a custom command")
|
||||
async def add_custom_slash(self, interaction: discord.Interaction):
|
||||
"""Slash command to add a custom command"""
|
||||
|
|
@ -138,35 +123,6 @@ class Owner(commands.Cog):
|
|||
modal = AddDadJoke(self.client)
|
||||
await interaction.response.send_modal(modal)
|
||||
|
||||
@add_slash.command(name="deadline", description="Add a deadline")
|
||||
@app_commands.describe(course="The name of the course to add a deadline for (aliases work too)")
|
||||
async def add_deadline_slash(self, interaction: discord.Interaction, course: str):
|
||||
"""Slash command to add a deadline"""
|
||||
async with self.client.postgres_session as session:
|
||||
course_instance = await ufora_courses.get_course_by_name(session, course)
|
||||
|
||||
if course_instance is None:
|
||||
return await interaction.response.send_message(f"No course found matching `{course}`.", ephemeral=True)
|
||||
|
||||
modal = AddDeadline(self.client, course_instance)
|
||||
await interaction.response.send_modal(modal)
|
||||
|
||||
@add_deadline_slash.autocomplete("course")
|
||||
async def _add_deadline_course_autocomplete(
|
||||
self, _: discord.Interaction, current: str
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
"""Autocompletion for the 'course'-parameter"""
|
||||
return self.client.database_caches.ufora_courses.get_autocomplete_suggestions(current)
|
||||
|
||||
@add_slash.command(name="link", description="Add a new link")
|
||||
async def add_link_slash(self, interaction: discord.Interaction):
|
||||
"""Slash command to add new links"""
|
||||
if not await self.client.is_owner(interaction.user):
|
||||
return interaction.response.send_message("You don't have permission to run this command.", ephemeral=True)
|
||||
|
||||
modal = AddLink(self.client)
|
||||
await interaction.response.send_modal(modal)
|
||||
|
||||
@commands.group(name="Edit", case_insensitive=True, invoke_without_command=False)
|
||||
async def edit_msg(self, ctx: commands.Context):
|
||||
"""Command group for [edit X] commands"""
|
||||
|
|
@ -179,7 +135,7 @@ class Owner(commands.Cog):
|
|||
await custom_commands.edit_command(session, command, flags.name, flags.response)
|
||||
return await self.client.confirm_message(ctx.message)
|
||||
except NoResultFoundException:
|
||||
await ctx.reply(f"No command found matching `{command}`.")
|
||||
await ctx.reply(f"No command found matching ``{command}``.")
|
||||
return await self.client.reject_message(ctx.message)
|
||||
|
||||
@edit_slash.command(name="custom", description="Edit a custom command")
|
||||
|
|
@ -187,13 +143,15 @@ class Owner(commands.Cog):
|
|||
async def edit_custom_slash(self, interaction: discord.Interaction, command: str):
|
||||
"""Slash command to edit a custom command"""
|
||||
if not await self.client.is_owner(interaction.user):
|
||||
return interaction.response.send_message("You don't have permission to run this command.", ephemeral=True)
|
||||
return interaction.response.send_message(
|
||||
"Je hebt geen toestemming om dit commando uit te voeren.", ephemeral=True
|
||||
)
|
||||
|
||||
async with self.client.postgres_session as session:
|
||||
_command = await custom_commands.get_command(session, command)
|
||||
if _command is None:
|
||||
return await interaction.response.send_message(
|
||||
f"No command found matching `{command}`.", ephemeral=True
|
||||
f"Geen commando gevonden voor ``{command}``.", ephemeral=True
|
||||
)
|
||||
|
||||
modal = EditCustomCommand(self.client, _command.name, _command.response)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ from discord import app_commands
|
|||
from discord.ext import commands
|
||||
|
||||
from database.crud import ufora_courses
|
||||
from database.crud.deadlines import get_deadlines
|
||||
from didier import Didier
|
||||
from didier.data.embeds.deadlines import Deadlines
|
||||
from didier.utils.discord.flags.school import StudyGuideFlags
|
||||
|
||||
|
||||
|
|
@ -29,15 +27,6 @@ class School(commands.Cog):
|
|||
"""Remove the commands when the cog is unloaded"""
|
||||
self.client.tree.remove_command(self._pin_ctx_menu.name, type=self._pin_ctx_menu.type)
|
||||
|
||||
@commands.hybrid_command(name="deadlines", description="Show upcoming deadlines")
|
||||
async def deadlines(self, ctx: commands.Context):
|
||||
"""Show upcoming deadlines"""
|
||||
async with self.client.postgres_session as session:
|
||||
deadlines = await get_deadlines(session)
|
||||
|
||||
embed = Deadlines(deadlines).to_embed()
|
||||
await ctx.reply(embed=embed, mention_author=False, ephemeral=False)
|
||||
|
||||
@commands.command(name="Pin", usage="[Message]")
|
||||
async def pin(self, ctx: commands.Context, message: Optional[discord.Message] = None):
|
||||
"""Pin a message in the current channel"""
|
||||
|
|
@ -76,14 +65,14 @@ class School(commands.Cog):
|
|||
@commands.hybrid_command(
|
||||
name="fiche", description="Sends the link to the study guide for [Course]", aliases=["guide", "studiefiche"]
|
||||
)
|
||||
@app_commands.describe(course="The name of the course to fetch the study guide for (aliases work too)")
|
||||
@app_commands.describe(course="vak")
|
||||
async def study_guide(self, ctx: commands.Context, course: str, *, flags: StudyGuideFlags):
|
||||
"""Create links to study guides"""
|
||||
async with self.client.postgres_session as session:
|
||||
ufora_course = await ufora_courses.get_course_by_name(session, course)
|
||||
|
||||
if ufora_course is None:
|
||||
return await ctx.reply(f"Found no course matching `{course}`", ephemeral=True)
|
||||
return await ctx.reply(f"Found no course matching ``{course}``", ephemeral=True)
|
||||
|
||||
return await ctx.reply(
|
||||
f"https://studiekiezer.ugent.be/studiefiche/nl/{ufora_course.code}/{flags.year}",
|
||||
|
|
@ -91,11 +80,12 @@ class School(commands.Cog):
|
|||
)
|
||||
|
||||
@study_guide.autocomplete("course")
|
||||
async def _study_guide_course_autocomplete(
|
||||
self, _: discord.Interaction, current: str
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
async def study_guide_autocomplete(self, _: discord.Interaction, current: str) -> list[app_commands.Choice[str]]:
|
||||
"""Autocompletion for the 'course'-parameter"""
|
||||
return self.client.database_caches.ufora_courses.get_autocomplete_suggestions(current)
|
||||
return [
|
||||
app_commands.Choice(name=course, value=course)
|
||||
for course in self.client.database_caches.ufora_courses.get_autocomplete_suggestions(current)
|
||||
]
|
||||
|
||||
|
||||
async def setup(client: Didier):
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
import itertools
|
||||
from datetime import datetime
|
||||
|
||||
import discord
|
||||
from overrides import overrides
|
||||
|
||||
from database.schemas.relational import Deadline
|
||||
from didier.data.embeds.base import EmbedBaseModel
|
||||
from didier.utils.types.datetime import tz_aware_now
|
||||
from didier.utils.types.string import get_edu_year_name
|
||||
|
||||
__all__ = ["Deadlines"]
|
||||
|
||||
|
||||
class Deadlines(EmbedBaseModel):
|
||||
"""Embed that shows all the deadlines of a semester"""
|
||||
|
||||
deadlines: list[Deadline]
|
||||
|
||||
def __init__(self, deadlines: list[Deadline]):
|
||||
self.deadlines = deadlines
|
||||
self.deadlines.sort(key=lambda deadline: deadline.deadline)
|
||||
|
||||
@overrides
|
||||
def to_embed(self, **kwargs: dict) -> discord.Embed:
|
||||
embed = discord.Embed(colour=discord.Colour.dark_gold())
|
||||
embed.set_author(name="Upcoming Deadlines")
|
||||
now = tz_aware_now()
|
||||
|
||||
has_active_deadlines = False
|
||||
deadlines_grouped: dict[int, list[str]] = {}
|
||||
|
||||
for year, deadlines in itertools.groupby(self.deadlines, key=lambda _deadline: _deadline.course.year):
|
||||
if year not in deadlines_grouped:
|
||||
deadlines_grouped[year] = []
|
||||
|
||||
for deadline in deadlines:
|
||||
passed = deadline.deadline <= now
|
||||
if not passed:
|
||||
has_active_deadlines = True
|
||||
|
||||
deadline_str = (
|
||||
f"{deadline.course.name} - {deadline.name}: <t:{round(datetime.timestamp(deadline.deadline))}:R>"
|
||||
)
|
||||
|
||||
# Strike through deadlines that aren't active anymore
|
||||
deadlines_grouped[year].append(deadline_str if not passed else f"~~{deadline_str}~~")
|
||||
|
||||
# Send an Easter egg when there are no deadlines
|
||||
if not has_active_deadlines:
|
||||
embed.description = "There are currently no upcoming deadlines."
|
||||
embed.set_image(url="https://c.tenor.com/RUzJ3lDGQUsAAAAC/iron-man-you-can-rest-now.gif")
|
||||
return embed
|
||||
|
||||
for i in range(1, 6):
|
||||
if i not in deadlines_grouped:
|
||||
continue
|
||||
|
||||
name = get_edu_year_name(i - 1)
|
||||
description = "\n".join(deadlines_grouped[i])
|
||||
|
||||
embed.add_field(name=name, value=description, inline=False)
|
||||
|
||||
return embed
|
||||
|
|
@ -111,17 +111,6 @@ class Didier(commands.Bot):
|
|||
for line in fp:
|
||||
self.wordle_words.add(line.strip())
|
||||
|
||||
async def get_reply_target(self, ctx: commands.Context) -> discord.Message:
|
||||
"""Get the target message that should be replied to
|
||||
|
||||
In case the invoking message is a reply to something, reply to the
|
||||
original message instead
|
||||
"""
|
||||
if ctx.message.reference is not None:
|
||||
return await self.resolve_message(ctx.message.reference)
|
||||
|
||||
return ctx.message
|
||||
|
||||
async def resolve_message(self, reference: discord.MessageReference) -> discord.Message:
|
||||
"""Fetch a message from a reference"""
|
||||
# Message is in the cache, return it
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import math
|
||||
from typing import Optional
|
||||
|
||||
__all__ = ["abbreviate", "leading", "pluralize", "get_edu_year_name"]
|
||||
__all__ = ["abbreviate", "leading", "pluralize"]
|
||||
|
||||
|
||||
def abbreviate(text: str, max_length: int) -> str:
|
||||
|
|
@ -43,10 +43,3 @@ def pluralize(word: str, amount: int, plural_form: Optional[str] = None) -> str:
|
|||
return word
|
||||
|
||||
return plural_form or (word + "s")
|
||||
|
||||
|
||||
def get_edu_year_name(year: int) -> str:
|
||||
"""Get the string representation of a university year"""
|
||||
years = ["1st Bachelor", "2nd Bachelor", "3rd Bachelor", "1st Master", "2nd Master"]
|
||||
|
||||
return years[year]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
from .custom_commands import CreateCustomCommand, EditCustomCommand
|
||||
from .dad_jokes import AddDadJoke
|
||||
from .deadlines import AddDeadline
|
||||
from .links import AddLink
|
||||
|
||||
__all__ = ["AddDadJoke", "AddDeadline", "CreateCustomCommand", "EditCustomCommand", "AddLink"]
|
||||
__all__ = ["AddDadJoke", "CreateCustomCommand", "EditCustomCommand"]
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class EditCustomCommand(discord.ui.Modal, title="Edit Custom Command"):
|
|||
async with self.client.postgres_session as session:
|
||||
await edit_command(session, self.original_name, name_field.value, response_field.value)
|
||||
|
||||
await interaction.response.send_message(f"Successfully edited `{self.original_name}`.", ephemeral=True)
|
||||
await interaction.response.send_message(f"Successfully edited ``{self.original_name}``.", ephemeral=True)
|
||||
|
||||
@overrides
|
||||
async def on_error(self, interaction: discord.Interaction, error: Exception): # type: ignore
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import traceback
|
||||
|
||||
import discord.ui
|
||||
from discord import Interaction
|
||||
from overrides import overrides
|
||||
|
||||
from database.crud.deadlines import add_deadline
|
||||
from database.schemas.relational import UforaCourse
|
||||
|
||||
__all__ = ["AddDeadline"]
|
||||
|
||||
from didier import Didier
|
||||
|
||||
|
||||
class AddDeadline(discord.ui.Modal, title="Add Deadline"):
|
||||
"""Modal to add a new deadline"""
|
||||
|
||||
client: Didier
|
||||
ufora_course: UforaCourse
|
||||
|
||||
name: discord.ui.TextInput = discord.ui.TextInput(
|
||||
label="Name", placeholder="Project 9001", required=True, style=discord.TextStyle.short
|
||||
)
|
||||
deadline: discord.ui.TextInput = discord.ui.TextInput(
|
||||
label="Deadline", placeholder="DD/MM/YYYY HH:MM:SS*", required=True, style=discord.TextStyle.short
|
||||
)
|
||||
|
||||
def __init__(self, client: Didier, ufora_course: UforaCourse, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.client = client
|
||||
self.ufora_course = ufora_course
|
||||
|
||||
@overrides
|
||||
async def on_submit(self, interaction: Interaction):
|
||||
if not self.name.value or not self.deadline.value:
|
||||
return await interaction.response.send_message("Required fields cannot be empty.", ephemeral=True)
|
||||
|
||||
async with self.client.postgres_session as session:
|
||||
await add_deadline(session, self.ufora_course.course_id, self.name.value, self.deadline.value)
|
||||
|
||||
await interaction.response.send_message("Successfully added new deadline.", ephemeral=True)
|
||||
|
||||
@overrides
|
||||
async def on_error(self, interaction: Interaction, error: Exception): # type: ignore
|
||||
await interaction.response.send_message("Something went wrong.", ephemeral=True)
|
||||
traceback.print_tb(error.__traceback__)
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import traceback
|
||||
|
||||
import discord.ui
|
||||
from overrides import overrides
|
||||
|
||||
from database.crud.links import add_link
|
||||
from didier import Didier
|
||||
|
||||
__all__ = ["AddLink"]
|
||||
|
||||
|
||||
class AddLink(discord.ui.Modal, title="Add Link"):
|
||||
"""Modal to add a new link"""
|
||||
|
||||
name: discord.ui.TextInput = discord.ui.TextInput(label="Name", style=discord.TextStyle.short, placeholder="Source")
|
||||
url: discord.ui.TextInput = discord.ui.TextInput(
|
||||
label="URL", style=discord.TextStyle.short, placeholder="https://github.com/stijndcl/didier"
|
||||
)
|
||||
|
||||
client: Didier
|
||||
|
||||
def __init__(self, client: Didier, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.client = client
|
||||
|
||||
@overrides
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if self.name.value is None:
|
||||
return await interaction.response.send_message("Required field `Name` cannot be empty.", ephemeral=True)
|
||||
|
||||
if self.url.value is None:
|
||||
return await interaction.response.send_message("Required field `URL` cannot be empty.", ephemeral=True)
|
||||
|
||||
async with self.client.postgres_session as session:
|
||||
await add_link(session, self.name.value, self.url.value)
|
||||
await self.client.database_caches.links.invalidate(session)
|
||||
|
||||
await interaction.response.send_message(f"Successfully added `{self.name.value.capitalize()}`.", ephemeral=True)
|
||||
|
||||
@overrides
|
||||
async def on_error(self, interaction: discord.Interaction, error: Exception): # type: ignore
|
||||
await interaction.response.send_message("Something went wrong.", ephemeral=True)
|
||||
traceback.print_tb(error.__traceback__)
|
||||
|
|
@ -8,7 +8,6 @@ pytest-asyncio==0.18.3
|
|||
pytest-env==0.6.2
|
||||
sqlalchemy2-stubs==0.0.2a23
|
||||
types-beautifulsoup4==4.11.3
|
||||
types-python-dateutil==2.8.19
|
||||
types-pytz==2021.3.8
|
||||
|
||||
# Flake8 + plugins
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from database.utils.caches import UforaCourseCache
|
|||
async def test_ufora_course_cache_refresh_empty(postgres: AsyncSession, ufora_course_with_alias: UforaCourse):
|
||||
"""Test loading the data for the Ufora Course cache when it's empty"""
|
||||
cache = UforaCourseCache()
|
||||
await cache.invalidate(postgres)
|
||||
await cache.refresh(postgres)
|
||||
|
||||
assert len(cache.data) == 1
|
||||
assert cache.data == ["test"]
|
||||
|
|
@ -20,7 +20,7 @@ async def test_ufora_course_cache_refresh_not_empty(postgres: AsyncSession, ufor
|
|||
cache.data = ["Something"]
|
||||
cache.data_transformed = ["something"]
|
||||
|
||||
await cache.invalidate(postgres)
|
||||
await cache.refresh(postgres)
|
||||
|
||||
assert len(cache.data) == 1
|
||||
assert cache.data == ["test"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue