diff --git a/.gitignore b/.gitignore
index d13be1bc2bedbe9814a6312e62bd7b2d00a25391..f8bf16a7ed6c30a03ecf31eea02e7cfc6829c5a7 100755
--- a/.gitignore
+++ b/.gitignore
@@ -116,3 +116,4 @@ GitHub.sublime-settings
 .history
 
 /data/
+/xanathar.db
diff --git a/cogs/appointments.py b/cogs/appointments.py
deleted file mode 100755
index a3737aae970ca6f1230dd15b9d10665358f2e67b..0000000000000000000000000000000000000000
--- a/cogs/appointments.py
+++ /dev/null
@@ -1,244 +0,0 @@
-import asyncio
-import datetime
-import io
-import json
-import os
-import uuid
-
-from discord import app_commands, errors, Embed, File, Interaction
-from discord.ext import tasks, commands
-
-import utils
-
-
-def get_ics_file(title, date_time, reminder, recurring):
-    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.datetime.now().strftime(fmt)}00Z\n" \
-                  f"UID:{uuid.uuid4()}\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:Halloooo, dein Termin findest bald statt!!!!\n" \
-                   f"END:VALARM\n" \
-                   f"END:VEVENT\n" \
-                   f"END:VCALENDAR"
-    ics_file = io.BytesIO(appointment.encode("utf-8"))
-    return ics_file
-
-
-@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()
-        self.appointments = {}
-        self.app_file = "data/appointments.json"
-        self.load_appointments()
-
-    def load_appointments(self):
-        """ Loads all appointments from APPOINTMENTS_FILE """
-
-        appointments_file = open(self.app_file, mode='r')
-        self.appointments = json.load(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.datetime.now()
-                date_time = datetime.datetime.strptime(appointment["date_time"], self.fmt)
-                remind_at = date_time - datetime.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"] = reminder
-                            appointment["reminder"] = 0
-                        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.datetime.strptime(date_time_str, self.fmt)
-                            new_date_time = date_time + datetime.timedelta(days=recurring)
-                            new_date_time_str = new_date_time.strftime(self.fmt)
-                            splitted_new_date_time_str = new_date_time_str.split(" ")
-                            reminder = channel_appointment.get("original_reminder")
-                            reminder = reminder if reminder else 0
-                            await self.add_appointment(channel, channel_appointment["author_id"],
-                                                       splitted_new_date_time_str[0],
-                                                       splitted_new_date_time_str[1],
-                                                       reminder,
-                                                       channel_appointment["title"],
-                                                       channel_appointment["recurring"])
-                        channel_appointments.pop(key)
-                self.save_appointments()
-
-    @timer.before_loop
-    async def before_timer(self):
-        await asyncio.sleep(60 - datetime.datetime.now().second)
-
-    @app_commands.command(name="add", description="Füge dem Kanal einen neuen Termin hinzu.")
-    @app_commands.describe(date="Tag des Termins", time="Uhrzeit des Termins",
-                           reminder="Wie viele Minuten bevor der Termin startet, soll eine Erinnerung verschickt werden?",
-                           title="Titel des Termins",
-                           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,
-                                  recurring: int = None):
-        """ Add an appointment to a channel """
-        await self.add_appointment(interaction.channel, interaction.user.id, date, time, reminder, title, recurring)
-        await interaction.response.send_message("Termin erfolgreich erstellt!", ephemeral=True)
-
-    async def add_appointment(self, channel, author_id, date, time, reminder, title, recurring: int = None):
-        """ Add appointment to a channel """
-
-        try:
-            date_time = datetime.datetime.strptime(f"{date} {time}", self.fmt)
-        except ValueError:
-            await channel.send("Fehler! Ungültiges Datums und/oder Zeit Format!")
-            return
-
-        embed = Embed(title="Neuer Termin hinzugefügt!",
-                      description=f"Wenn du eine Benachrichtigung zum Beginn des Termins"
-                                  f"{f', sowie {reminder} Minuten vorher, ' if reminder > 0 else f''} "
-                                  f"erhalten möchtest, reagiere mit :thumbsup: auf diese Nachricht.",
-                      color=19607)
-
-        embed.add_field(name="Titel", value=title, 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)
-
-        message = await channel.send(embed=embed, file=File(get_ics_file(title, date_time, reminder, recurring),
-                                                            filename=f"{title}.ics"))
-        await message.add_reaction("👍")
-        await message.add_reaction("🗑️")
-
-        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}
-
-        self.save_appointments()
-
-    @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 """
-
-        if str(interaction.channel.id) in self.appointments:
-            channel_appointments = self.appointments.get(str(interaction.channel.id))
-            answer = f'Termine dieses Channels:\n'
-            delete = []
-
-            for message_id, appointment in channel_appointments.items():
-                try:
-                    message = await interaction.channel.fetch_message(int(message_id))
-                    answer += f'{appointment["date_time"]}: {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.response.send_message(answer, ephemeral=not show_all)
-        else:
-            await interaction.response.send_message("Für diesen Channel existieren derzeit keine Termine",
-                                                    ephemeral=not show_all)
-
-    def save_appointments(self):
-        appointments_file = open(self.app_file, mode='w')
-        json.dump(self.appointments, appointments_file)
-
-    async def handle_reactions(self, payload):
-        channel = await self.bot.fetch_channel(payload.channel_id)
-        channel_appointments = self.appointments.get(str(payload.channel_id))
-        if channel_appointments:
-            appointment = channel_appointments.get(str(payload.message_id))
-            if appointment:
-                if payload.user_id == appointment["author_id"]:
-                    message = await channel.fetch_message(payload.message_id)
-                    await message.delete()
-                    channel_appointments.pop(str(payload.message_id))
-
-        self.save_appointments()
-
-    @commands.Cog.listener()
-    async def on_raw_reaction_add(self, payload):
-        if payload.user_id == self.bot.user.id:
-            return
-
-        if payload.emoji.name in ["🗑️"]:
-            channel = await self.bot.fetch_channel(payload.channel_id)
-            message = await channel.fetch_message(payload.message_id)
-            if len(message.embeds) > 0 and message.embeds[0].title == "Neuer Termin hinzugefügt!":
-                await self.handle_reactions(payload)
-
-
-async def setup(bot: commands.Bot) -> None:
-    await bot.add_cog(Appointments(bot))
diff --git a/cogs/components/poll/poll.py b/cogs/components/poll/poll.py
deleted file mode 100755
index 38248ff4391b06c5e99a605e2c2913f7932d6e8a..0000000000000000000000000000000000000000
--- a/cogs/components/poll/poll.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import discord
-import emoji
-
-DEFAULT_OPTIONS = ["🇦", "🇧", "🇨", "🇩", "🇪", "🇫", "🇬", "🇭", "🇮", "🇯", "🇰", "🇱", "🇲", "🇳", "🇴", "🇵", "🇶",
-                   "🇷", "🇸", "🇹"]
-DELETE_POLL = "🗑️"
-CLOSE_POLL = "🛑"
-
-
-def get_unique_option(options):
-    for option in DEFAULT_OPTIONS:
-        if option not in options:
-            return option
-
-
-def get_options(bot, answers):
-    options = []
-
-    for i in range(min(len(answers), len(DEFAULT_OPTIONS))):
-        option = ""
-        answer = answers[i].strip()
-        index = answer.find(" ")
-
-        if index > -1:
-            possible_option = answer[:index]
-            if emoji.is_emoji(possible_option):
-                if len(answer[index:].strip()) > 0:
-                    option = possible_option
-                    answers[i] = answer[index:].strip()
-            elif len(possible_option) > 1:
-                if (possible_option[0:2] == "<:" or possible_option[0:3] == "<a:") and possible_option[-1] == ">":
-                    splitted_custom_emoji = possible_option.strip("<a:>").split(":")
-                    if len(splitted_custom_emoji) == 2:
-                        id = splitted_custom_emoji[1]
-                        custom_emoji = bot.get_emoji(int(id))
-                        if custom_emoji and len(answer[index:].strip()) > 0:
-                            option = custom_emoji
-                            answers[i] = answer[index:].strip()
-
-        if (isinstance(option, str) and len(option) == 0) or option in options or option in [DELETE_POLL,
-                                                                                             CLOSE_POLL]:
-            option = get_unique_option(options)
-        options.append(option)
-
-    return options
-
-
-class Poll:
-    def __init__(self, bot, question=None, answers=None, author=None, message=None):
-        self.bot = bot
-        self.question = question
-        self.answers = answers
-        self.author = author
-
-        if message:
-            self.message = message
-            self.answers = []
-            embed = message.embeds[0]
-            self.author = embed.fields[0].value[3:-1]
-            self.question = embed.description
-            for i in range(2, len(embed.fields)):
-                self.answers.append(f"{embed.fields[i].name} {embed.fields[i].value}")
-
-        self.options = get_options(self.bot, self.answers)
-
-    async def send_poll(self, interaction, result=False, message=None):
-        option_ctr = 0
-        title = "Umfrage"
-        participants = {}
-
-        if result:
-            title += " Ergebnis"
-
-        if len(self.answers) > len(DEFAULT_OPTIONS):
-            await interaction.response.send_message(
-                f"Fehler beim Erstellen der Umfrage! Es werden nicht mehr als {len(DEFAULT_OPTIONS)} Optionen unterstützt!", ephemera=True)
-            return
-
-        embed = discord.Embed(title=title, description=self.question)
-        embed.add_field(name="Erstellt von", value=f'<@!{self.author}>', inline=False)
-        embed.add_field(name="\u200b", value="\u200b", inline=False)
-
-        for i in range(0, len(self.answers)):
-            name = f'{self.options[i]}'
-            value = f'{self.answers[i]}'
-
-            if result:
-                reaction = self.get_reaction(name)
-                if reaction:
-                    name += f' : {reaction.count - 1}'
-                    async for user in reaction.users():
-                        if user != self.bot.user:
-                            participants[str(user.id)] = 1
-
-            embed.add_field(name=name, value=value, inline=False)
-            option_ctr += 1
-
-        if result:
-            embed.add_field(name="\u200b", value="\u200b", inline=False)
-            embed.add_field(name="Anzahl Teilnehmer an der Umfrage", value=f"{len(participants)}", inline=False)
-
-        if message:
-            await message.edit(embed=embed)
-        else:
-            message = await interaction.response.send_message("", embed=embed)
-
-    async def close_poll(self):
-        await self.send_poll(self.message.channel, result=True)
-        await self.delete_poll()
-
-    async def delete_poll(self):
-        await self.message.delete()
-
-    def get_reaction(self, reaction):
-        if self.message:
-            reactions = self.message.reactions
-
-            for react in reactions:
-                if react.emoji == reaction:
-                    return react
diff --git a/cogs/polls.py b/cogs/polls.py
deleted file mode 100755
index 0e6d60fd8d3f7ca5a15bd1b34cd7f7111f07b634..0000000000000000000000000000000000000000
--- a/cogs/polls.py
+++ /dev/null
@@ -1,123 +0,0 @@
-import discord
-from discord import app_commands, Interaction
-from discord.ext import commands
-
-from cogs.components.poll.poll import Poll
-
-
-@app_commands.guild_only()
-class Polls(commands.GroupCog, name="poll", description="Handle Polls in Channels"):
-    def __init__(self, bot):
-        self.bot = bot
-
-    @app_commands.command(name="add", description="Erstelle eine Umfrage mit bis zu 20 Antwortmöglichkeiten.")
-    @app_commands.describe(question="Welche Frage möchtest du stellen?", choice_a="1. Antwortmöglichkeit",
-                           choice_b="2. Antwortmöglichkeit", choice_c="3. Antwortmöglichkeit",
-                           choice_d="4. Antwortmöglichkeit", choice_e="5. Antwortmöglichkeit",
-                           choice_f="6. Antwortmöglichkeit", choice_g="7. Antwortmöglichkeit",
-                           choice_h="8. Antwortmöglichkeit", choice_i="9. Antwortmöglichkeit",
-                           choice_j="10. Antwortmöglichkeit", choice_k="11. Antwortmöglichkeit",
-                           choice_l="12. Antwortmöglichkeit", choice_m="13. Antwortmöglichkeit",
-                           choice_n="14. Antwortmöglichkeit", choice_o="15. Antwortmöglichkeit",
-                           choice_p="16. Antwortmöglichkeit", choice_q="17. Antwortmöglichkeit",
-                           choice_r="18. Antwortmöglichkeit", choice_s="19. Antwortmöglichkeit",
-                           choice_t="20. Antwortmöglichkeit")
-    async def cmd_poll(self, interaction: Interaction, question: str, choice_a: str, choice_b: str,
-                       choice_c: str = None, choice_d: str = None, choice_e: str = None, choice_f: str = None,
-                       choice_g: str = None, choice_h: str = None, choice_i: str = None, choice_j: str = None,
-                       choice_k: str = None, choice_l: str = None, choice_m: str = None, choice_n: str = None,
-                       choice_o: str = None, choice_p: str = None, choice_q: str = None, choice_r: str = None,
-                       choice_s: str = None, choice_t: str = None):
-        """ Create a new poll """
-        choices = [choice for choice in
-                   [choice_a, choice_b, choice_c, choice_d, choice_e, choice_f, choice_g, choice_h, choice_i, choice_j,
-                    choice_k, choice_l, choice_m, choice_n, choice_o, choice_p, choice_q, choice_r, choice_s, choice_t]
-                   if choice]
-        await Poll(self.bot, question, choices, interaction.user.id).send_poll(interaction)
-        # view = DropdownView()
-        # await ctx.send("", view=view)
-        await interaction.response.send_message("")
-
-    async def cmd_edit_poll(self, ctx, message_id, question, *answers):
-        message = await ctx.fetch_message(message_id)
-        if message:
-            if message.embeds[0].title == "Umfrage":
-                old_poll = Poll(self.bot, message=message)
-                new_poll = Poll(self.bot, question=question, answers=list(answers), author=old_poll.author)
-                await new_poll.send_poll(ctx.channel, message=message)
-        else:
-            ctx.send("Fehler! Umfrage nicht gefunden!")
-        pass
-
-    @commands.Cog.listener()
-    async def on_raw_reaction_add(self, payload):
-        if payload.user_id == self.bot.user.id:
-            return
-
-        if payload.emoji.name in ["🗑️", "🛑"]:
-            channel = await self.bot.fetch_channel(payload.channel_id)
-            message = await channel.fetch_message(payload.message_id)
-            if len(message.embeds) > 0 and message.embeds[0].title == "Umfrage":
-                poll = Poll(self.bot, message=message)
-                if str(payload.user_id) == poll.author:
-                    if payload.emoji.name == "🗑️":
-                        await poll.delete_poll()
-                    else:
-                        await poll.close_poll()
-
-
-async def setup(bot: commands.Bot) -> None:
-    await bot.add_cog(Polls(bot))
-
-
-class Dropdown(discord.ui.Select):
-    def __init__(self):
-        # Set the options that will be presented inside the dropdown
-        options = [
-            discord.SelectOption(label='Red', description='Your favourite colour is red'),
-            discord.SelectOption(label='Green', description='Your favourite colour is green'),
-            discord.SelectOption(label='Blue', description='Your favourite colour is blue'),
-            discord.SelectOption(label='1', description='Your favourite colour is blue'),
-            discord.SelectOption(label='2', description='Your favourite colour is blue'),
-            discord.SelectOption(label='3', description='Your favourite colour is blue'),
-            discord.SelectOption(label='4', description='Your favourite colour is blue'),
-            discord.SelectOption(label='5', description='Your favourite colour is blue'),
-            discord.SelectOption(label='7', description='Your favourite colour is blue'),
-            discord.SelectOption(label='6', description='Your favourite colour is blue'),
-            discord.SelectOption(label='8', description='Your favourite colour is blue'),
-            discord.SelectOption(label='9', description='Your favourite colour is blue'),
-            discord.SelectOption(label='0', description='Your favourite colour is blue'),
-            discord.SelectOption(label='10', description='Your favourite colour is blue'),
-            discord.SelectOption(label='11', description='Your favourite colour is blue'),
-            discord.SelectOption(label='12', description='Your favourite colour is blue'),
-            discord.SelectOption(label='13', description='Your favourite colour is blue'),
-            discord.SelectOption(label='14', description='Your favourite colour is blue'),
-            discord.SelectOption(label='15', description='Your favourite colour is blue'),
-            discord.SelectOption(label='16', description='Your favourite colour is blue'),
-            discord.SelectOption(label='17', description='Your favourite colour is blue'),
-            discord.SelectOption(label='18', description='Your favourite colour is blue'),
-            discord.SelectOption(label='19', description='Your favourite colour is blue'),
-            discord.SelectOption(label='20', description='Your favourite colour is blue'),
-            discord.SelectOption(label='21', description='Your favourite colour is blue'),
-
-        ]
-
-        # The placeholder is what will be shown when no option is chosen
-        # The min and max values indicate we can only pick one of the three options
-        # The options parameter defines the dropdown options. We defined this above
-        super().__init__(placeholder='Choose your favourite colour...', min_values=1, max_values=1, options=options)
-
-    async def callback(self, interaction: discord.Interaction):
-        # Use the interaction object to send a response message containing
-        # the user's favourite colour or choice. The self object refers to the
-        # Select object, and the values attribute gets a list of the user's
-        # selected options. We only want the first one.
-        await interaction.response.send_message(f'Your favourite colour is {self.values[0]}')
-
-
-class DropdownView(discord.ui.View):
-    def __init__(self):
-        super().__init__()
-
-        # Adds the dropdown to our view object.
-        self.add_item(Dropdown())
diff --git a/extensions/appointments.py b/extensions/appointments.py
new file mode 100755
index 0000000000000000000000000000000000000000..35e207de6eb801c60d40fd331e1126d171d92819
--- /dev/null
+++ b/extensions/appointments.py
@@ -0,0 +1,117 @@
+import asyncio
+from datetime import datetime, timedelta
+import os
+
+from discord import app_commands, errors, Interaction
+from discord.ext import tasks, commands
+
+from models import Appointment
+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"\n"
+    message += " ".join([f"<@!{str(attendee.member_id)}>" for attendee in appointment.attendees])
+
+    await channel.send(message)
+
+
+@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)
+    async def timer(self):
+        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:
+                            new_date_time = appointment.date_time + timedelta(days=appointment.recurring)
+                            Appointment.update(reminder_sent=False, 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=AppointmentView())
+                            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:
+                    appointment.delete_instance(recursive=True)
+
+    @timer.before_loop
+    async def before_timer(self):
+        await asyncio.sleep(60 - datetime.now().second)
+
+    @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 (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 = 0):
+        """ Add an appointment to a channel """
+        channel = interaction.channel
+        author_id = interaction.user.id
+        try:
+            date_time = datetime.strptime(f"{date} {time}", self.fmt)
+        except ValueError:
+            await channel.send("Fehler! Ungültiges Datums und/oder Zeit Format!")
+            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=False)
+
+        await interaction.response.send_message(embed=appointment.get_embed(), view=AppointmentView())
+        message = await interaction.original_message()
+        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 """
+
+        if appointments := Appointment.select(Appointment.channel == interaction.channel_id):
+            answer = f'Termine dieses Channels:\n'
+
+            for appointment in appointments:
+                try:
+                    message = await interaction.channel.fetch_message(appointment.message)
+                    answer += f'<t:{appointment.date_time}:F>: {appointment.title} => {message.jump_url}\n'
+                except errors.NotFound:
+                    appointment.delete_instance(recursive=True)
+
+            await interaction.response.send_message(answer, ephemeral=not show_all)
+        else:
+            await interaction.response.send_message("Für diesen Channel existieren derzeit keine Termine",
+                                                    ephemeral=not show_all)
+
+
+async def setup(bot: commands.Bot) -> None:
+    await bot.add_cog(Appointments(bot))
+    bot.add_view(AppointmentView())
diff --git a/extensions/polls.py b/extensions/polls.py
new file mode 100755
index 0000000000000000000000000000000000000000..30788bf433ad973c180745de1a3399f2002c1480
--- /dev/null
+++ b/extensions/polls.py
@@ -0,0 +1,76 @@
+import discord
+import emoji
+from discord import app_commands, Interaction
+from discord.ext import commands
+
+from models import *
+
+# from cogs.components.poll.poll import Poll
+from views.poll_view import PollView
+
+DEFAULT_CHOICES = ["🇦", "🇧", "🇨", "🇩", "🇪", "🇫", "🇬", "🇭", "🇮", "🇯", "🇰", "🇱", "🇲", "🇳", "🇴", "🇵", "🇶",
+                   "🇷", "🇸", "🇹"]
+
+
+@app_commands.guild_only()
+class Polls(commands.GroupCog, name="poll", description="Handle Polls in Channels"):
+    def __init__(self, bot):
+        self.bot = bot
+
+    @app_commands.command(name="add", description="Erstelle eine Umfrage mit bis zu 20 Antwortmöglichkeiten.")
+    @app_commands.describe(question="Welche Frage möchtest du stellen?", choice_a="1. Antwortmöglichkeit",
+                           choice_b="2. Antwortmöglichkeit", choice_c="3. Antwortmöglichkeit",
+                           choice_d="4. Antwortmöglichkeit", choice_e="5. Antwortmöglichkeit",
+                           choice_f="6. Antwortmöglichkeit", choice_g="7. Antwortmöglichkeit",
+                           choice_h="8. Antwortmöglichkeit", choice_i="9. Antwortmöglichkeit",
+                           choice_j="10. Antwortmöglichkeit", choice_k="11. Antwortmöglichkeit",
+                           choice_l="12. Antwortmöglichkeit", choice_m="13. Antwortmöglichkeit",
+                           choice_n="14. Antwortmöglichkeit", choice_o="15. Antwortmöglichkeit",
+                           choice_p="16. Antwortmöglichkeit", choice_q="17. Antwortmöglichkeit",
+                           choice_r="18. Antwortmöglichkeit", choice_s="19. Antwortmöglichkeit",
+                           choice_t="20. Antwortmöglichkeit")
+    async def cmd_poll(self, interaction: Interaction, question: str, choice_a: str, choice_b: str,
+                       choice_c: str = None, choice_d: str = None, choice_e: str = None, choice_f: str = None,
+                       choice_g: str = None, choice_h: str = None, choice_i: str = None, choice_j: str = None,
+                       choice_k: str = None, choice_l: str = None, choice_m: str = None, choice_n: str = None,
+                       choice_o: str = None, choice_p: str = None, choice_q: str = None, choice_r: str = None,
+                       choice_s: str = None, choice_t: str = None):
+        """ Create a new poll """
+        choices = [self.parse_choice(index, choice) for index, choice in enumerate(
+            [choice_a, choice_b, choice_c, choice_d, choice_e, choice_f, choice_g, choice_h, choice_i, choice_j,
+             choice_k, choice_l, choice_m, choice_n, choice_o, choice_p, choice_q, choice_r, choice_s, choice_t]) if
+                   choice]
+
+        await interaction.response.send_message("Bereite Umfrage vor, bitte warten...", view=PollView())
+        message = await interaction.original_message()
+        poll = Poll.create(question=question, author=interaction.user.id, channel=interaction.channel_id,
+                           message=message.id)
+        for choice in choices:
+            PollChoice.create(poll_id=poll.id, emoji=choice[0], text=choice[1])
+
+        await interaction.edit_original_message(content="", embed=poll.get_embed(), view=PollView())
+
+    def parse_choice(self, idx: int, choice: str):
+        choice = choice.strip()
+        index = choice.find(" ")
+
+        if index > -1:
+            possible_option = choice[:index]
+            if emoji.is_emoji(possible_option) or possible_option in DEFAULT_CHOICES:
+                if len(choice[index:].strip()) > 0:
+                    return [possible_option, choice[index:].strip()]
+            elif len(possible_option) > 1:
+                if (possible_option[0:2] == "<:" or possible_option[0:3] == "<a:") and possible_option[-1] == ">":
+                    splitted_custom_emoji = possible_option.strip("<a:>").split(":")
+                    if len(splitted_custom_emoji) == 2:
+                        id = splitted_custom_emoji[1]
+                        custom_emoji = self.bot.get_emoji(int(id))
+                        if custom_emoji and len(choice[index:].strip()) > 0:
+                            return [custom_emoji, choice[index:].strip()]
+
+        return [DEFAULT_CHOICES[idx], choice]
+
+
+async def setup(bot: commands.Bot) -> None:
+    await bot.add_cog(Polls(bot))
+    bot.add_view(PollView())
diff --git a/models.py b/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..cca1526ec22ba39692cf6ff16a2916b8428fef98
--- /dev/null
+++ b/models.py
@@ -0,0 +1,134 @@
+import datetime
+import io
+
+import discord
+import uuid
+from peewee import *
+
+db = SqliteDatabase("xanathar.db")
+
+
+class BaseModel(Model):
+    class Meta:
+        database = db
+
+
+class Poll(BaseModel):
+    question = CharField()
+    author = IntegerField()
+    channel = IntegerField()
+    message = IntegerField()
+
+    def get_embed(self):
+        embed = discord.Embed(title="Umfrage", description=self.question)
+        embed.add_field(name="Erstellt von", value=f'<@!{self.author}>', inline=False)
+        embed.add_field(name="\u200b", value="\u200b", inline=False)
+
+        for choice in self.choices:
+            name = f'{choice.emoji}  {choice.text}'
+            value = f'{len(choice.choice_chosen)}'
+
+            embed.add_field(name=name, value=value, inline=False)
+
+        participants = {str(choice_chosen.member_id): 1 for choice_chosen in
+                        PollChoiceChosen.select().join(PollChoice, on=PollChoiceChosen.poll_choice).where(
+                            PollChoice.poll == self)}
+
+        embed.add_field(name="\u200b", value="\u200b", inline=False)
+        embed.add_field(name="Anzahl der Teilnehmer an der Umfrage", value=f"{len(participants)}", inline=False)
+
+        return embed
+
+
+class PollChoice(BaseModel):
+    poll = ForeignKeyField(Poll, backref='choices')
+    text = CharField()
+    emoji = CharField()
+
+
+class PollChoiceChosen(BaseModel):
+    poll_choice = ForeignKeyField(PollChoice, backref='choice_chosen')
+    member_id = IntegerField()
+
+
+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(default=uuid.uuid4())
+
+    def get_embed(self):
+        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):
+        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.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(appointment.encode("utf-8"))
+        return ics_file
+
+
+class Attendee(BaseModel):
+    appointment = ForeignKeyField(Appointment, backref='attendees')
+    member_id = IntegerField()
+
+
+db.create_tables([Poll, PollChoice, PollChoiceChosen, Appointment, Attendee], safe=True)
+poll = Poll.select().where(id == 1)
diff --git a/views/appointment_view.py b/views/appointment_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..89391640765281ec0127c4409f2e54febab927c0
--- /dev/null
+++ b/views/appointment_view.py
@@ -0,0 +1,61 @@
+import discord
+from discord import File
+
+from models import Appointment, Attendee
+
+
+class AppointmentView(discord.ui.View):
+    def __init__(self):
+        super().__init__(timeout=None)
+
+    @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):
+        appointment = Appointment.select().where(Appointment.message == interaction.message.id)
+        if appointment:
+            appointment = appointment[0]
+            attendee = appointment.attendees.filter(member_id=interaction.user.id)
+            if attendee:
+                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):
+        appointment = Appointment.select().where(Appointment.message == interaction.message.id)
+        if appointment:
+            appointment = appointment[0]
+            attendee = appointment.attendees.filter(member_id=interaction.user.id)
+            if attendee:
+                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='Download .ics', style=discord.ButtonStyle.blurple, custom_id='appointment_view:ics',
+                       emoji="📅")
+    async def ics(self, interaction: discord.Interaction, button: discord.ui.Button):
+        appointment = Appointment.select().where(Appointment.message == interaction.message.id)
+        if appointment:
+            appointment = appointment[0]
+            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(thinking=False)
+        appointment = Appointment.select().where(Appointment.message == interaction.message.id)
+        if appointment:
+            appointment = appointment[0]
+            if interaction.user.id == appointment.author:
+                appointment.delete_instance(recursive=True)
+                await interaction.message.delete()
diff --git a/views/poll_view.py b/views/poll_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..550fbf7d3b866dc7d7b3b02639e73404a97e4793
--- /dev/null
+++ b/views/poll_view.py
@@ -0,0 +1,76 @@
+import discord
+
+from models import Poll, PollChoiceChosen
+
+
+class PollView(discord.ui.View):
+    def __init__(self):
+        super().__init__(timeout=None)
+
+    @discord.ui.button(label='Abstimmen', style=discord.ButtonStyle.green, custom_id='poll_view:vote', emoji="✅")
+    async def vote(self, interaction: discord.Interaction, button: discord.ui.Button):
+        poll = Poll.select().where(Poll.message == interaction.message.id)
+        if poll:
+            poll = poll[0]
+            await interaction.response.send_message(f"{poll.question}\n\n*(Nach der Abstimmung kannst du diese Nachricht "
+                                                    f"verwerfen. Wenn die Abstimmung nicht funktioniert, bitte verwirf "
+                                                    f"die Nachricht und Klicke erneut auf den Abstimmen Button der "
+                                                    f"Abstimmung.)*", view=PollChoiceView(poll, interaction.user),
+                                                    ephemeral=True)
+
+    @discord.ui.button(label='Schließen', style=discord.ButtonStyle.red, custom_id='poll_view:close', emoji="🛑")
+    async def close(self, interaction: discord.Interaction, button: discord.ui.Button):
+        await interaction.response.defer(thinking=False)
+        poll = Poll.select().where(Poll.message == interaction.message.id)
+        if poll:
+            poll = poll[0]
+            if interaction.user.id == poll.author:
+                poll.delete_instance(recursive=True)
+                await interaction.edit_original_message(view=None)
+
+    @discord.ui.button(label='Löschen', style=discord.ButtonStyle.gray, custom_id='poll_view:delete', emoji="🗑")
+    async def delete(self, interaction: discord.Interaction, button: discord.ui.Button):
+        await interaction.response.defer(thinking=False)
+        poll = Poll.select().where(Poll.message == interaction.message.id)
+        if poll:
+            poll = poll[0]
+            if interaction.user.id == poll.author:
+                poll.delete_instance(recursive=True)
+                await interaction.message.delete()
+
+
+class PollChoiceView(discord.ui.View):
+    def __init__(self, poll, user):
+        super().__init__(timeout=None)
+        self.poll = poll
+        self.user = user
+        # Adds the dropdown to our view object.
+        self.add_item(PollDropdown(poll, user))
+
+
+class PollDropdown(discord.ui.Select):
+    def __init__(self, poll, user):
+        self.poll = poll
+        self.user = user
+        # Set the options that will be presented inside the dropdown
+
+        options = [discord.SelectOption(label=choice.text, emoji=choice.emoji,
+                                        default=len(choice.choice_chosen.filter(member_id=user.id)) > 0) for choice in
+                   poll.choices]
+
+        # The placeholder is what will be shown when no option is chosen
+        # The min and max values indicate we can only pick one of the three options
+        # The options parameter defines the dropdown options. We defined this above
+        super().__init__(placeholder='Wähle weise....', min_values=0, max_values=len(options), options=options)
+
+    async def callback(self, interaction: discord.Interaction):
+        await interaction.response.defer(thinking=False)
+        for choice in self.poll.choices:
+            chosen = choice.choice_chosen.filter(member_id=self.user.id)
+            if chosen and choice.text not in self.values:
+                chosen[0].delete_instance()
+            elif not chosen and choice.text in self.values:
+                PollChoiceChosen.create(poll_choice_id=choice.id, member_id=self.user.id)
+
+        message = await interaction.channel.fetch_message(self.poll.message)
+        await message.edit(embed=self.poll.get_embed(), view=PollView())
diff --git a/xanathar.py b/xanathar.py
index 5b73007894e9e13ba29ac83c6b36ffa011e82cd3..af78f128d91b14238ddb90eeb98059a26c170a5b 100755
--- a/xanathar.py
+++ b/xanathar.py
@@ -6,13 +6,15 @@ from discord.ext import commands
 from dotenv import load_dotenv
 
 # .env file is necessary in the same directory, that contains several strings.
+from views import poll_view, appointment_view
+
 load_dotenv()
 TOKEN = os.getenv('DISCORD_TOKEN')
 ACTIVITY = os.getenv('DISCORD_ACTIVITY')
 
 intents = discord.Intents.all()
 help_command = commands.DefaultHelpCommand(dm_help=True)
-extensions = ["cogs.appointments", "cogs.polls"]
+extensions = ["extensions.appointments", "extensions.polls"]
 
 
 class Xanathar(commands.Bot):