diff --git a/.gitignore b/.gitignore index f8bf16a7ed6c30a03ecf31eea02e7cfc6829c5a7..44289c7837349705b2040886442766c358f8e020 100755 --- a/.gitignore +++ b/.gitignore @@ -115,5 +115,6 @@ GitHub.sublime-settings !.vscode/extensions.json .history +/data/*.json /data/ /xanathar.db diff --git a/data/config_template.json b/data/config_template.json new file mode 100644 index 0000000000000000000000000000000000000000..99babc00727442342ce2b85064cf689f3c9dbe69 --- /dev/null +++ b/data/config_template.json @@ -0,0 +1,73 @@ +{ + "token": "TOKEN", + "activity": "Aktivity", + "mod_roles": [ + 00, + 00 + ], + "extensions": { + "poll": {}, + "appointments": {}, + "welcome": { + "greeting_channel": 00, + "introduction_channel": 00, + "offtopic_channel": 00, + "unitalk_channel": 00, + "announcement_channel": 00, + "uninews_channel": 00, + "": , + "greeting_on_join": true, + "dm_message": "00", + "greeting_messages": [ + "Willkommen <@{user_id}> auf dem Discordserver 00! :partying_face:", + "Hi <@{user_id}>, herzlich willkommen! :hugging:", + "Hey <@{user_id}>, hast du Kuchen mitgebracht? :cake:", + "Hey <@{user_id}> ist da! :partying_face:", + "Moin <@{user_id}> und willkommen!", + "Hi <@{user_id}>, bei <id:customize> kannst du dir verschiedene Server-Rollen vergeben :placard:", + "Hi <@{user_id}>, bei <id:browse> kannst du deine Kanalliste anpassen: so wird sie genau so lang oder kurz, wie es dir am besten passt :bulb:", + "Hey <@{user_id}>, hast du bei <id:browse> schon vorbei geschaut? Dort kannst du auswählen, welche Kanäle für dich sichtbar sind!", + "Hi <@{user_id}>, *wusstest du*, dass du mit Rechtsklick Kanäle zu deinen Favoriten hinzufügen kannst? So behälst du alles Wichtige gut im Blick!", + "Moin <@{user_id}>, in <#{announcement_channel}> gibt es immer wieder nützliche Infos :notepad_spiral: Schau gerne vorbei!", + "Willkommen <@{user_id}>, hast du den <#{offtopic_channel}>-Kanal schon entdeckt? Dort kann man über alles reden, was nicht studienspezifisch ist :teapot: ", + ":wave: <@{user_id}>, erzähl gerne etwas über dich in <#{introduction_channel}>.", + "Hallo <@{user_id}>, ich bin der hausgemachte Server-Bot! Mach es dir gemütlich und zögere nicht, mir per privaten Nachricht Fragen zu stellen, wenn du Hilfe vom Support-Team brauchst :love_letter:", + "Willkommen <@{user_id}>, stehst du am Anfang des Studiums an der FernUni? Die Ersti-Rollen in <id:customize> und der <#{ersti_channel}> könnten dich interessieren!", + "Hey <@{user_id}>! Im Channel <#{unitalk_channel}> kannst du dich mit Kommilitoninnen über Themen rund um das Studium unterhalten :student:" + ] + }, + "links": {}, + "pomodoro": { + "default_names": [ + "Noam Chomsky", + "Leonardo da Vinci", + "René Descartes", + "Hypatia von Alexandria", + "Umberto Eco", + "Hannah Arendt", + "Jane Austen", + "Simone de Beauvoir", + "Astrid Lindgren", + "Clinton Richard Dawkins", + "Daniel Kahneman", + "Judith Rich Harris", + "Aristoteles", + "Platon", + "Immanuel Kant" + ] + }, + "mod_mail": { + "channel_id": 00 + }, + "text_commands": { + "mod_channel": 00 + }, + "news":{ + "url": "00", + "channel_id": 00, + "news_role": 00, + "announcement_text": ":loudspeaker: <@&{news_role}> Neues aus der Fakultät vom {date} :loudspeaker: \n{title} \n{link}" + } + + } +} diff --git a/extensions/appointments.py b/extensions/appointments.py index c902fdab4bde0f7a47ed69e60ed4881239450132..96b5643b0394320c886a8fe12ed5509fb72bc272 100755 --- a/extensions/appointments.py +++ b/extensions/appointments.py @@ -1,7 +1,6 @@ import asyncio import uuid from datetime import datetime, timedelta -import os from discord import app_commands, errors, Interaction from discord.ext import tasks, commands @@ -11,24 +10,21 @@ from views.appointment_view import AppointmentView async def send_notification(appointment, channel): - message = f"Benachrichtigung!\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"Erinnerung!" message += f"\n" message += " ".join([f"<@!{str(attendee.member_id)}>" for attendee in appointment.attendees]) - await channel.send(message) + 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()) @app_commands.guild_only() 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() @tasks.loop(minutes=1) @@ -43,11 +39,11 @@ class Appointments(commands.GroupCog, name="appointments", description="Handle A try: channel = await self.bot.fetch_channel(appointment.channel) message = await channel.fetch_message(appointment.message) - await send_notification(appointment, channel) + new_message = await send_notification(appointment, channel) + Appointment.update(message=new_message.id).where(Appointment.id == appointment.id).execute() + await message.delete() if appointment.reminder_sent: - await message.delete() - if appointment.recurring == 0: appointment.delete_instance(recursive=True) else: @@ -56,7 +52,7 @@ class Appointments(commands.GroupCog, name="appointments", description="Handle A 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(), + new_message = await channel.send(embed=updated_appointment.get_embed(0), view=AppointmentView()) Appointment.update(message=new_message.id).where(Appointment.id == appointment.id).execute() else: @@ -80,7 +76,7 @@ class Appointments(commands.GroupCog, name="appointments", description="Handle A channel = interaction.channel author_id = interaction.user.id try: - date_time = datetime.strptime(f"{date} {time}", self.fmt) + date_time = datetime.strptime(f"{date} {time}", self.bot.dt_format()) except ValueError: await channel.send("Fehler! Ungültiges Datums und/oder Zeit Format!") return @@ -89,7 +85,7 @@ class Appointments(commands.GroupCog, name="appointments", description="Handle A 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=AppointmentView()) + await interaction.response.send_message(embed=appointment.get_embed(0), view=AppointmentView()) message = await interaction.original_response() Appointment.update(message=message.id).where(Appointment.id == appointment.id).execute() diff --git a/extensions/text_commands.py b/extensions/text_commands.py new file mode 100644 index 0000000000000000000000000000000000000000..b119b30f2781d16f7c87241e5856447cdbd69f3d --- /dev/null +++ b/extensions/text_commands.py @@ -0,0 +1,187 @@ +import random +import re + +import discord +from discord import app_commands, Interaction +from discord.ext import commands + +import utils +from modals.text_command_modal import TextCommandModal +from models import Command, CommandText +from views.text_command_view import TextCommandView + + +@app_commands.guild_only() +class TextCommands(commands.GroupCog, name="commands", description="Text Commands auflisten und verwalten"): + def __init__(self, bot): + self.bot = bot + + @app_commands.command(name="list", description="Listet die Text Commands dieses Servers auf.") + async def cmd_list(self, interaction: Interaction, cmd: str = None): + await interaction.response.defer(ephemeral=True) + items = [] + if cmd: + if command := Command.get_or_none(Command.command == cmd): + items = [command_text.text for command_text in command.texts] + + if len(items) == 0: + await interaction.edit_original_response(content=f"{cmd} ist kein verfügbares Text-Command") + return + else: + for command in Command.select(): + if command.texts.count() > 0: + items.append(command.command) + + answer = f"Text Commands:\n" if cmd is None else f"Für {cmd} hinterlegte Texte:\n" + first = True + for i, item in enumerate(items): + if len(answer) + len(item) > 2000: + if first: + await interaction.edit_original_response(content=answer) + first = False + else: + await interaction.followup.send(answer, ephemeral=True) + answer = f"" + + answer += f"{i}: {item}\n" + + if first: + await interaction.edit_original_response(content=answer) + else: + await interaction.followup.send(answer, ephemeral=True) + + @app_commands.command(name="add", + description="Ein neues Text Command hinzufügen, oder zu einem bestehenden einen weiteren Text hinzufügen") + @app_commands.describe(cmd="Command. Bsp: \"link\" für das Command \"/link\".", + text="Text, der bei Benutzung des Commands ausgegeben werden soll.") + async def cmd_add(self, interaction: Interaction, cmd: str, text: str): + if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", cmd): + await interaction.response.send_message( + "Ein Command darf nur aus Kleinbuchstaben und Zahlen bestehen, die durch Bindestriche getrennt werden können.", + ephemeral=True) + return + + command = Command.get_or_none(Command.command == cmd) + description = command.description if command else "" + await interaction.response.send_modal( + TextCommandModal(text_commands=self, cmd=cmd, text=text, description=description)) + + @app_commands.command(name="edit", description="Bearbeite bestehende Text Commands") + @app_commands.describe(cmd="Command, dass du bearbeiten möchtest", id="ID des zu bearbeitenden Texts", + text="Neuer Text, der statt des alten ausgegeben werden soll.") + async def cmd_edit(self, interaction: Interaction, cmd: str, id: int, text: str): + await interaction.response.defer(ephemeral=True) + + if not utils.is_mod(interaction.user, self.bot): + await interaction.edit_original_response(content="Du hast nicht die notwendigen Berechtigungen, " + "um dieses Command zu benutzen!") + return + + if command := Command.get_or_none(Command.command == cmd): + command_texts = list(command.texts) + if 0 <= id < len(command_texts): + CommandText.update(text=text).where(CommandText.id == command_texts[id].id).execute() + await interaction.edit_original_response( + content=f"Text {id} für Command {cmd} wurde erfolgreich geändert") + else: + await interaction.edit_original_response(content="Ungültiger Index") + else: + await interaction.edit_original_response(content=f"Command `{cmd}` nicht vorhanden!") + + @app_commands.command(name="remove", + description="Entferne ein gesamtes Command oder einen einzelnen Text von einem Command.") + @app_commands.describe(cmd="Command, dass du entfernen möchtest, oder von dem du einen Text entfernen möchtest.", + id="ID des zu entfernenden Texts.") + async def cmd_command_remove(self, interaction: Interaction, cmd: str, id: int = None): + await interaction.response.defer(ephemeral=True) + + if not utils.is_mod(interaction.user, self.bot): + await interaction.edit_original_response(content="Du hast nicht die notwendigen Berechtigungen, " + "um dieses Command zu benutzen!") + return + + if command := Command.get_or_none(Command.command == cmd): + if id is None: + await self.remove_command(command) + await interaction.edit_original_response(content=f"Text Command `{cmd}` wurde erfolgreich entfernt.") + else: + command_texts = list(command.texts) + if 0 <= id < len(command_texts): + await self.remove_text(command, command_texts, id) + await interaction.edit_original_response( + content=f"Text {id} für Command `{cmd}` wurde erfolgreich entfernt") + else: + await interaction.edit_original_response(content=f"Ungültiger Index") + else: + await interaction.edit_original_response(content=f"Command `{cmd}` nicht vorhanden!") + + async def add_command(self, cmd: str, text: str, description: str, guild_id: int): + mod_channel_id = self.bot.get_settings(guild_id).modmail_channel_id + mod_channel = await self.bot.fetch_channel(mod_channel_id) + if command := Command.get_or_none(Command.command == cmd): + CommandText.create(text=text, command=command.id) + if command.description != description: + Command.update(description=description).where(Command.id == command.id).execute() + self.bot.tree.get_command(command.command).description = description + await self.bot.sync_slash_commands_for_guild(command.guild_id) + await mod_channel.send(f"Beschreibung von Command `{cmd}` geändert zu `{description}`") + else: + if self.exists(cmd): + return False + command = Command.create(command=cmd, description=description, guild_id=guild_id) + CommandText.create(text=text, command=command.id) + await self.register_command(command) + + await mod_channel.send(f"[{cmd}] => [{text}] erfolgreich hinzugefügt.") + return True + + async def remove_text(self, command, command_texts, id): + command_text = list(command_texts)[id] + command_text.delete_instance(recursive=True) + if command.texts.count() == 0: + await self.remove_command(command) + + async def remove_command(self, command: Command): + await self.unregister_command(command) + command.delete_instance(recursive=True) + + def exists(self, cmd): + for command in self.bot.tree.get_commands(): + if command.name == cmd: + return True + + return False + + async def init_commands(self): + for command in Command.select(): + if command.texts.count() > 0: + await self.register_command(command, sync=False) + + async def register_command(self, command: Command, sync: bool = True): + @app_commands.command(name=command.command, description=command.description) + @app_commands.guild_only() + @app_commands.describe(public="Zeige die Ausgabe des Commands öffentlich, für alle Mitglieder sichtbar.") + async def process_command(interaction: Interaction, public: bool): + await interaction.response.defer(ephemeral=not public) + if cmd := Command.get_or_none(Command.command == interaction.command.name): + texts = list(cmd.texts) + if len(texts) > 0: + await interaction.edit_original_response(content=(random.choice(texts)).text) + return + + await interaction.edit_original_response(content="FEHLER! Command wurde nicht gefunden!") + + self.bot.tree.add_command(process_command) + if sync: + await self.bot.sync_slash_commands_for_guild(command.guild_id) + + async def unregister_command(self, command: Command): + self.bot.tree.remove_command(command.command) + await self.bot.sync_slash_commands_for_guild(command.guild_id) + + +async def setup(bot: commands.Bot) -> None: + text_commands = TextCommands(bot) + await bot.add_cog(text_commands) + await text_commands.init_commands() + bot.add_view(TextCommandView(text_commands))