Skip to content
Snippets Groups Projects
learninggroups.py 46.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • dnns01's avatar
    dnns01 committed
    import json
    import os
    import re
    import time
    
    from enum import Enum
    
    from typing import Union
    
    dnns01's avatar
    dnns01 committed
    
    import disnake
    
    from disnake import InteractionMessage
    
    dnns01's avatar
    dnns01 committed
    from disnake.ext import commands
    
    from disnake.ui import Button
    
    dnns01's avatar
    dnns01 committed
    
    import utils
    from cogs.help import help, handle_error, help_category
    
    """
    
      Umgebungsvariablen:
      DISCORD_LEARNINGGROUPS_OPEN - Kategorie-ID der offenen Lerngruppen
      DISCORD_LEARNINGGROUPS_CLOSE - Kategorie-ID der geschlossenen Lerngruppen
      DISCORD_LEARNINGGROUPS_PRIVATE - Kategorie-ID der privaten Lerngruppen
      DISCORD_LEARNINGGROUPS_ARCHIVE - Kategorie-ID der archivierten Lerngruppen
      DISCORD_LEARNINGGROUPS_REQUEST - ID des Kanals, in dem Anfragen, die über den Bot gestellt wurden, eingetragen werden
      DISCORD_LEARNINGGROUPS_INFO - ID des Kanals, in dem die Lerngruppen-Informationen gepostet/aktualisert werden
    
    dnns01's avatar
    dnns01 committed
      DISCORD_LEARNINGGROUPS_FILE - Name der Datei mit Verwaltungsdaten der Lerngruppen (minimaler Inhalt: {"requested": {},"groups": {}})
    
      DISCORD_LEARNINGGROUPS_COURSE_FILE - Name der Datei welche die Kursnamen für die Lerngruppen-Informationen enthält (minimaler Inhalt: {})
      DISCORD_MOD_ROLE - ID der Moderations-Rolle, die erweiterte Lerngruppen-Aktionen ausführen darf
    
    dnns01's avatar
    dnns01 committed
    """
    
    
    LG_OPEN_SYMBOL = f'🌲'
    LG_CLOSE_SYMBOL = f'🛑'
    
    LG_PRIVATE_SYMBOL = f'🚪'
    
    LG_LISTED_SYMBOL = f'📖'
    
    dnns01's avatar
    dnns01 committed
    
    
    
    class GroupState(Enum):
        OPEN = "OPEN"
        CLOSED = "CLOSED"
        PRIVATE = "PRIVATE"
        ARCHIVED = "ARCHIVED"
        REMOVED = "REMOVED"
    
    
    
    dnns01's avatar
    dnns01 committed
    @help_category("learninggroups", "Lerngruppen",
    
                   "Mit dem Lerngruppen-Feature kannst du Lerngruppenkanäle beantragen und verwalten.",
                   "Hier kannst du Lerngruppenkanäle anlegen, beantragen und verwalten.")
    
    dnns01's avatar
    dnns01 committed
    class LearningGroups(commands.Cog):
        def __init__(self, bot):
            self.bot = bot
            # ratelimit 2 in 10 minutes (305 * 2 = 610 = 10 minutes and 10 seconds)
            self.rename_ratelimit = 305
            self.msg_max_len = 2000
    
    
            self.categories = {
                GroupState.OPEN: os.getenv('DISCORD_LEARNINGGROUPS_OPEN'),
                GroupState.CLOSED: os.getenv('DISCORD_LEARNINGGROUPS_CLOSE'),
                GroupState.PRIVATE: os.getenv('DISCORD_LEARNINGGROUPS_PRIVATE'),
                GroupState.ARCHIVED: os.getenv('DISCORD_LEARNINGGROUPS_ARCHIVE')
            }
            self.symbols = {
                GroupState.OPEN: LG_OPEN_SYMBOL,
                GroupState.CLOSED: LG_CLOSE_SYMBOL,
                GroupState.PRIVATE: LG_PRIVATE_SYMBOL
            }
    
    dnns01's avatar
    dnns01 committed
            self.channel_request = os.getenv('DISCORD_LEARNINGGROUPS_REQUEST')
            self.channel_info = os.getenv('DISCORD_LEARNINGGROUPS_INFO')
            self.group_file = os.getenv('DISCORD_LEARNINGGROUPS_FILE')
            self.header_file = os.getenv('DISCORD_LEARNINGGROUPS_COURSE_FILE')
    
            self.support_channel = os.getenv('DISCORD_SUPPORT_CHANNEL')
    
    dnns01's avatar
    dnns01 committed
            self.mod_role = os.getenv("DISCORD_MOD_ROLE")
    
            self.guild_id = os.getenv("DISCORD_GUILD")
    
            self.groups = {}  # organizer and learninggroup-member ids
    
            self.channels = {}  # complete channel configs
    
            self.header = {}  # headlines for status message
    
    dnns01's avatar
    dnns01 committed
            self.load_groups()
            self.load_header()
    
    
        @commands.Cog.listener()
        async def on_button_click(self, interaction: InteractionMessage):
            button: Button = interaction.component
    
            if button.custom_id == "learninggroups:group_yes":
                await self.on_group_request(True, button, interaction)
            elif button.custom_id == "learninggroups:group_no":
                await self.on_group_request(False, button, interaction)
            elif button.custom_id == "learninggroups:join_yes":
                await self.on_join_request(True, button, interaction)
            elif button.custom_id == "learninggroups:join_no":
                await self.on_join_request(False, button, interaction)
    
    
    
        @commands.Cog.listener(name="on_ready")
        async def on_ready(self):
            await self.update_channels()
    
    
    dnns01's avatar
    dnns01 committed
        def load_header(self):
            file = open(self.header_file, mode='r')
            self.header = json.load(file)
    
        def save_header(self):
            file = open(self.header_file, mode='w')
            json.dump(self.header, file)
    
        def load_groups(self):
            group_file = open(self.group_file, mode='r')
            self.groups = json.load(group_file)
    
            if not self.groups.get("groups"):
                self.groups['groups'] = {}
            if not self.groups.get("requested"):
                self.groups['requested'] = {}
            if not self.groups.get("messageids"):
                self.groups['messageids'] = []
    
    
            for _, group in self.groups['requested'].items():
                group["state"] = GroupState[group["state"]]
    
    
        async def save_groups(self):
            await self.update_channels()
    
    dnns01's avatar
    dnns01 committed
            group_file = open(self.group_file, mode='w')
    
    
            groups = copy.deepcopy(self.groups)
    
            for _, group in groups['requested'].items():
                group["state"] = group["state"].name
            json.dump(groups, group_file)
    
        def arg_state_to_group_state(self, state: str):
            if state in ["offen", "open", "o"]:
                return GroupState.OPEN
            if state in ["geschlossen", "closed", "close"]:
                return GroupState.CLOSED
            if state in ["private", "privat"]:
                return GroupState.PRIVATE
    
    dnns01's avatar
    dnns01 committed
            return None
    
    
        def is_request_organizer(self, request, member):
            return request["organizer_id"] == member.id
    
    dnns01's avatar
    dnns01 committed
    
    
        def is_group_organizer(self, channel, member):
    
    dnns01's avatar
    dnns01 committed
            channel_config = self.groups["groups"].get(str(channel.id))
            if channel_config:
    
                return channel_config["organizer_id"] == member.id
    
    dnns01's avatar
    dnns01 committed
            return False
    
        def is_mod(self, member):
            roles = member.roles
            for role in roles:
                if role.id == int(self.mod_role):
                    return True
    
            return False
    
        def is_group_request_message(self, message):
            return len(message.embeds) > 0 and message.embeds[0].title == "Lerngruppenanfrage!"
    
        async def is_channel_config_valid(self, ctx, channel_config, command=None):
    
            if channel_config['state'] is None:
    
    dnns01's avatar
    dnns01 committed
                if command:
                    await ctx.channel.send(
    
                        f"Fehler! Bitte gib an ob die Gruppe **offen** (**open**) **geschlossen** (**closed**) oder **privat** (**private**) ist. Gib `!help {command}` für Details ein.")
    
    dnns01's avatar
    dnns01 committed
                return False
            if not re.match(r"^[0-9]+$", channel_config['course']):
                if command:
                    await ctx.channel.send(
                        f"Fehler! Die Kursnummer muss numerisch sein. Gib `!help {command}` für Details ein.")
                return False
            if not re.match(r"^(sose|wise)[0-9]{2}$", channel_config['semester']):
                if command:
                    await ctx.channel.send(
                        f"Fehler! Das Semester muss mit **sose** oder **wise** angegeben werden gefolgt von der **zweistelligen Jahreszahl**. Gib `!help {command}` für Details ein.")
                return False
            return True
    
        async def check_rename_rate_limit(self, channel_config):
            if channel_config.get("last_rename") is None:
                return False
            now = int(time.time())
            seconds = channel_config["last_rename"] + self.rename_ratelimit - now
            if seconds > 0:
                channel = await self.bot.fetch_channel(int(channel_config["channel_id"]))
    
                await channel.send(f"Discord schränkt die Anzahl der Aufrufe für manche Funktionen ein, daher kannst du diese Aktion erst wieder in {seconds} Sekunden ausführen.")
    
    dnns01's avatar
    dnns01 committed
            return seconds > 0
    
    
        async def category_of_channel(self, state: GroupState):
            category_to_fetch = self.categories[state]
    
    dnns01's avatar
    dnns01 committed
            category = await self.bot.fetch_channel(category_to_fetch)
            return category
    
        def full_channel_name(self, channel_config):
    
            return (f"{self.symbols[channel_config['state']]}"
    
                    f"{channel_config['course']}-{channel_config['name']}-{channel_config['semester']}"
                    f"{LG_LISTED_SYMBOL if channel_config['is_listed'] else ''}")
    
    dnns01's avatar
    dnns01 committed
    
    
        async def update_statusmessage(self):
    
    dnns01's avatar
    dnns01 committed
            info_message_ids = self.groups.get("messageids")
            channel = await self.bot.fetch_channel(int(self.channel_info))
    
            for info_message_id in info_message_ids:
                message = await channel.fetch_message(info_message_id)
                await message.delete()
    
            info_message_ids = []
    
            msg = f"**Lerngruppen**\n\n"
            course_msg = ""
    
            sorted_channels = sorted(self.channels.values(
            ), key=lambda channel: f"{channel['course']}-{channel['name']}")
    
            open_channels = [channel for channel in sorted_channels if channel['state'] in [GroupState.OPEN]
                             or channel['is_listed']]
    
    dnns01's avatar
    dnns01 committed
            courseheader = None
    
            no_headers = []
            for lg_channel in open_channels:
    
    dnns01's avatar
    dnns01 committed
    
    
                if lg_channel['course'] != courseheader:
    
    dnns01's avatar
    dnns01 committed
                    if len(msg) + len(course_msg) > self.msg_max_len:
                        message = await channel.send(msg)
                        info_message_ids.append(message.id)
                        msg = course_msg
                        course_msg = ""
                    else:
                        msg += course_msg
                        course_msg = ""
    
                    header = self.header.get(lg_channel['course'])
    
    dnns01's avatar
    dnns01 committed
                    if header:
                        course_msg += f"**{header}**\n"
                    else:
    
                        course_msg += f"**{lg_channel['course']} - -------------------------------------**\n"
                        no_headers.append(lg_channel['course'])
                    courseheader = lg_channel['course']
    
                groupchannel = await self.bot.fetch_channel(int(lg_channel['channel_id']))
                course_msg += f"    {groupchannel.mention}"
    
    
                if lg_channel['is_listed'] and lg_channel['state'] == GroupState.PRIVATE:
    
                    group_config = self.groups["groups"].get(lg_channel['channel_id'])
                    if group_config:
    
                        user = await self.bot.fetch_user(group_config['organizer_id'])
    
                            course_msg += f" **@{user.name}#{user.discriminator}**"
    
                    course_msg +=  f"\n       **↳** `!lg join {groupchannel.id}`"
                course_msg += "\n"
    
    dnns01's avatar
    dnns01 committed
    
            msg += course_msg
            message = await channel.send(msg)
    
            if len(no_headers) > 0:
                support_channel = await self.bot.fetch_channel(int(self.support_channel))
                if support_channel:
    
                    await support_channel.send(f"In der Lerngruppenübersicht fehlen noch Überschriften für die folgenden Kurse: **{', '.join(no_headers)}**")
    
    dnns01's avatar
    dnns01 committed
            info_message_ids.append(message.id)
            self.groups["messageids"] = info_message_ids
    
            await self.save_groups()
    
    dnns01's avatar
    dnns01 committed
    
        async def archive(self, channel):
    
            group_config = self.groups["groups"].get(str(channel.id))
            if not group_config:
                await channel.send("Das ist kein Lerngruppenkanal.")
                return
    
            category = await self.bot.fetch_channel(self.categories[GroupState.ARCHIVED])
    
    dnns01's avatar
    dnns01 committed
            await self.move_channel(channel, category)
            await channel.edit(name=f"archiv-${channel.name[1:]}")
    
            await self.update_permissions(channel)
    
            await self.remove_group(channel)
            await self.update_statusmessage()
    
    
    dnns01's avatar
    dnns01 committed
    
    
        async def set_channel_state(self, channel, state: GroupState = None):
    
            channel_config = self.channels[str(channel.id)]
    
    dnns01's avatar
    dnns01 committed
            if await self.check_rename_rate_limit(channel_config):
    
                return False  # prevent api requests when ratelimited
    
    dnns01's avatar
    dnns01 committed
    
    
            if state is not None:
                old_state = channel_config["state"]
                if old_state == state:
    
                    return False  # prevent api requests when nothing has changed
    
                channel_config["state"] = state
                await self.alter_channel(channel, channel_config)
                return True
    
    dnns01's avatar
    dnns01 committed
    
    
        async def set_channel_listing(self, channel, is_listed):
            channel_config = self.channels[str(channel.id)]
            if await self.check_rename_rate_limit(channel_config):
                return False  # prevent api requests when ratelimited
            if channel_config["state"] in [GroupState.CLOSED, GroupState.PRIVATE]:
    
                was_listed = channel_config["is_listed"]
                if was_listed == is_listed:
    
                    return False  # prevent api requests when nothing has changed
    
                channel_config["is_listed"] = is_listed
    
                await self.alter_channel(channel, channel_config)
                return True
    
    dnns01's avatar
    dnns01 committed
    
    
        async def alter_channel(self, channel, channel_config):
    
            self.groups["groups"][str(channel.id)]["last_rename"] = int(time.time())
    
    dnns01's avatar
    dnns01 committed
            await channel.edit(name=self.full_channel_name(channel_config))
    
            category = await self.category_of_channel(channel_config["state"])
            await self.move_channel(channel, category,
                                    sync=True if channel_config["state"] in [GroupState.OPEN, GroupState.CLOSED] else False)
    
            await self.save_groups()
            await self.update_statusmessage()
            return True
    
    dnns01's avatar
    dnns01 committed
    
        async def set_channel_name(self, channel, name):
    
            channel_config = self.channels[str(channel.id)]
    
    dnns01's avatar
    dnns01 committed
    
            if await self.check_rename_rate_limit(channel_config):
                return  # prevent api requests when ratelimited
    
    
            self.groups["groups"][str(channel.id)]["last_rename"] = int(time.time())
    
    dnns01's avatar
    dnns01 committed
            channel_config["name"] = name
    
            await channel.edit(name=self.full_channel_name(channel_config))
    
            await self.save_groups()
            await self.update_statusmessage()
    
    dnns01's avatar
    dnns01 committed
    
    
        async def move_channel(self, channel, category, sync=True):
    
    dnns01's avatar
    dnns01 committed
            for sortchannel in category.text_channels:
                if sortchannel.name[1:] > channel.name[1:]:
    
                    await channel.move(category=category, before=sortchannel, sync_permissions=sync)
    
    dnns01's avatar
    dnns01 committed
                    return
    
            await channel.move(category=category, sync_permissions=sync, end=True)
    
    dnns01's avatar
    dnns01 committed
    
        async def add_requested_group_channel(self, message, direct=False):
    
            requested_channel_config = self.groups["requested"].get(str(message.id))
    
    
            category = await self.category_of_channel(requested_channel_config["state"])
    
            full_channel_name = self.full_channel_name(requested_channel_config)
            channel = await category.create_text_channel(full_channel_name)
            await self.move_channel(channel, category, False)
    
            user = await self.bot.fetch_user(requested_channel_config["organizer_id"])
    
    
            await channel.send(f":wave: <@!{user.id}>, hier ist deine neue Lerngruppe.\n"
                               "Es gibt offene und private Lerngruppen. Eine offene Lerngruppe ist für jeden sichtbar "
                               "und jeder kann darin schreiben. Eine private Lerngruppe ist unsichtbar und auf eine "
                               "Gruppe an Kommilitoninnen beschränkt."
                               "```"
    
                               "Funktionen für Lerngruppenorganisatorinnen:\n"
    
                               "!lg addmember <@newmember>: Fügt ein Mitglied zur Lerngruppe hinzu.\n"                           
    
                               "!lg organizer <@neworganizer>: Ändert die Organisatorin der Lerngruppe auf @neworganizer.\n"
    
                               "!lg open: Öffnet eine Lerngruppe.\n"
                               "!lg close: Schließt eine Lerngruppe.\n"
                               "!lg private: Stellt die Lerngruppe auf privat.\n"
                               "!lg show: Zeigt eine private oder geschlossene Lerngruppe in der Lerngruppenliste an.\n"
                               "!lg hide: Entfernt eine private oder geschlossene Lerngruppe aus der Lerngruppenliste.\n"
                               "!lg kick <@user>: Schließt eine Benutzerin von der Lerngruppe aus.\n"   
    
                               "\nKommandos für alle:\n"
                               "!lg id: Zeigt die ID der Lerngruppe an mit der andere Kommilitoninnen beitreten können.\n"
                               "!lg members: Zeigt die Mitglieder der Lerngruppe an.\n"
    
                               "!lg organizer: Zeigt die Organisatorin der Lerngruppe an.\n"
    
                               "!lg leave: Du verlässt die Lerngruppe.\n"
    
                               "!lg join: Anfrage, um der Lerngruppe beizutreten.\n"
    
                               "\nMit dem nachfolgenden Kommando kann eine Kommilitonin darum "
    
                               "bitten in die Lerngruppe aufgenommen zu werden wenn die Gruppe privat ist.\n"
    
                               f"!lg join {channel.id}"
    
                                "\n(Manche Kommandos werden von Discord eingeschränkt und können nur einmal alle 5 Minuten ausgeführt werden.)"
    
            self.groups["groups"][str(channel.id)] = {
    
                "organizer_id": requested_channel_config["organizer_id"],
    
                "last_rename": int(time.time())
            }
    
    
            await self.remove_group_request(message)
    
    dnns01's avatar
    dnns01 committed
            if not direct:
                await message.delete()
    
    
            await self.save_groups()
            await self.update_statusmessage()
    
            if requested_channel_config["state"] is GroupState.PRIVATE:
    
                await self.update_permissions(channel)
    
    dnns01's avatar
    dnns01 committed
    
    
        async def remove_group_request(self, message):
    
    dnns01's avatar
    dnns01 committed
            del self.groups["requested"][str(message.id)]
    
            await self.save_groups()
    
    dnns01's avatar
    dnns01 committed
    
    
        async def remove_group(self, channel):
    
    dnns01's avatar
    dnns01 committed
            del self.groups["groups"][str(channel.id)]
    
            await self.save_groups()
    
        def channel_to_channel_config(self, channel):
            cid = str(channel.id)
            is_listed = channel.name[-1] == LG_LISTED_SYMBOL
    
            result = re.match(r"([0-9]+)-(.*)-([a-z0-9]+)$", channel.name[1:] if not is_listed else channel.name[1:-1])
    
            state = None
            if channel.name[0] == LG_OPEN_SYMBOL:
                state = GroupState.OPEN
            elif channel.name[0] == LG_CLOSE_SYMBOL:
                state = GroupState.CLOSED
            elif channel.name[0] == LG_PRIVATE_SYMBOL:
                state = GroupState.PRIVATE
    
    
            course, name, semester = result.group(1, 2, 3)
    
            channel_config = {"course": course, "name": name, "category": channel.category_id, "semester": semester,
    
                              "state": state, "is_listed": is_listed, "channel_id": cid}
    
            if self.groups["groups"].get(cid):
                channel_config.update(self.groups["groups"].get(cid))
            return channel_config
    
        async def update_channels(self):
            self.channels = {}
    
            for state in [GroupState.OPEN, GroupState.CLOSED, GroupState.PRIVATE]:
                category = await self.category_of_channel(state)
    
    dnns01's avatar
    dnns01 committed
    
                for channel in category.text_channels:
    
                    channel_config = self.channel_to_channel_config(channel)
    
                    self.channels[str(channel.id)] = channel_config
    
    
        async def add_member_to_group(self, channel: disnake.TextChannel, arg_member: disnake.Member, send_message=True):
    
            group_config = self.groups["groups"].get(str(channel.id))
            if not group_config:
                await channel.send("Das ist kein Lerngruppenkanal.")
                return
    
            users = group_config.get("users")
            if not users:
                users = {}
            mid = str(arg_member.id)
            if not users.get(mid):
                users[mid] = True
    
                user = await self.bot.fetch_user(mid)
    
                if user and send_message:
    
                    try:
                        await utils.send_dm(user, f"Du wurdest in die Lerngruppe <#{channel.id}> aufgenommen. " 
                                                  "Viel Spass beim gemeinsamen Lernen!\n"
    
                                                  "Dieser Link führt dich direkt zum Lerngruppenkanal. " 
                                                  "Diese Nachricht kannst du in unserer Unterhaltung mit Rechtsklick anpinnen, " 
                                                  "wenn du möchtest.")
    
            group_config["users"] = users
    
            await self.save_groups()
    
    
        async def remove_member_from_group(self, channel: disnake.TextChannel, arg_member: disnake.Member, send_message=True):
    
            group_config = self.groups["groups"].get(str(channel.id))
            if not group_config:
                await channel.send("Das ist kein Lerngruppenkanal.")
                return
    
            users = group_config.get("users")
            if not users:
                return
            mid = str(arg_member.id)
    
            if users.pop(mid, None):
                user = await self.bot.fetch_user(mid)
    
                if user and send_message:
    
                    await utils.send_dm(user, f"Du wurdest aus der Lerngruppe {channel.name} entfernt.")
    
    dnns01's avatar
    dnns01 committed
    
    
            await self.save_groups()
    
    dnns01's avatar
    dnns01 committed
    
    
        async def update_permissions(self, channel):
    
            channel_config = self.channels[str(channel.id)]
            if channel_config.get("state") == GroupState.PRIVATE:
                overwrites = await self.overwrites(channel)
                await channel.edit(overwrites=overwrites)
            else:
                await channel.edit(sync_permissions=True)
    
    dnns01's avatar
    dnns01 committed
    
    
        async def overwrites(self, channel):
            channel = await self.bot.fetch_channel(str(channel.id))
            group_config = self.groups["groups"].get(str(channel.id))
            guild = await self.bot.fetch_guild(int(self.guild_id))
    
            mods = guild.get_role(int(self.mod_role))
    
    dnns01's avatar
    dnns01 committed
    
    
            overwrites = {
                mods: disnake.PermissionOverwrite(read_messages=True),
                guild.default_role: disnake.PermissionOverwrite(read_messages=False)
            }
    
    
            if not group_config:
                return overwrites
    
    
            organizer = self.bot.get_user(group_config["organizer_id"])
            if not organizer:
    
            overwrites[organizer] = disnake.PermissionOverwrite(read_messages=True)
    
            users = group_config.get("users")
            if not users:
                return overwrites
    
            for userid in users.keys():
                user = await self.bot.fetch_user(userid)
                overwrites[user] = disnake.PermissionOverwrite(read_messages=True)
    
            return overwrites
    
        @help(
            category="learninggroups",
            syntax="!lg <command>",
            brief="Lerngruppenverwaltung"
        )
        @commands.group(name="lg", aliases=["learninggroup", "lerngruppe"], pass_context=True)
        async def cmd_lg(self, ctx):
    
            if not ctx.invoked_subcommand:
    
                await ctx.channel.send("Gib `!help lg` ein, um eine Übersicht über die Lerngruppen-Kommandos zu erhalten.")
    
    
        @help(
            command_group="lg",
            category="learninggroups",
    
            brief="Aktualisiert die Lerngruppenliste",
    
            mod=True
        )
        @cmd_lg.command(name="update")
        @commands.check(utils.is_mod)
        async def cmd_update(self, ctx):
            await self.update_channels()
            await self.update_statusmessage()
    
    dnns01's avatar
    dnns01 committed
    
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg header <coursenumber> <name...>",
            brief="Fügt einen Kurs als neue Überschrift in Botys Lerngruppen-Liste (Kanal #lerngruppen) hinzu. "
    
                  "Der Name darf Leerzeichen enthalten, Anführungszeichen sind nicht erforderlich.",
    
            example="!lg header 1141 Mathematische Grundlagen",
    
    dnns01's avatar
    dnns01 committed
            parameters={
                "coursenumber": "Nummer des Kurses wie von der Fernuni angegeben (ohne führende Nullen z. B. 1142).",
                "name...": "Ein frei wählbarer Text (darf Leerzeichen enthalten).",
            },
    
            description="Kann auch zum Bearbeiten einer Überschrift genutzt werden. Existiert die Kursnummer bereits, "
                        "wird die Überschrift geändert.",
    
    dnns01's avatar
    dnns01 committed
            mod=True
        )
    
        @cmd_lg.command(name="header")
    
    dnns01's avatar
    dnns01 committed
        @commands.check(utils.is_mod)
    
        async def cmd_add_header(self, ctx, arg_course, *arg_name):
    
    dnns01's avatar
    dnns01 committed
            if not re.match(r"[0-9]+", arg_course):
                await ctx.channel.send(
                    f"Fehler! Die Kursnummer muss numerisch sein. Gib `!help add-course` für Details ein.")
                return
    
            self.header[arg_course] = f"{arg_course} - {' '.join(arg_name)}"
            self.save_header()
    
            await self.update_statusmessage()
    
    dnns01's avatar
    dnns01 committed
    
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg add <coursenumber> <name> <semester> <status> <@usermention>",
            example="!lg add 1142 mathegenies sose22 closed @someuser",
    
            brief="Fügt einen Lerngruppenkanal hinzu. Der Name darf keine Leerzeichen enthalten.",
    
    dnns01's avatar
    dnns01 committed
            parameters={
                "coursenumber": "Nummer des Kurses wie von der Fernuni angegeben (ohne führende Nullen z. B. 1142).",
                "name": "Ein frei wählbarer Text ohne Leerzeichen. Bindestriche sind zulässig.",
    
                "semester": ("Das Semester, für welches diese Lerngruppe erstellt werden soll."
                             "sose oder wise gefolgt von der zweistelligen Jahreszahl (z. B. sose22)."),
    
    dnns01's avatar
    dnns01 committed
                "status": "Gibt an ob die Lerngruppe für weitere Lernwillige geöffnet ist (open) oder nicht (closed).",
    
                "@usermention": "Die so erwähnte Benutzerin wird als Organisatorin der Lerngruppe eingesetzt."
    
    dnns01's avatar
    dnns01 committed
            },
            mod=True
        )
    
        @cmd_lg.command(name="add")
    
    dnns01's avatar
    dnns01 committed
        @commands.check(utils.is_mod)
    
        async def cmd_add_group(self, ctx, arg_course, arg_name, arg_semester, arg_state, arg_organizer: disnake.Member):
    
            state = self.arg_state_to_group_state(arg_state)
    
            channel_config = {"organizer_id": arg_organizer.id, "course": arg_course, "name": arg_name, "semester": arg_semester,
    
                              "state": state, "is_listed": False}
    
    dnns01's avatar
    dnns01 committed
    
            if not await self.is_channel_config_valid(ctx, channel_config, ctx.command.name):
                return
    
            self.groups["requested"][str(ctx.message.id)] = channel_config
    
            await self.save_groups()
    
    dnns01's avatar
    dnns01 committed
            await self.add_requested_group_channel(ctx.message, direct=True)
    
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg request <coursenumber> <name> <semester> <status>",
    
            brief="Stellt eine Anfrage für einen neuen Lerngruppenkanal.",
    
            example="!lg request 1142 mathegenies sose22 closed",
    
    dnns01's avatar
    dnns01 committed
            description=("Moderatorinnen können diese Anfrage bestätigen, dann wird die Gruppe eingerichtet. "
    
                         "Die Organisatorin der Gruppe ist die Benutzerin, die die Anfrage gestellt hat."),
    
    dnns01's avatar
    dnns01 committed
            parameters={
                "coursenumber": "Nummer des Kurses, wie von der FernUni angegeben (ohne führende Nullen z. B. 1142).",
                "name": "Ein frei wählbarer Text ohne Leerzeichen.",
    
                "semester": "Das Semester, für welches diese Lerngruppe erstellt werden soll. sose oder wise gefolgt "
                "von der zweistelligen Jahreszahl (z. B. sose22).",
    
                "status": "Gibt an ob die Lerngruppe für weitere Lernwillige geöffnet ist (open) oder nicht (closed) oder ob es sich um eine private Lerngruppe handelt (private)."
    
    dnns01's avatar
    dnns01 committed
            }
        )
    
        @cmd_lg.command(name="request", aliases=["r", "req"])
    
        async def cmd_request_group(self, ctx, arg_course, arg_name, arg_semester, arg_state):
    
    
            arg_state = re.sub(r"[^a-z0-9]", "", arg_state.lower())
            arg_semester = re.sub(r"[^a-z0-9]", "", arg_semester.lower())
    
            if re.match(r"(wise)|(sose)[0-9]+", arg_state) and re.match(r"(open)|(closed*)|(private)", arg_semester):
                tmp = arg_state
                arg_state = arg_semester
                arg_semester = tmp
    
            arg_semester = re.sub(r"[^wiseo0-9]", "", arg_semester)
    
            arg_state = re.sub(r"[^a-z]", "", arg_state)
    
    
            state = self.arg_state_to_group_state(arg_state)
    
    
            arg_course = re.sub(r"[^0-9]", "", arg_course)
            arg_course = re.sub(r"^0+", "", arg_course)
    
    
            arg_name = re.sub(
                r"[^A-Za-zäöüß0-9-]",
                "",
                arg_name.lower().replace(" ", "-")
            )
    
            if len(arg_semester) == 8:
                arg_semester = f"{arg_semester[0:4]}{arg_semester[-2:]}"
    
            channel_config = {"organizer_id": ctx.author.id, "course": arg_course, "name": arg_name, "semester": arg_semester,
    
                              "state": state, "is_listed": False}
    
    dnns01's avatar
    dnns01 committed
    
            if not await self.is_channel_config_valid(ctx, channel_config, ctx.command.name):
                return
    
    
            channel = await self.bot.fetch_channel(int(self.channel_request))
    
    dnns01's avatar
    dnns01 committed
            channel_name = self.full_channel_name(channel_config)
    
    
            message = await utils.confirm(
                channel=channel,
                title="Lerngruppenanfrage",
                description=f"<@!{ctx.author.id}> möchte gerne die Lerngruppe **#{channel_name}** eröffnen.",
    
                custom_prefix="learninggroups:group"
    
    dnns01's avatar
    dnns01 committed
            self.groups["requested"][str(message.id)] = channel_config
    
            await self.save_groups()
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg show",
            brief="Zeigt einen privaten Lerngruppenkanal trotzdem in der Liste an.",
    
            description=("Muss im betreffenden Lerngruppenkanal ausgeführt werden. "
                         "Die Lerngruppe wird in der Übersicht der Lerngruppen aufgeführt, so dass Kommilitoninnen noch "
                         "anfragen können, in die Lerngruppe aufgenommen zu werden."
                         "Diese Aktion kann nur von der Organisatorin der Lerngruppe ausgeführt werden. ")
    
        )
        @cmd_lg.command(name="show")
        async def cmd_show(self, ctx):
    
            if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
    
                channel_config = self.channels[str(ctx.channel.id)]
                if channel_config:
    
                    if channel_config.get("state") == GroupState.PRIVATE:
                        if await self.set_channel_listing(ctx.channel, True):
                            await ctx.channel.send("Die Lerngruppe wird nun in der Lerngruppenliste angezeigt.")
                    elif channel_config.get("state") == GroupState.OPEN:
    
                        await ctx.channel.send("Nichts zu tun. Offene Lerngruppen werden sowieso in der Liste angezeigt.")
    
                    elif channel_config.get("state") == GroupState.CLOSED:
    
                        await ctx.channel.send("Möchtest du die Gruppen öffnen? Versuch's mit `!lg open`")
    
    dnns01's avatar
    dnns01 committed
    
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg hide",
            brief="Versteckt einen privaten Lerngruppenkanal. ",
    
            description=("Muss im betreffenden Lerngruppenkanal ausgeführt werden. "
                         "Die Lerngruppe wird nicht mehr in der Liste der Lerngruppen angezeigt. "
                         "Diese Aktion kann nur von der Organisatorin der Lerngruppe ausgeführt werden. ")
    
        )
        @cmd_lg.command(name="hide")
        async def cmd_hide(self, ctx):
    
            if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
    
                channel_config = self.channels[str(ctx.channel.id)]
    
                if channel_config:
                    if channel_config.get("state") == GroupState.PRIVATE:
                        if await self.set_channel_listing(ctx.channel, False):
                            await ctx.channel.send("Die Lerngruppe wird nun nicht mehr in der Lerngruppenliste angezeigt.")
                        return
    
                    elif channel_config.get("state") == GroupState.OPEN:
                        await ctx.channel.send("Offene Lerngruppen können nicht aus der Lerngruppenliste entfernt werden. " 
    
                                               "Führe `!lg close` aus, um die Lerngruppe zu schließen, "
                                               "oder `!lg private`, um diese auf "
    
                                               "privat zu schalten.")
                    elif channel_config.get("state") == GroupState.CLOSED:
                        await ctx.channel.send("Wenn diese Gruppe privat werden soll, ist das Kommando das du brauchst: `!lg private`")
    
        @cmd_lg.command(name="debug")
        @commands.check(utils.is_mod)
        async def cmd_debug(self, ctx):
            channel_config = self.channels[str(ctx.channel.id)]
            if not channel_config:
                await ctx.channel.send("None")
                return
            await ctx.channel.send(str(channel_config))
    
    
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg open",
    
            brief="Öffnet den Lerngruppenkanal, wenn du die Organisatorin bist. ",
            description=("Muss im betreffenden Lerngruppenkanal ausgeführt werden. "
                         "Verschiebt den Lerngruppenkanal in die Kategorie für offene Kanäle und ändert das Icon. "
                         "Diese Aktion kann nur von der Organisatorin der Lerngruppe ausgeführt werden. ")
    
    dnns01's avatar
    dnns01 committed
        )
    
        @cmd_lg.command(name="open", aliases=["opened", "offen"])
    
    dnns01's avatar
    dnns01 committed
        async def cmd_open(self, ctx):
    
            if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
    
                await self.set_channel_state(ctx.channel, state=GroupState.OPEN)
    
    dnns01's avatar
    dnns01 committed
    
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg close",
    
            brief="Schließt den Lerngruppenkanal, wenn du die Organisatorin bist. ",
            description=("Muss im betreffenden Lerngruppenkanal ausgeführt werden. "
    
                         "Stellt die Lerngruppe auf geschlossen. Dies ist rein symbolisch und zeigt an, "
                         "dass keine neuen Mitglieder mehr aufgenommen werden. "
    
                         "Diese Aktion kann nur von der Organisatorin der Lerngruppe ausgeführt werden. ")
    
        )
        @cmd_lg.command(name="close", aliases=["closed", "geschlossen"])
        async def cmd_close(self, ctx):
    
            if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
    
                await self.set_channel_state(ctx.channel, state=GroupState.CLOSED)
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg private",
    
            brief="Macht aus deiner Lerngruppe eine private Lerngruppe, wenn du die Organisatorin bist. ",
            description=("Muss im betreffenden Lerngruppenkanal ausgeführt werden. "
    
                         "Stellt die Lerngruppe auf privat. Es haben nur noch Mitglieder "
    
                         "der Lerngruppe Zugriff auf den Kanal. (siehe `!lg members`)"
                         "Diese Aktion kann nur von der Organisatorin der Lerngruppe ausgeführt werden. ")
    
    dnns01's avatar
    dnns01 committed
        )
    
        @cmd_lg.command(name="private", aliases=["privat"])
        async def cmd_private(self, ctx):
    
            if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
    
                if await self.set_channel_state(ctx.channel, state=GroupState.PRIVATE):
    
                    await self.update_permissions(ctx.channel)
    
    dnns01's avatar
    dnns01 committed
    
    
    dnns01's avatar
    dnns01 committed
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg rename <name>",
    
            brief="Ändert den Namen des Lerngruppenkanals, in dem das Kommando ausgeführt wird.",
    
            example="!lg rename matheluschen",
    
    dnns01's avatar
    dnns01 committed
            description="Aus #1142-matheprofis-sose22 wird nach dem Aufruf des Beispiels #1142-matheluschen-sose22.",
            parameters={
                "name": "Der neue Name der Lerngruppe ohne Leerzeichen."
            },
            mod=True
        )
    
        @cmd_lg.command(name="rename")
    
    dnns01's avatar
    dnns01 committed
        @commands.check(utils.is_mod)
        async def cmd_rename(self, ctx, arg_name):
            await self.set_channel_name(ctx.channel, arg_name)
    
        @help(
    
            command_group="lg",
            syntax="!lg archive",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            brief="Archiviert den Lerngruppenkanal.",
            description="Verschiebt den Lerngruppenkanal, in welchem dieses Kommando ausgeführt wird, ins Archiv.",
    
    dnns01's avatar
    dnns01 committed
            mod=True
        )
    
        @cmd_lg.command(name="archive", aliases=["archiv"])
    
    dnns01's avatar
    dnns01 committed
        @commands.check(utils.is_mod)
        async def cmd_archive(self, ctx):
            await self.archive(ctx.channel)
    
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg organizer <@usermention>",
            example="!lg organizer @someuser",
            brief="Bestimmt die Organisatorin eines Lerngruppenkanals.",
            description="Muss im betreffenden Lerngruppenkanal ausgeführt werden. ",
    
    dnns01's avatar
    dnns01 committed
            parameters={
    
                "@usermention": "Die neue Organisatorin der Lerngruppe."
    
        @cmd_lg.command(name="organizer")
        async def cmd_organizer(self, ctx, new_organizer: disnake.Member = None):
    
            group_config = self.groups["groups"].get(str(ctx.channel.id))
    
            if not group_config:
                self.groups["groups"][str(ctx.channel.id)] = {}
                group_config = self.groups["groups"][str(ctx.channel.id)]
    
    
            organizer_id = group_config.get("organizer_id")
    
            if not organizer_id:
    
            if not new_organizer:
                    user = await self.bot.fetch_user(organizer_id)
                    await ctx.channel.send(f"Organisatorin: @{user.name}#{user.discriminator}")
    
    
            elif isinstance(group_config, dict):
    
                organizer = await self.bot.fetch_user(organizer_id)
                if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
                    group_config["organizer_id"] = new_organizer.id
                    await self.remove_member_from_group(ctx.channel, new_organizer, False)
                    if new_organizer != organizer:
                        await self.add_member_to_group(ctx.channel, organizer, False)
    
                    await self.save_groups()
                    await self.update_permissions(ctx.channel)
                    await ctx.channel.send(
    
                        f"Glückwunsch {new_organizer.mention}! Du bist jetzt die Organisatorin dieser Lerngruppe.")
    
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg addmember <@usermention> <#channel>",
            example="!lg addmember @someuser #1141-mathegl-lerngruppe-sose21",
    
            brief="Fügt eine Benutzerin zu einer Lerngruppe hinzu.",
    
                "@usermention": "Die so erwähnte Benutzerin wird zur Lerngruppe hinzugefügt.",
    
                "#channel": "(optional) Der Kanal, zu dem die Benutzerin hinzugefügt werden soll."
    
            }
        )
        @cmd_lg.command(name="addmember", aliases=["addm", "am"])
    
        async def cmd_add_member(self, ctx, arg_member: disnake.Member, arg_channel: disnake.TextChannel = None):
            if not arg_channel:
                if not self.channels.get(str(ctx.channel.id)):
                    await ctx.channel.send("Wenn das Kommando außerhalb eines Lerngruppenkanals aufgerufen wird, muss der" 
    
                                           "Lerngruppenkanal angefügt werden. `!lg addmember <@usermention> <#channel>`")
    
                    return
                arg_channel = ctx.channel
    
            if self.is_group_organizer(arg_channel, ctx.author) or utils.is_mod(ctx):
    
                await self.add_member_to_group(arg_channel, arg_member)
                await self.update_permissions(arg_channel)
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg removemember <@usermention> <#channel>",
            example="!lg removemember @someuser #1141-mathegl-lerngruppe-sose21",
    
            brief="Entfernt eine Benutzerin aus einer Lerngruppe.",
    
                "#channel": "Der Kanal, aus dem die Benutzerin gelöscht werden soll.",
    
                "@usermention": "Die so erwähnte Benutzerin wird aus der Lerngruppe entfernt."
    
    dnns01's avatar
    dnns01 committed
            },
            mod=True
        )
    
        @cmd_lg.command(name="removemember", aliases=["remm", "rm"])
    
    dnns01's avatar
    dnns01 committed
        @commands.check(utils.is_mod)
    
        async def cmd_remove_member(self, ctx, arg_member: disnake.Member, arg_channel: disnake.TextChannel):
            await self.remove_member_from_group(arg_channel, arg_member)
            await self.update_permissions(arg_channel)
    
    dnns01's avatar
    dnns01 committed
    
        @help(
    
            command_group="lg",
    
    dnns01's avatar
    dnns01 committed
            category="learninggroups",
    
            syntax="!lg members",
    
            brief="Zählt die Mitglieder der Lerngruppe auf.",
    
    dnns01's avatar
    dnns01 committed
        )
    
        @cmd_lg.command(name="members")
        async def cmd_members(self, ctx):
            group_config = self.groups["groups"].get(str(ctx.channel.id))
            if not group_config:
                await ctx.channel.send("Das ist kein Lerngruppenkanal.")
                return
    
            organizer_id = group_config.get("organizer_id")
    
            if not organizer_id:
    
            organizer = await self.bot.fetch_user(organizer_id)
    
            users = group_config.get("users", {})
    
            if not users and not organizer:
    
                await ctx.channel.send("Keine Lerngruppenmitglieder vorhanden.")
    
            for user_id in users:
                user = await self.bot.fetch_user(user_id)
    
                names.append("@" + user.name + "#" + user.discriminator)
    
    
            await ctx.channel.send(f"Organisatorin: **@{organizer.name}#{organizer.discriminator}**\nMitglieder: " +
    
                                   (f"{', '.join(names)}" if len(names) > 0 else "Keine"))
    
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg id",
            brief="Zeigt die ID für deine Lerngruppe an.",
        )
        @cmd_lg.command(name="id")
        async def cmd_id(self, ctx):
    
            if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
    
                group_config = self.groups["groups"].get(str(ctx.channel.id))
                if not group_config:
                    await ctx.channel.send("Das ist kein Lerngruppenkanal.")
                    return
            await ctx.channel.send(f"Die ID dieser Lerngruppe lautet: `{str(ctx.channel.id)}`.\n"
                                   f"Beitrittsanfrage mit: `!lg join {str(ctx.channel.id)}`")
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg join <lg-id>",
    
            brief="Fragt bei der Organisatorin einer Lerngruppe um Aufnahme an.",
    
                "id": "Die ID der Lerngruppe."
    
            }
        )
        @cmd_lg.command(name="join")
        async def cmd_join(self, ctx, arg_id_or_channel: Union[int, disnake.TextChannel] = None):
    
            if arg_id_or_channel is None:
                arg_id_or_channel = ctx.channel
    
            cid = arg_id_or_channel.id if type(arg_id_or_channel) is disnake.TextChannel else arg_id_or_channel
    
            group_config = self.groups["groups"].get(str(cid))
            if not group_config:
                await ctx.channel.send("Das ist keine gültiger Lerngruppenkanal.")
    
    dnns01's avatar
    dnns01 committed
                return
    
    
            channel = await self.bot.fetch_channel(int(cid))
    
            await utils.confirm(
                channel=channel,
                title="Jemand möchte deiner Lerngruppe beitreten!",
                description=f"<@!{ctx.author.id}> möchte gerne der Lerngruppe **#{channel.name}** beitreten.",
    
                message=f"<@!{group_config['organizer_id']}>, du wirst gebraucht. Anfrage von <@!{ctx.author.id}>:",
    
                custom_prefix="learninggroups:join"
    
            await utils.send_dm(ctx.author, f"Deine Anfrage wurde an **#{channel.name}** gesendet. "
    
                                            "Sobald die Organisatorin der Lerngruppe darüber "
                                            "entschieden hat, bekommst du Bescheid.")
    
    dnns01's avatar
    dnns01 committed
    
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg kick <@usermention>",
            brief="Wirft @usermention aus der Gruppe."
        )
        @cmd_lg.command(name="kick")
        async def cmd_kick(self, ctx, arg_member: disnake.Member):
    
            if self.is_group_organizer(ctx.channel, ctx.author) or utils.is_mod(ctx):
    
                group_config = self.groups["groups"].get(str(ctx.channel.id))
                if not group_config:
                    await ctx.channel.send("Das ist keine gültiger Lerngruppenkanal.")
                    return
    
                await self.remove_member_from_group(ctx.channel, arg_member)
                await self.update_permissions(ctx.channel)
    
        @help(
            command_group="lg",
            category="learninggroups",
            syntax="!lg leave",
            brief="Du verlässt die Lerngruppe."
        )
        @cmd_lg.command(name="leave")
        async def cmd_leave(self, ctx):
            group_config = self.groups["groups"].get(str(ctx.channel.id))
            if not group_config:
                await ctx.channel.send("Das ist keine gültiger Lerngruppenkanal.")
                return
    
    
            if group_config["organizer_id"] == ctx.author.id:
                await ctx.channel.send("Du kannst nicht aus deiner eigenen Lerngruppe flüchten. Gib erst die Verantwortung ab.")
    
                return
    
            await self.remove_member_from_group(ctx.channel, ctx.author)
            await self.update_permissions(ctx.channel)
    
    
        async def on_group_request(self, confirmed, button, interaction: InteractionMessage):
    
            channel = interaction.channel
            member = interaction.author
            message = interaction.message
    
            if str(channel.id) == str(self.channel_request):
                request = self.groups["requested"].get(str(message.id))
                if confirmed and self.is_mod(member):
                    await self.add_requested_group_channel(message, direct=False)
    
    
                elif not confirmed and (self.is_request_organizer(request, member) or self.is_mod(member)):
    
                    if self.is_mod(member):
    
                        user = await self.bot.fetch_user(request["organizer_id"] )
    
                        if user:
                            await utils.send_dm(user, f"Deine Lerngruppenanfrage für #{self.full_channel_name(request)} wurde abgelehnt.")
    
                    await self.remove_group_request(message)
    
                    await message.delete()
    
    
        async def on_join_request(self, confirmed, button, interaction: InteractionMessage):
    
            channel = interaction.channel
            member = interaction.author
            message = interaction.message
            group_config = self.groups["groups"].get(str(channel.id))
    
            if not group_config:
                return