From c9b2a41e1ab024cd07915a8f45a7aa5d0f72908e Mon Sep 17 00:00:00 2001 From: dnns01 <git@dnns01.de> Date: Wed, 30 Aug 2023 00:05:00 +0200 Subject: [PATCH 1/2] Different enhancements to appointments --- extensions/appointments.py | 309 ++++++++++++------------------------- json_import.py | 25 +++ models.py | 88 ++++++++++- views/appointment_view.py | 147 ++++++------------ 4 files changed, 257 insertions(+), 312 deletions(-) diff --git a/extensions/appointments.py b/extensions/appointments.py index 5ecf147..83e3a79 100644 --- a/extensions/appointments.py +++ b/extensions/appointments.py @@ -1,251 +1,134 @@ import asyncio -import json -import os import uuid from datetime import datetime, timedelta -from typing import NewType, Union, Dict -from discord import app_commands, errors, Embed, Interaction, VoiceChannel, StageChannel, TextChannel, \ - ForumChannel, CategoryChannel, Thread, PartialMessageable +from discord import app_commands, errors, Interaction from discord.ext import tasks, commands +import models +from models import Appointment from views.appointment_view import AppointmentView -Channel = NewType('Channel', Union[ - VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel, Thread, PartialMessageable, None]) + +async def send_notification(appointment, channel) -> None: + message = f"Aufgepasst!\nDer Termin \"{appointment.title}\" startet " + + if appointment.reminder_sent: + message += f"jetzt! :loudspeaker: " + else: + message += f"<t:{int(appointment.date_time.timestamp())}:R>." + + message += f"\n" + message += " ".join([f"<@!{str(attendee.member_id)}>" for attendee in appointment.attendees]) + + await channel.send(message) + + +def get_view(appointment: models.Appointment) -> AppointmentView: + view = AppointmentView() + if appointment.recurring == 0: + for child in view.children: + if child.custom_id == "appointment_view:skip": + child.disabled = True + + return view @app_commands.guild_only() -class Appointments(commands.GroupCog, name="appointments", description="Verwaltet Termine in Kanälen"): +class Appointments(commands.GroupCog, name="appointments", description="Handle Appointments in Channels"): def __init__(self, bot): self.bot = bot - self.fmt = os.getenv("DISCORD_DATE_TIME_FORMAT") self.timer.start() - self.appointments = {} - self.app_file = os.getenv("DISCORD_APPOINTMENTS_FILE") - self.load_appointments() - - def load_appointments(self): - appointments_file = open(self.app_file, mode='r') - self.appointments = json.load(appointments_file) - - def save_appointments(self): - appointments_file = open(self.app_file, mode='w') - json.dump(self.appointments, appointments_file) @tasks.loop(minutes=1) async def timer(self): - delete = [] - - for channel_id, channel_appointments in self.appointments.items(): - channel = None - for message_id, appointment in channel_appointments.items(): - now = datetime.now() - date_time = datetime.strptime(appointment["date_time"], self.fmt) - remind_at = date_time - timedelta(minutes=appointment["reminder"]) - - if now >= remind_at: - try: - channel = await self.bot.fetch_channel(int(channel_id)) - message = await channel.fetch_message(int(message_id)) - reactions = message.reactions - diff = int(round(((date_time - now).total_seconds() / 60), 0)) - answer = f"Benachrichtigung!\nDer Termin \"{appointment['title']}\" startet " - - if appointment["reminder"] > 0 and diff > 0: - answer += f"<t:{int(date_time.timestamp())}:R>." - if (reminder := appointment.get("reminder")) and appointment.get("recurring"): - appointment["original_reminder"] = str(reminder) - appointment["reminder"] = 0 + for appointment in Appointment.select().order_by(Appointment.channel): + now = datetime.now() + date_time = appointment.date_time + remind_at = date_time - timedelta( + minutes=appointment.reminder) if not appointment.reminder_sent else date_time + + if now >= remind_at: + try: + channel = await self.bot.fetch_channel(appointment.channel) + message = await channel.fetch_message(appointment.message) + await send_notification(appointment, channel) + + if appointment.reminder_sent: + await message.delete() + + if appointment.recurring == 0: + appointment.delete_instance(recursive=True) else: - answer += f"jetzt! :loudspeaker: " - delete.append(message_id) - - answer += f"\n" - for reaction in reactions: - if reaction.emoji == "ðŸ‘": - async for user in reaction.users(): - if user != self.bot.user: - answer += f"<@!{str(user.id)}> " - - await channel.send(answer) - - if str(message.id) in delete: - await message.delete() - except errors.NotFound: - delete.append(message_id) - - if len(delete) > 0: - for key in delete: - channel_appointment = channel_appointments.get(key) - if channel_appointment: - if channel_appointment.get("recurring"): - recurring = channel_appointment["recurring"] - date_time_str = channel_appointment["date_time"] - date_time = datetime.strptime(date_time_str, self.fmt) - new_date_time = date_time + timedelta(days=recurring) - reminder = channel_appointment.get("original_reminder") - reminder = reminder if reminder else 0 - await self.add_appointment(channel, channel_appointment["author_id"], - new_date_time, - reminder, - channel_appointment["title"], - channel_appointment["attendees"], - channel_appointment["ics_uuid"], - channel_appointment["description"], - channel_appointment["recurring"]) - channel_appointments.pop(key) - self.save_appointments() + new_date_time = appointment.date_time + timedelta(days=appointment.recurring) + reminder_sent = appointment.reminder == 0 + Appointment.update(reminder_sent=reminder_sent, date_time=new_date_time).where( + Appointment.id == appointment.id).execute() + updated_appointment = Appointment.get(Appointment.id == appointment.id) + new_message = await channel.send(embed=updated_appointment.get_embed(), + view=get_view(updated_appointment)) + Appointment.update(message=new_message.id).where(Appointment.id == appointment.id).execute() + else: + Appointment.update(reminder_sent=True).where(Appointment.id == appointment.id).execute() + except (errors.NotFound, errors.Forbidden): + appointment.delete_instance(recursive=True) @timer.before_loop async def before_timer(self): await asyncio.sleep(60 - datetime.now().second) - async def add_appointment(self, channel: Channel, author_id: int, date_time: datetime, reminder: int, title: str, - attendees: Dict, ics_uuid: str, description: str = "", recurring: int = None) -> None: - message = await self.send_or_update_appointment(channel, author_id, description, title, date_time, reminder, - recurring, attendees) - - if str(channel.id) not in self.appointments: - self.appointments[str(channel.id)] = {} - - channel_appointments = self.appointments.get(str(channel.id)) - channel_appointments[str(message.id)] = {"date_time": date_time.strftime(self.fmt), "reminder": reminder, - "title": title, "author_id": author_id, "recurring": recurring, - "description": description, "attendees": attendees, - "ics_uuid": ics_uuid} - - self.save_appointments() - - @app_commands.command(name="add", description="Fügt dem Kanal einen neunen Termin hinzu.") - @app_commands.describe(date="Tag des Termins (z. B. 21.10.2015).", time="Uhrzeit des Termins (z. B. 13:37).", + @app_commands.command(name="add", description="Füge dem Kanal einen neuen Termin hinzu.") + @app_commands.describe(date="Tag des Termins im Format TT.MM.JJJJ", time="Uhrzeit des Termins im Format HH:MM", reminder="Wie viele Minuten bevor der Termin startet, soll eine Erinnerung verschickt werden?", - title="Titel des Termins.", description="Beschreibung des Termins.", + title="Titel des Termins (so wie er dann evtl. auch im Kalender steht).", + description="Detailliertere Beschreibung, was gemacht werden soll.", recurring="In welchem Intervall (in Tagen) soll der Termin wiederholt werden?") async def cmd_add_appointment(self, interaction: Interaction, date: str, time: str, reminder: int, title: str, - description: str = "", recurring: int = None): - - await interaction.response.defer(ephemeral=True) + description: str = "", recurring: int = 0): + """ Add an appointment to a channel """ + channel = interaction.channel + author_id = interaction.user.id try: - attendees = {str(interaction.user.id): 1} - date_time = datetime.strptime(f"{date} {time}", self.fmt) - if date_time < datetime.now(): - await interaction.edit_original_response( - content="Fehler! Der Termin darf nicht in der Vergangenheit liegen.") - return - await self.add_appointment(interaction.channel, interaction.user.id, date_time, reminder, title, attendees, - str(uuid.uuid4()), description, recurring) - await interaction.edit_original_response(content="Termin erfolgreich erstellt!") + date_time = datetime.strptime(f"{date} {time}", "%d.%m.%Y %H:%M") except ValueError: - await interaction.edit_original_response(content="Fehler! Ungültiges Datums und/oder Zeit Format!") - - def get_embed(self, title: str, organizer: int, description: str, date_time: datetime, reminder: int, - recurring: int, attendees: Dict): - embed = Embed(title=title, - description="Benutze die Buttons unter dieser Nachricht, um dich für Benachrichtigungen zu " - "diesem Termin an- bzw. abzumelden.", - color=19607) - - embed.add_field(name="Erstellt von", value=f"<@{organizer}>", inline=False) - if len(description) > 0: - embed.add_field(name="Beschreibung", value=description, inline=False) - embed.add_field(name="Startzeitpunkt", value=f"{date_time.strftime(self.fmt)}", inline=False) - if reminder > 0: - embed.add_field(name="Benachrichtigung", value=f"{reminder} Minuten vor dem Start", inline=False) - if recurring: - embed.add_field(name="Wiederholung", value=f"Alle {recurring} Tage", inline=False) - embed.add_field(name=f"Teilnehmerinnen ({len(attendees)})", - value=",".join([f"<@{attendee}>" for attendee in attendees.keys()])) - - return embed - - @app_commands.command(name="list", description="Listet alle Termine dieses Channels auf") - async def cmd_appointments(self, interaction: Interaction): - await interaction.response.defer(ephemeral=False) - - if str(interaction.channel.id) in self.appointments: - channel_appointments = self.appointments.get(str(interaction.channel.id)) + await interaction.response.send_message("Fehler! Ungültiges Datums und/oder Zeit Format!", ephemeral=True) + return + + if date_time <= datetime.now(): + await interaction.response.send_message("Fehler! Der Termin liegt in der Vergangenheit!", ephemeral=True) + return + + appointment = Appointment.create(channel=channel.id, message=0, date_time=date_time, reminder=reminder, + title=title, description=description, author=author_id, recurring=recurring, + reminder_sent=reminder == 0, uuid=uuid.uuid4()) + + await interaction.response.send_message(embed=appointment.get_embed(), view=get_view(appointment)) + message = await interaction.original_response() + Appointment.update(message=message.id).where(Appointment.id == appointment.id).execute() + + @app_commands.command(name="list", description="Listet alle Termine dieses Kanals auf.") + @app_commands.describe(show_all="Zeige die Liste für alle an.") + async def cmd_appointments_list(self, interaction: Interaction, show_all: bool = False): + """ List (and link) all Appointments in the current channel """ + await interaction.response.defer(ephemeral=not show_all) + + appointments = Appointment.select().where(Appointment.channel == interaction.channel_id) + if appointments: answer = f'Termine dieses Channels:\n' - delete = [] - for message_id, appointment in channel_appointments.items(): + for appointment in appointments: try: - message = await interaction.channel.fetch_message(int(message_id)) - answer += f'{appointment["date_time"]}: {appointment["title"]} => ' \ + message = await interaction.channel.fetch_message(appointment.message) + answer += f'<t:{int(appointment.date_time.timestamp())}:F>: {appointment.title} => ' \ f'{message.jump_url}\n' except errors.NotFound: - delete.append(message_id) - - if len(delete) > 0: - for key in delete: - channel_appointments.pop(key) - self.save_appointments() - - await interaction.followup.send(answer, ephemeral=False) - else: - await interaction.followup.send("Für diesen Kanal existieren derzeit keine Termine.", ephemeral=True) + appointment.delete_instance(recursive=True) - async def send_or_update_appointment(self, channel, organizer, description, title, date_time, reminder, recurring, - attendees, message=None): - embed = self.get_embed(title, organizer, description, date_time, reminder, recurring, attendees) - if message: - return await message.edit(embed=embed, view=AppointmentView(self)) + await interaction.edit_original_response(content=answer) else: - return await channel.send(embed=embed, view=AppointmentView(self)) - - async def update_legacy_appointments(self): - new_appointments = {} - for channel_id, appointments in self.appointments.items(): - channel_appointments = {} - try: - channel = await self.bot.fetch_channel(int(channel_id)) - - for message_id, appointment in appointments.items(): - if appointment.get("attendees") is not None: - continue - try: - message = await channel.fetch_message(int(message_id)) - title = appointment.get("title") - date_time = appointment.get("date_time") - reminder = appointment.get("reminder") - recurring = appointment.get("recurring") - author_id = appointment.get("author_id") - description = "" - attendees = {} - ics_uuid = str(uuid.uuid4()) - - for reaction in message.reactions: - if reaction.emoji == "ðŸ‘": - async for user in reaction.users(): - if user.id != self.bot.user.id: - attendees[str(user.id)] = 1 - - dt = datetime.strptime(f"{date_time}", self.fmt) - await self.send_or_update_appointment(channel, author_id, description, title, dt, reminder, - recurring, attendees, message=message) - channel_appointments[message_id] = {"date_time": date_time, - "reminder": reminder, - "title": title, - "author_id": author_id, - "recurring": recurring, - "description": description, - "attendees": attendees, - "ics_uuid": ics_uuid} - - except: - pass - except: - pass - - if len(channel_appointments) > 0: - new_appointments[channel_id] = channel_appointments - - self.appointments = new_appointments - self.save_appointments() + await interaction.edit_original_response(content="Für diesen Channel existieren derzeit keine Termine") async def setup(bot: commands.Bot) -> None: - appointments = Appointments(bot) - await bot.add_cog(appointments) - bot.add_view(AppointmentView(appointments)) - await appointments.update_legacy_appointments() + await bot.add_cog(Appointments(bot)) + bot.add_view(AppointmentView()) diff --git a/json_import.py b/json_import.py index 6e8406f..6a1ad1e 100644 --- a/json_import.py +++ b/json_import.py @@ -1,4 +1,6 @@ import json +import uuid +from datetime import datetime import models @@ -15,8 +17,31 @@ def import_links(json_file: str) -> None: models.Link.create(link=link, title=title, category=db_category[0].id) +def import_appointments(json_file: str) -> None: + file = open(json_file, mode="r") + appointments = json.load(file) + + for channel, channel_appointments in appointments.items(): + for message, appointment in channel_appointments.items(): + date_time = datetime.strptime(appointment["date_time"], "%d.%m.%Y %H:%M") + reminder = appointment["reminder"] + title = appointment["title"] + author = appointment["author_id"] + recurring = appointment.get("recurring") if appointment.get("recurring") else 0 + + db_appointment = models.Appointment.get_or_create(channel=int(channel), message=int(message), + date_time=date_time, reminder=reminder, title=title, + description="", author=author, recurring=recurring, + reminder_sent=False, uuid=uuid.uuid4()) + + if appointment.get("attendees"): + for attendee in appointment.get("attendees"): + models.Attendee.create(appointment=db_appointment[0].id, member_id=attendee) + + if __name__ == "__main__": """ Make sure to create a database backup before you import data from json files. """ # import_links("data/links.json") + # import_appointments("data/appointments.json") diff --git a/models.py b/models.py index 71393bc..c7ff3c0 100644 --- a/models.py +++ b/models.py @@ -1,3 +1,7 @@ +import io +import uuid +from datetime import datetime + import discord from peewee import * from peewee import ModelSelect @@ -33,6 +37,9 @@ class LinkCategory(BaseModel): for link in self.links: value += f"- [{link.title}]({link.link})\n" + if len(value) > 1024: + value = value[:1023] + embed.add_field(name=self.name, value=value, inline=False) @@ -42,4 +49,83 @@ class Link(BaseModel): category = ForeignKeyField(LinkCategory, backref='links') -db.create_tables([LinkCategory, Link], safe=True) +class Appointment(BaseModel): + channel = IntegerField() + message = IntegerField() + date_time = DateTimeField() + reminder = IntegerField() + title = CharField() + description = CharField() + author = IntegerField() + recurring = IntegerField() + reminder_sent = BooleanField() + uuid = UUIDField(unique=True) + + def get_embed(self) -> discord.Embed: + attendees = self.attendees + embed = discord.Embed(title=self.title, + description=f"Wenn du eine Benachrichtigung zum Beginn des Termins" + f"{f', sowie {self.reminder} Minuten vorher, ' if self.reminder > 0 else f''}" + f" erhalten möchtest, reagiere mit :thumbsup: auf diese Nachricht.", + color=19607) + + if len(self.description) > 0: + embed.add_field(name="Beschreibung", value=self.description, inline=False) + embed.add_field(name="Startzeitpunkt", value=f"<t:{int(self.date_time.timestamp())}:F>", inline=False) + if self.reminder > 0: + embed.add_field(name="Benachrichtigung", value=f"{self.reminder} Minuten vor dem Start", inline=False) + if self.recurring > 0: + embed.add_field(name="Wiederholung", value=f"Alle {self.recurring} Tage", inline=False) + if len(attendees) > 0: + embed.add_field(name=f"Teilnehmerinnen ({len(attendees)})", + value=",".join([f"<@{attendee.member_id}>" for attendee in attendees])) + + return embed + + def get_ics_file(self) -> io.BytesIO: + fmt: str = "%Y%m%dT%H%M" + appointment: str = f"BEGIN:VCALENDAR\n" \ + f"PRODID:Boty McBotface\n" \ + f"VERSION:2.0\n" \ + f"BEGIN:VTIMEZONE\n" \ + f"TZID:Europe/Berlin\n" \ + f"BEGIN:DAYLIGHT\n" \ + f"TZOFFSETFROM:+0100\n" \ + f"TZOFFSETTO:+0200\n" \ + f"TZNAME:CEST\n" \ + f"DTSTART:19700329T020000\n" \ + f"RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\n" \ + f"END:DAYLIGHT\n" \ + f"BEGIN:STANDARD\n" \ + f"TZOFFSETFROM:+0200\n" \ + f"TZOFFSETTO:+0100\n" \ + f"TZNAME:CET\n" \ + f"DTSTART:19701025T030000\n" \ + f"RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" \ + f"END:STANDARD\n" \ + f"END:VTIMEZONE\n" \ + f"BEGIN:VEVENT\n" \ + f"DTSTAMP:{datetime.now().strftime(fmt)}00Z\n" \ + f"UID:{self.uuid}\n" \ + f"SUMMARY:{self.title}\n" + appointment += f"RRULE:FREQ=DAILY;INTERVAL={self.recurring}\n" if self.recurring else f"" + appointment += f"DTSTART;TZID=Europe/Berlin:{self.date_time.strftime(fmt)}00\n" \ + f"DTEND;TZID=Europe/Berlin:{self.date_time.strftime(fmt)}00\n" \ + f"TRANSP:OPAQUE\n" \ + f"BEGIN:VALARM\n" \ + f"ACTION:DISPLAY\n" \ + f"TRIGGER;VALUE=DURATION:-PT{self.reminder}M\n" \ + f"DESCRIPTION:{self.description}\n" \ + f"END:VALARM\n" \ + f"END:VEVENT\n" \ + f"END:VCALENDAR" + ics_file: io.BytesIO = io.BytesIO(appointment.encode("utf-8")) + return ics_file + + +class Attendee(BaseModel): + appointment = ForeignKeyField(Appointment, backref='attendees') + member_id = IntegerField() + + +db.create_tables([LinkCategory, Link, Appointment, Attendee], safe=True) diff --git a/views/appointment_view.py b/views/appointment_view.py index 8b6ed62..285b775 100644 --- a/views/appointment_view.py +++ b/views/appointment_view.py @@ -1,116 +1,67 @@ -import io -from datetime import datetime +from datetime import timedelta import discord from discord import File import utils - - -def get_ics_file(title, date_time, reminder, recurring, description, ics_uuid): - fmt = "%Y%m%dT%H%M" - appointment = f"BEGIN:VCALENDAR\n" \ - f"PRODID:Boty McBotface\n" \ - f"VERSION:2.0\n" \ - f"BEGIN:VTIMEZONE\n" \ - f"TZID:Europe/Berlin\n" \ - f"BEGIN:DAYLIGHT\n" \ - f"TZOFFSETFROM:+0100\n" \ - f"TZOFFSETTO:+0200\n" \ - f"TZNAME:CEST\n" \ - f"DTSTART:19700329T020000\n" \ - f"RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\n" \ - f"END:DAYLIGHT\n" \ - f"BEGIN:STANDARD\n" \ - f"TZOFFSETFROM:+0200\n" \ - f"TZOFFSETTO:+0100\n" \ - f"TZNAME:CET\n" \ - f"DTSTART:19701025T030000\n" \ - f"RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" \ - f"END:STANDARD\n" \ - f"END:VTIMEZONE\n" \ - f"BEGIN:VEVENT\n" \ - f"DTSTAMP:{datetime.now().strftime(fmt)}00Z\n" \ - f"UID:{ics_uuid}\n" \ - f"SUMMARY:{title}\n" - appointment += f"RRULE:FREQ=DAILY;INTERVAL={recurring}\n" if recurring else f"" - appointment += f"DTSTART;TZID=Europe/Berlin:{date_time.strftime(fmt)}00\n" \ - f"DTEND;TZID=Europe/Berlin:{date_time.strftime(fmt)}00\n" \ - f"TRANSP:OPAQUE\n" \ - f"BEGIN:VALARM\n" \ - f"ACTION:DISPLAY\n" \ - f"TRIGGER;VALUE=DURATION:-PT{reminder}M\n" \ - f"DESCRIPTION:{description}\n" \ - f"END:VALARM\n" \ - f"END:VEVENT\n" \ - f"END:VCALENDAR" - ics_file = io.BytesIO(appointment.encode("utf-8")) - return ics_file +from models import Appointment, Attendee class AppointmentView(discord.ui.View): - def __init__(self, appointments): + def __init__(self): super().__init__(timeout=None) - self.appointments = appointments @discord.ui.button(label='Zusagen', style=discord.ButtonStyle.green, custom_id='appointment_view:accept', emoji="ðŸ‘") - async def accept(self, interaction: discord.Interaction, button: discord.ui.Button): - await interaction.response.defer(ephemeral=True) - if channel_appointments := self.appointments.appointments.get(str(interaction.channel_id)): - if appointment := channel_appointments.get(str(interaction.message.id)): - if attendees := appointment.get("attendees"): - attendees[str(interaction.user.id)] = 1 - self.appointments.save_appointments() - await self.update_appointment(interaction.message, appointment) + async def on_accept(self, interaction: discord.Interaction, button: discord.ui.Button): + if appointment := Appointment.get_or_none(Appointment.message == interaction.message.id): + if attendee := appointment.attendees.filter(member_id=interaction.user.id): + await interaction.response.send_message("Du bist bereits Teilnehmerin dieses Termins.", + ephemeral=True) + return + else: + Attendee.create(appointment=appointment.id, member_id=interaction.user.id) + await interaction.message.edit(embed=appointment.get_embed()) + + await interaction.response.defer(thinking=False) @discord.ui.button(label='Absagen', style=discord.ButtonStyle.red, custom_id='appointment_view:decline', emoji="👎") - async def decline(self, interaction: discord.Interaction, button: discord.ui.Button): - await interaction.response.defer(ephemeral=True) - if channel_appointments := self.appointments.appointments.get(str(interaction.channel_id)): - if appointment := channel_appointments.get(str(interaction.message.id)): - if attendees := appointment.get("attendees"): - if attendees.get(str(interaction.user.id)): - del attendees[str(interaction.user.id)] - self.appointments.save_appointments() - await self.update_appointment(interaction.message, appointment) + async def on_decline(self, interaction: discord.Interaction, button: discord.ui.Button): + if appointment := Appointment.get_or_none(Appointment.message == interaction.message.id): + if attendee := appointment.attendees.filter(member_id=interaction.user.id): + attendee = attendee[0] + attendee.delete_instance() + await interaction.message.edit(embed=appointment.get_embed()) + else: + await interaction.response.send_message("Du kannst nur absagen, wenn du vorher zugesagt hast.", + ephemeral=True) + return + + await interaction.response.defer(thinking=False) + + @discord.ui.button(label='Übersringen', style=discord.ButtonStyle.blurple, custom_id='appointment_view:skip', + emoji="âï¸") + async def on_skip(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer(thinking=False) + if appointment := Appointment.get_or_none(Appointment.message == interaction.message.id): + if interaction.user.id == appointment.author or utils.is_mod(interaction.user): + new_date_time = appointment.date_time + timedelta(days=appointment.recurring) + Appointment.update(date_time=new_date_time, reminder_sent=False).where( + Appointment.id == appointment.id).execute() + updated_appointment = Appointment.get(Appointment.id == appointment.id) + await interaction.message.edit(embed=updated_appointment.get_embed()) + @discord.ui.button(label='Download .ics', style=discord.ButtonStyle.blurple, custom_id='appointment_view:ics', emoji="📅") - async def ics(self, interaction: discord.Interaction, button: discord.ui.Button): - await interaction.response.defer(ephemeral=True) - if channel_appointments := self.appointments.appointments.get(str(interaction.channel_id)): - if appointment := channel_appointments.get(str(interaction.message.id)): - title = appointment.get("title") - date_time = datetime.strptime(appointment.get("date_time"), self.appointments.fmt) - reminder = appointment.get("reminder") - recurring = appointment.get("recurring") - description = appointment.get("description") - ics_uuid = appointment.get("ics_uuid") - file = File(get_ics_file(title, date_time, reminder, recurring, description, ics_uuid), - filename=f"{appointment.get('title')}_{appointment.get('ics_uuid')}.ics") - await interaction.followup.send(file=file, ephemeral=True) + async def on_ics(self, interaction: discord.Interaction, button: discord.ui.Button): + if appointment := Appointment.get_or_none(Appointment.message == interaction.message.id): + await interaction.response.send_message("", file=File(appointment.get_ics_file(), + filename=f"{appointment.title}.ics"), ephemeral=True) @discord.ui.button(label='Löschen', style=discord.ButtonStyle.gray, custom_id='appointment_view:delete', emoji="🗑") - async def delete(self, interaction: discord.Interaction, button: discord.ui.Button): - await interaction.response.defer(ephemeral=True) - if channel_appointments := self.appointments.appointments.get(str(interaction.channel_id)): - if appointment := channel_appointments.get(str(interaction.message.id)): - if appointment.get("author_id") == interaction.user.id or utils.is_mod(interaction.user): - await interaction.followup.send(f"Termin {appointment.get('title')} gelöscht.", ephemeral=True) - await interaction.message.delete() - del channel_appointments[str(interaction.message.id)] - self.appointments.save_appointments() - - async def update_appointment(self, message, appointment): - channel = message.channel - message = message - author_id = appointment.get("author_id") - description = appointment.get("description") - title = appointment.get("title") - date_time = datetime.strptime(appointment.get("date_time"), self.appointments.fmt) - reminder = appointment.get("reminder") - recurring = appointment.get("recurring") - attendees = appointment.get("attendees") - - await self.appointments.send_or_update_appointment(channel, author_id, description, title, date_time, reminder, - recurring, attendees, message=message) + async def on_delete(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer(thinking=False) + if appointment := Appointment.get_or_none(Appointment.message == interaction.message.id): + if interaction.user.id == appointment.author or utils.is_mod(interaction.user): + appointment.delete_instance(recursive=True) + await interaction.message.delete() -- GitLab From 073b86ef54d9c4f824320e1ceb4314f116cdc01f Mon Sep 17 00:00:00 2001 From: dnns01 <git@dnns01.de> Date: Tue, 17 Sep 2024 23:49:01 +0200 Subject: [PATCH 2/2] Fix skip functionality --- extensions/appointments.py | 8 ++++---- views/appointment_view.py | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/extensions/appointments.py b/extensions/appointments.py index 1b6bcae..4eee611 100644 --- a/extensions/appointments.py +++ b/extensions/appointments.py @@ -18,7 +18,7 @@ async def send_notification(appointment, channel): if appointment.reminder_sent: return await channel.send(message, embed=appointment.get_embed(2)) - return await channel.send(message, embed=appointment.get_embed(1), view=AppointmentView()) + return await channel.send(message, embed=appointment.get_embed(1), view=AppointmentView(can_skip=appointment.recurring > 0)) @app_commands.guild_only() @@ -50,7 +50,7 @@ class Appointments(commands.GroupCog, name="appointments", description="Handle A Appointment.id == appointment.id).execute() updated_appointment = Appointment.get(Appointment.id == appointment.id) new_message = await channel.send(embed=updated_appointment.get_embed(0), - view=AppointmentView()) + view=AppointmentView(can_skip=appointment.recurring > 0)) Appointment.update(message=new_message.id).where(Appointment.id == appointment.id).execute() else: Appointment.update(reminder_sent=True).where(Appointment.id == appointment.id).execute() @@ -98,7 +98,7 @@ class Appointments(commands.GroupCog, name="appointments", description="Handle A reminder_sent=reminder == 0, uuid=uuid.uuid4()) Attendee.create(appointment=appointment, member_id=author_id) - await interaction.response.send_message(embed=appointment.get_embed(0), view=AppointmentView()) + await interaction.response.send_message(embed=appointment.get_embed(0), view=AppointmentView(can_skip=appointment.recurring > 0)) message = await interaction.original_response() Appointment.update(message=message.id).where(Appointment.id == appointment.id).execute() @@ -127,4 +127,4 @@ class Appointments(commands.GroupCog, name="appointments", description="Handle A async def setup(bot: commands.Bot) -> None: await bot.add_cog(Appointments(bot)) - bot.add_view(AppointmentView()) + bot.add_view(AppointmentView(can_skip=True)) diff --git a/views/appointment_view.py b/views/appointment_view.py index e3faa4a..724e066 100644 --- a/views/appointment_view.py +++ b/views/appointment_view.py @@ -1,12 +1,16 @@ +from datetime import timedelta + import discord from discord import File +import utils from models import Appointment, Attendee class AppointmentView(discord.ui.View): - def __init__(self): + def __init__(self, can_skip: bool): super().__init__(timeout=None) + self.on_skip.disabled = not can_skip @discord.ui.button(label='Anmelden', style=discord.ButtonStyle.green, custom_id='appointment_view:accept', emoji="ðŸ‘") async def accept(self, interaction: discord.Interaction, button: discord.ui.Button): @@ -37,7 +41,7 @@ class AppointmentView(discord.ui.View): await interaction.response.defer(thinking=False) - @discord.ui.button(label='Übersringen', style=discord.ButtonStyle.blurple, custom_id='appointment_view:skip', + @discord.ui.button(label='Überspringen', style=discord.ButtonStyle.blurple, custom_id='appointment_view:skip', emoji="âï¸") async def on_skip(self, interaction: discord.Interaction, button: discord.ui.Button): await interaction.response.defer(thinking=False) @@ -47,7 +51,7 @@ class AppointmentView(discord.ui.View): Appointment.update(date_time=new_date_time, reminder_sent=False).where( Appointment.id == appointment.id).execute() updated_appointment = Appointment.get(Appointment.id == appointment.id) - await interaction.message.edit(embed=updated_appointment.get_embed()) + await interaction.message.edit(embed=updated_appointment.get_embed(1 if updated_appointment.reminder_sent and updated_appointment.reminder > 0 else 0)) @discord.ui.button(label='Download .ics', style=discord.ButtonStyle.blurple, custom_id='appointment_view:ics', -- GitLab