Start working on events

pull/167/head
stijndcl 2023-02-02 23:16:51 +01:00 committed by Stijn De Clercq
parent 1ae7790ca2
commit 5deb312474
7 changed files with 203 additions and 1 deletions

View File

@ -0,0 +1,36 @@
"""Add events table
Revision ID: 954ad804f057
Revises: 9fb84b4d9f0b
Create Date: 2023-02-02 22:20:23.107931
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "954ad804f057"
down_revision = "9fb84b4d9f0b"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"events",
sa.Column("event_id", sa.Integer(), nullable=False),
sa.Column("name", sa.Text(), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("notification_channel", sa.BigInteger(), nullable=False),
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("event_id"),
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("events")
# ### end Alembic commands ###

View File

@ -0,0 +1,41 @@
from typing import Optional
from zoneinfo import ZoneInfo
from dateutil.parser import parse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.schemas import Event
__all__ = ["add_event", "get_event_by_id", "get_events", "get_next_event"]
async def add_event(
session: AsyncSession, *, name: str, description: Optional[str], date_str: str, channel_id: int
) -> Event:
"""Create a new event"""
date_dt = parse(date_str, dayfirst=True).replace(tzinfo=ZoneInfo("Europe/Brussels"))
event = Event(name=name, description=description, timestamp=date_dt, notification_channel=channel_id)
session.add(event)
await session.commit()
return event
async def get_event_by_id(session: AsyncSession, event_id: int) -> Optional[Event]:
"""Get an event by its id"""
statement = select(Event).where(Event.event_id == event_id)
return (await session.execute(statement)).scalar_one_or_none()
async def get_events(session: AsyncSession) -> list[Event]:
"""Get a list of all upcoming events"""
statement = select(Event)
return (await session.execute(statement)).scalars().all()
async def get_next_event(session: AsyncSession) -> Optional[Event]:
"""Get the first upcoming event"""
statement = select(Event).order_by(Event.timestamp)
return (await session.execute(statement)).scalar_one_or_none()

View File

@ -33,6 +33,7 @@ __all__ = [
"DadJoke", "DadJoke",
"Deadline", "Deadline",
"EasterEgg", "EasterEgg",
"Event",
"FreeGame", "FreeGame",
"GitHubLink", "GitHubLink",
"Link", "Link",
@ -175,6 +176,18 @@ class EasterEgg(Base):
startswith: bool = Column(Boolean, nullable=False, server_default="1") startswith: bool = Column(Boolean, nullable=False, server_default="1")
class Event(Base):
"""A scheduled event"""
__tablename__ = "events"
event_id: int = Column(Integer, primary_key=True)
name: str = Column(Text, nullable=False)
description: Optional[str] = Column(Text, nullable=True)
notification_channel: int = Column(BigInteger, nullable=False)
timestamp: datetime = Column(DateTime(timezone=True), nullable=False)
class FreeGame(Base): class FreeGame(Base):
"""A temporarily free game""" """A temporarily free game"""

View File

@ -4,7 +4,7 @@ import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
from database.crud import birthdays, bookmarks, github from database.crud import birthdays, bookmarks, events, github
from database.exceptions import ( from database.exceptions import (
DuplicateInsertException, DuplicateInsertException,
Forbidden, Forbidden,
@ -200,6 +200,52 @@ class Discord(commands.Cog):
modal = CreateBookmark(self.client, message.jump_url) modal = CreateBookmark(self.client, message.jump_url)
await interaction.response.send_modal(modal) await interaction.response.send_modal(modal)
@commands.hybrid_command(name="events")
@app_commands.rename(event_id="id")
@app_commands.describe(event_id="The id of the event to fetch. If not passed, all events are fetched instead.")
async def events(self, ctx: commands.Context, event_id: Optional[int] = None):
"""Show information about the event with id `event_id`.
If no value for `event_id` is supplied, this shows all upcoming events instead.
"""
async with ctx.typing():
async with self.client.postgres_session as session:
if event_id is None:
upcoming = await events.get_events(session)
embed = discord.Embed(title="Upcoming Events", colour=discord.Colour.blue())
if not upcoming:
embed.colour = discord.Colour.red()
embed.description = "There are currently no upcoming events scheduled."
return await ctx.reply(embed=embed, mention_author=False)
upcoming.sort(key=lambda e: e.timestamp.timestamp())
description_items = []
for event in upcoming:
description_items.append(
f"`{event.event_id}`: {event.name} ({discord.utils.format_dt(event.timestamp, style='R')})"
)
embed.description = "\n".join(description_items)
return await ctx.reply(embed=embed, mention_author=False)
else:
event = await events.get_event_by_id(session, event_id)
if event is None:
return await ctx.reply(f"Found no event with id `{event_id}`.", mention_author=False)
embed = discord.Embed(title="Upcoming Events", colour=discord.Colour.blue())
embed.add_field(name="Name", value=event.name, inline=True)
embed.add_field(name="Id", value=event.event_id, inline=True)
embed.add_field(
name="Timer", value=discord.utils.format_dt(event.timestamp, style="R"), inline=True
)
embed.add_field(
name="Channel", value=self.client.get_channel(event.notification_channel).mention, inline=False
)
embed.description = event.description
return await ctx.reply(embed=embed, mention_author=False)
@commands.group(name="github", aliases=["gh", "git"], case_insensitive=True, invoke_without_command=True) @commands.group(name="github", aliases=["gh", "git"], case_insensitive=True, invoke_without_command=True)
async def github_group(self, ctx: commands.Context, user: Optional[discord.User] = None): async def github_group(self, ctx: commands.Context, user: Optional[discord.User] = None):
"""Show a user's GitHub links. """Show a user's GitHub links.

View File

@ -13,6 +13,7 @@ from didier.utils.discord.flags.owner import EditCustomFlags, SyncOptionFlags
from didier.views.modals import ( from didier.views.modals import (
AddDadJoke, AddDadJoke,
AddDeadline, AddDeadline,
AddEvent,
AddLink, AddLink,
CreateCustomCommand, CreateCustomCommand,
EditCustomCommand, EditCustomCommand,
@ -173,6 +174,15 @@ class Owner(commands.Cog):
"""Autocompletion for the 'course'-parameter""" """Autocompletion for the 'course'-parameter"""
return self.client.database_caches.ufora_courses.get_autocomplete_suggestions(current) return self.client.database_caches.ufora_courses.get_autocomplete_suggestions(current)
@add_slash.command(name="event", description="Add a new event")
async def add_event_slash(self, interaction: discord.Interaction):
"""Slash command to add new events"""
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 = AddEvent(self.client)
await interaction.response.send_modal(modal)
@add_slash.command(name="link", description="Add a new link") @add_slash.command(name="link", description="Add a new link")
async def add_link_slash(self, interaction: discord.Interaction): async def add_link_slash(self, interaction: discord.Interaction):
"""Slash command to add new links""" """Slash command to add new links"""

View File

@ -2,6 +2,7 @@ from .bookmarks import CreateBookmark
from .custom_commands import CreateCustomCommand, EditCustomCommand from .custom_commands import CreateCustomCommand, EditCustomCommand
from .dad_jokes import AddDadJoke from .dad_jokes import AddDadJoke
from .deadlines import AddDeadline from .deadlines import AddDeadline
from .events import AddEvent
from .links import AddLink from .links import AddLink
from .memes import GenerateMeme from .memes import GenerateMeme
@ -9,6 +10,7 @@ __all__ = [
"CreateBookmark", "CreateBookmark",
"AddDadJoke", "AddDadJoke",
"AddDeadline", "AddDeadline",
"AddEvent",
"CreateCustomCommand", "CreateCustomCommand",
"EditCustomCommand", "EditCustomCommand",
"AddLink", "AddLink",

View File

@ -0,0 +1,54 @@
from zoneinfo import ZoneInfo
import discord
from dateutil.parser import ParserError, parse
from overrides import overrides
from database.crud.events import add_event
from didier import Didier
__all__ = ["AddEvent"]
class AddEvent(discord.ui.Modal, title="Add Event"):
"""Modal to add a new event"""
name: discord.ui.TextInput = discord.ui.TextInput(label="Name", style=discord.TextStyle.short, required=True)
description: discord.ui.TextInput = discord.ui.TextInput(
label="Description", style=discord.TextStyle.paragraph, required=False, default=None
)
channel: discord.ui.TextInput = discord.ui.TextInput(
label="Channel id", style=discord.TextStyle.short, required=True, placeholder="676713433567199232"
)
timestamp: discord.ui.TextInput = discord.ui.TextInput(
label="Date", style=discord.TextStyle.short, required=True, placeholder="21/02/2020 21:21:00"
)
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) -> None:
try:
parse(self.timestamp.value, dayfirst=True).replace(tzinfo=ZoneInfo("Europe/Brussels"))
except ParserError:
return await interaction.response.send_message("Unable to parse date argument.", ephemeral=True)
if self.client.get_channel(int(self.channel.value)) is None:
return await interaction.response.send_message(
f"Unable to find channel `{self.channel.value}`", ephemeral=True
)
async with self.client.postgres_session as session:
event = await add_event(
session,
name=self.name.value,
description=self.description.value,
date_str=self.timestamp.value,
channel_id=int(self.channel.value),
)
return await interaction.response.send_message(f"Successfully added event `{event.event_id}`.", ephemeral=True)