Пример #1
0
def add_setting(guild_id: int,
                setting: str,
                value: Union[str, int],
                active=True,
                set_by="",
                set_date=datetime.now()):
    """
    Add an entry to the settings database

    :param guild_id: id the setting is in
    :param value: value of the setting - probably name of a word-list
    :param set_by: user id or name of the member who entered that setting - could be neat for logs
    :param set_date: date the setting was configured
    :param setting: setting type to add
    :param active: if setting shall be active, not used at the moment
    """

    if type(value) is int:
        value = str(value)

    session = db.open_session()
    entry = db.Settings(guild_id=guild_id,
                        setting=setting,
                        value=value,
                        is_active=active,
                        set_by=set_by,
                        set_date=set_date)
    session.add(entry)
    session.commit()
    session.close()
Пример #2
0
def add_channel(voice_channel_id: int, text_channel_id: str, guild_id: int, internal_type: str,
                category=None, set_by='unknown', set_date=datetime.now()):
    """
    :param voice_channel_id: id of created voice channel
    :param text_channel_id: id of linked text_channel
    :param guild_id: id of the guild the channels were created on
    :param internal_type: type of the channels 'public' or 'private' etc
    :param category: optional category the channels are in
    :param set_by: optional which module issued creation
    :param set_date: date the channel was created - default is datetime.now()
    """

    session = db.open_session()

    entry = db.CreatedChannels(
        voice_channel_id=voice_channel_id,
        text_channel_id=text_channel_id,
        guild_id=guild_id,
        internal_type=internal_type,
        category=category,
        set_by=set_by,
        set_date=set_date
    )

    session.add(entry)
    session.commit()
    session.close()
Пример #3
0
def get_voice_channel_by_id(channel_id: int, session=db.open_session()):

    statement = select(db.CreatedChannels).where(
        db.CreatedChannels.voice_channel_id == channel_id
    )

    entry = session.execute(statement).first()
    return entry[0] if entry else None
Пример #4
0
def del_channel(voice_channel_id: int):
    session = db.open_session()

    statement = delete(db.CreatedChannels).where(
            db.CreatedChannels.voice_channel_id == voice_channel_id
    )
    session.execute(statement)
    session.commit()
    session.close()
Пример #5
0
def del_setting_by_setting(guild_id: int, setting: str):
    """
    Delete an entry from the settings table by giving only the settings name

    :param guild_id: id the setting is in
    :param setting: the setting - like archive or prefix
    """

    session = db.open_session()
    statement = delete(db.Settings).where(
        and_(db.Settings.guild_id == guild_id, db.Settings.setting == setting))
    session.execute(statement)
    session.commit()
    session.close()
Пример #6
0
def get_first_setting_for(
    guild_id: int, setting: str,
    session=db.open_session()) -> Union[db.Settings, None]:
    """
    Wrapper around get_all_settings_for() that extracts the first entry from returned list

    :param guild_id: id of the guild to search for
    :param setting: name of the setting to search for
    :param session: session to search with, helpful if object shall be edited, since the same session is needed for this

    :return: first setting to match the query
    """

    entries = get_all_settings_for(guild_id, setting, session)

    return entries[0] if entries else None
Пример #7
0
def get_all_settings_for(
    guild_id: int, setting: str,
    session=db.open_session()) -> Union[List[db.Settings], None]:
    """
    Searches db for setting in a guild that matches the setting name

    :param guild_id: id of the guild to search for
    :param setting: name of the setting to search for
    :param session: session to search with, helpful if object shall be edited, since the same session is needed for this

    :return: list of settings that match the given given setting name
    """

    sel_statement = select(db.Settings).where(
        and_(db.Settings.guild_id == guild_id, db.Settings.setting == setting))
    entries = session.execute(sel_statement).all()
    return [entry[0] for entry in entries] if entries else None
Пример #8
0
def del_setting_by_value(guild_id: int, value: Union[str, int]):
    """
    Delete an entry from the settings table by giving only the value

    :param guild_id: id the setting is in
    :param value: value of the setting like a channel id
    """

    if type(value) is int:
        value = str(int)

    session = db.open_session()
    statement = delete(db.Settings).where(
        and_(db.Settings.guild_id == guild_id, db.Settings.value == value))
    session.execute(statement)
    session.commit()
    session.close()
Пример #9
0
def get_setting_by_value(
    guild_id: int, value: Union[str, int],
    session=db.open_session()) -> Union[db.Settings, None]:
    """
    Used to extract a setting that has a channel id as value and an unknown setting-name

    :param guild_id: guild  to search on
    :param value: settings value to search for
    :param session: will be created if none is passed in

    :return: database entry if exists with those specific parameters, else None
    """

    statement = select(db.Settings).where(
        and_(db.Settings.guild_id == guild_id,
             db.Settings.value == str(value)))
    entry = session.execute(statement).first()
    return entry[0] if entry else None
Пример #10
0
def get_setting(
    guild_id: int, setting: str, value: str,
    session=db.open_session()) -> Union[db.Settings, None]:
    """
    Searches db for one specific setting and returns if if exists

    :param guild_id: id of the guild to search for
    :param value: value of the setting to search for
    :param setting: name of the setting to search for
    :param session: session to search with, helpful if object shall be edited, since the same session is needed for this.

    :return: database entry if exists with those specific parameters, else None
    """

    sel_statement = select(db.Settings).where(
        and_(db.Settings.guild_id == guild_id, db.Settings.setting == setting,
             db.Settings.value == value))
    entry = session.execute(sel_statement).first()
    return entry[0] if entry else None
Пример #11
0
def del_setting(guild_id: int, setting: str, value: Union[str, int]):
    """
    Delete an entry from the settings table

    :param guild_id: id the setting is in
    :param value: value of the setting - probably name of a word-list
    :param setting: setting type to delete
    """

    if type(value) is int:
        value = str(int)

    session = db.open_session()
    statement = delete(db.Settings).where(
        and_(db.Settings.guild_id == guild_id, db.Settings.setting == setting,
             db.Settings.value == value))
    session.execute(statement)
    session.commit()
    session.close()
Пример #12
0
    async def update_value_or_create_entry(ctx: commands.Context,
                                           setting_name: str, set_value: str,
                                           value_name: str):
        """
        Check if an entry exists based on setting name, alter its value\n
        Create a new entry if no setting was found\n
        -> If there is a setting with the name 'prefix' update setting.value = new_prefix\n
        Send update messages on discord

        :param ctx: command context
        :param setting_name: name of the setting
        :param set_value: value the setting shall be set to
        :param value_name: name the set value should have in bot message

        """
        # check if there is an entry for that setting - toggle it
        session = db_models.open_session()

        entry = settings_db.get_first_setting_for(ctx.guild.id,
                                                  setting_name,
                                                  session=session)

        if entry:
            entry.value = set_value
            session.add(entry)
            session.commit()

            # send reply
            await Settings.send_setting_updated(ctx, setting_name, value_name)

            return

        settings_db.add_setting(guild_id=ctx.guild.id,
                                setting=setting_name,
                                value=set_value,
                                set_by=f"{ctx.author.id}")

        session.close()

        # send reply
        await Settings.send_setting_added(ctx, setting_name, value_name)
Пример #13
0
def get_channels_by_type(guild_id: int, internal_type: str,
                         session=db.open_session()) -> Union[List[db.CreatedChannels], None]:
    """
    Get all channels of an internal type by it's name

    :param guild_id: guild to search on
    :param internal_type: type of channels to search - e.g. 'public_channel'
    :param session: optional if an entry shall be updated

    :return: list of all channels of that 'class'
    """

    statement = select(db.CreatedChannels).where(
        and_(
            db.CreatedChannels.guild_id == guild_id,
            db.CreatedChannels.internal_type == internal_type
        )
    )

    entries = session.execute(statement).all()
    return [entry[0] for entry in entries] if entries else None
Пример #14
0
    async def get_settings(self, ctx):
        """
        prints setting on guild
        """

        tracked_channels = []

        # conversion to set since some keys appear multiple times due to aliases

        # create session externally to remove flawed entries
        # this should never happen, except during development.
        # But we can prevent crashes due to unexpected circumstances
        session = db_models.open_session()
        for setting in set(settings.values()):
            entries = settings_db.get_all_settings_for(ctx.guild.id,
                                                       setting,
                                                       session=session)
            if entries:
                tracked_channels.extend(entries)

        stc = "__Static Channels:__\n"  # TODO: Implement static channels in settings - extra db access + loop needed
        pub = "__Public Channels:__\n"
        priv = "__Private Channels:___\n"
        log = "__Log Channel:__\n"
        archive = "__Archive Category:__\n"
        prefix = "__Prefixes:__\n"
        if not tracked_channels:
            emb = utils.make_embed(
                color=utils.yellow,
                name="No configuration yet",
                value="You didn't configure anything yet.\n"
                f"Use the help command "
                f"or try the quick-setup command `{PREFIX}setup @role` to get started :)"
            )
            await ctx.send(embed=emb)
            return

        for i, elm in enumerate(tracked_channels):  # building strings
            # security check to prevent the command from crashing
            # if an entry has a NoneType value it's useless an can be deleted
            if elm.setting is None or elm.value is None:
                session.delete(elm)
                logger.warning(
                    f"Deleting setting, because containing NoneType values:\n{elm}"
                )
                continue

            if elm.setting == "public_channel":
                pub += f"`{ctx.guild.get_channel(int(elm.value))}` with ID `{elm.value}`\n"

            elif elm.setting == "private_channel":
                priv += f"`{ctx.guild.get_channel(int(elm.value))}` with ID `{elm.value}`\n"

            elif elm.setting == "log_channel":
                log += f"`{ctx.guild.get_channel(int(elm.value)).mention}` with ID `{elm.value}`\n"

            elif elm.setting == "archive_category":
                archive += f"`{ctx.guild.get_channel(int(elm.value))}` with ID `{elm.value}`\n"
            elif elm.setting == "prefix":
                prefix += f"`{elm.value}` example `{elm.value}help`\n"

        session.commit()  # delete all flawed entries
        session.close()

        emby = utils.make_embed(color=utils.blue_light,
                                name="Server Settings",
                                value=f"‌\n"
                                f"{stc}\n"
                                f"{pub}\n"
                                f"{priv}\n"
                                f"{archive}\n"
                                f"{log}\n"
                                f"{prefix}")
        await ctx.send(embed=emby)
Пример #15
0
    async def set_setting(self, ctx: commands.Context, *params):
        # first param setting name, second param setting value
        syntax = "Example: `set [setting name] [setting value]`"

        try:
            setting = params[0]

        except IndexError:

            msg = (f"Please ensure that you've entered a valid setting \
                                and channel-id or role-id for that setting.\n{syntax}"
                   )
            emby = utils.make_embed(color=discord.Color.orange(),
                                    name="Can't get setting name",
                                    value=msg)
            await ctx.send(embed=emby)

            return

        try:
            value = params[1]

        except IndexError:

            msg = (f"Please ensure that you've entered a valid \
            channel-id / role-id or other required parameter for that setting.\n{syntax}"
                   )
            emby = utils.make_embed(color=discord.Color.orange(),
                                    name="Can't get value",
                                    value=msg)
            await ctx.send(embed=emby)

            return

        # look if setting name is valid
        setting = setting.lower(
        )  # dict only handles lower case, so do we all the time
        setting_type = settings.get(setting, None)

        if not setting_type:
            await ctx.send(embed=utils.make_embed(
                name=f"'{setting}' is no valid setting name",
                value=
                "Use the help command to get an overview of possible settings",
                color=utils.yellow))
            return

        # setting is validated, let's see if the value matches the required setting
        value = value.strip()  # just in case
        set_value, set_name = None, None

        # first check 'easy' cases
        if setting_type in ['archive_category', 'log_channel']:
            # trying to get a corresponding channel (id: str, name/ mention: str)
            set_value, set_name = await self.channel_from_input(
                ctx, setting_type, value)

        elif setting_type == "prefix":
            # need to await since it sends the error message if we can't match
            set_value, set_name = await self.prefix_validation(ctx, value)

        elif setting_type == "allow_public_rename":
            set_value, set_name = await self.validate_toggle(ctx, value)

        # enter to database if value is correct
        if set_value:
            print(f"{set_value=}")
            await self.update_value_or_create_entry(ctx, setting_type,
                                                    set_value, set_name)
            return  # we're done - the other handling isn't needed

        # handle the addition of static channels
        # those are voice channels that are persistent but receive a new text channel every time a member joins
        if setting_type in ['static_channel']:
            # trying to get a corresponding channel (id: str, name/ mention: str)
            set_value, set_name = await self.channel_from_input(
                ctx, setting_type, value)

            # given channel id seems flawed - returning
            if not set_value:
                return

            channel: discord.VoiceChannel = await self.validate_channel(
                ctx, set_value)

            if channel is None:
                return

            session = db_models.open_session()
            entry: Union[db_models.CreatedChannels,
                         None] = channels_db.get_voice_channel_by_id(
                             set_value, session)

            if entry:
                entry.internal_type = setting_type
                session.add(entry)
                session.commit()

                await self.send_setting_updated(ctx, setting_type, set_name)
                return

            # delete old setting if channels was e.g. public-channel before
            settings_db.del_setting_by_value(ctx.guild.id, set_value)
            channels_db.add_channel(voice_channel_id=int(set_value),
                                    text_channel_id=None,
                                    guild_id=ctx.guild.id,
                                    internal_type=setting_type,
                                    category=channel.category_id,
                                    set_by=ctx.author.id)

            session.close()
            await self.send_setting_added(ctx, setting_type, set_name)

        # now handle tracked channels
        # tracked channels require an other database handling as the other settings
        # we need to check if there is a setting with that value and adjust the setting_name
        # in all other cases it's we check the settings name and alter the value...
        # e.g. if there is a setting for setting.value == channel_id change setting.name to channel_type
        if setting_type in ['public_channel', 'private_channel']:
            # trying to get a corresponding channel (id: str, name/ mention: str)
            set_value, set_name = await self.channel_from_input(
                ctx, setting_type, value)

            session = db_models.open_session()
            entry: Union[db_models.Settings,
                         None] = settings_db.get_setting_by_value(
                             ctx.guild.id, set_value, session)

            # given channel id seems flawed - returning
            if not set_value:
                return

            # if channel is already registered - update
            if entry:
                entry.setting = setting_type

                session.add(entry)
                session.commit()

                # send reply
                await self.send_setting_updated(ctx, setting_type, set_name)

            # create new entry, channel not tracked yet
            else:

                # delete old entry in channels db if it exists - maybe channel was static channel before
                channels_db.del_channel(int(set_value))

                # write entry to db
                settings_db.add_setting(
                    guild_id=ctx.guild.id,
                    setting=setting_type,
                    value=set_value,
                    set_by=ctx.author.id,
                )

                # send reply
                await self.send_setting_updated(ctx, setting_type, set_name)

            session.close()
Пример #16
0
    async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState,
                                    after: discord.VoiceState):

        # this is the case that a state update happens that is not a channel switch, but a mute or something like that
        if before.channel and after.channel and before.channel.id == after.channel.id:
            return

        # as shorthand - we'll need this a few times
        guild: discord.Guild = member.guild
        bot_member_on_guild: discord.Member = guild.get_member(self.bot.user.id)
        after_channel: Union[discord.VoiceChannel, None] = after.channel
        before_channel: Union[discord.VoiceChannel, None] = before.channel

        # open db session
        session = db.open_session()

        # get settings for archive and log channel
        log_entry = settings_db.get_first_setting_for(guild.id, "log_channel", session)  # get entry if exists
        archive_entry = settings_db.get_first_setting_for(guild.id, "archive_category", session)

        # get channels from entries if existing
        log_channel: Union[discord.TextChannel, None] = guild.get_channel(int(log_entry.value)) if log_entry else None
        archive_category: Union[discord.CategoryChannel, None] = guild.get_channel(
            int(archive_entry.value)) if archive_entry else None

        # check if member has a voice channel after the state update
        # could trigger the creation of a new channel or require an update for an existing one
        if after_channel:

            # check db if channel is a channel that was created by the bot
            created_channel: Union[db.CreatedChannels, None] = channels_db.get_voice_channel_by_id(after_channel.id, session)

            # check if joined (after) channel is a channel that triggers a channel creation
            tracked_channel = settings_db.get_setting_by_value(guild.id, after_channel.id, session)

            if tracked_channel:
                voice_channel, text_channel = await create_new_channels(member, after,
                                                                        tracked_channel.setting, bot_member_on_guild)

                # write to log channel if configured
                if log_entry:
                    await log_channel.send(
                        embed=utl.make_embed(
                            name="Created voice channel",
                            value=f"{member.mention} created `{voice_channel.name if voice_channel else '`deleted`'}` "
                                  f"with {text_channel.mention if text_channel else '`deleted`'}",
                            color=utl.green
                        )
                    )

                # moving creator to created channel
                try:
                    await member.move_to(voice_channel, reason=f'{member} issued creation')
                    await send_welcome_message(text_channel, voice_channel)  # send message explaining text channel
                    
                # if user already left already
                except discord.HTTPException as e:
                    print("Handle HTTP exception during creation of channels - channel was already empty")
                    await clean_after_exception(voice_channel, text_channel, self.bot,
                                                archive=archive_category, log_channel=log_channel)

            # channel is in our database - add user to linked text_channel
            elif created_channel:

                # static channels need a new linked text-channel if they were empty before
                if created_channel.internal_type == 'static_channel' and created_channel.text_channel_id is None:

                    try:
                        tc_overwrite = generate_text_channel_overwrite(after_channel, self.bot.user)
                        text_channel = await guild.create_text_channel(f"{tc_sign_prefix}{after_channel.name}",
                                                                       overwrites=tc_overwrite,
                                                                       category=after_channel.category,
                                                                       reason="User joined linked voice channel")
                        created_channel.text_channel_id = text_channel.id
                        session.add(created_channel)
                        session.flush()

                        await send_welcome_message(text_channel, after_channel)  # send message explaining text channel

                    except discord.HTTPException as e:
                        # TODO: log this
                        pass

                # processing 'normal', existing linked channel
                else:
                    # update overwrites to add user to joined channel
                    # TODO we can skip this API call when the creator just got moved
                    await update_channel_overwrites(after_channel, created_channel, bot_member_on_guild)

        if before_channel:

            # check db if before channel is a channel that was created by the bot
            created_channel: Union[db.CreatedChannels, None] = channels_db.get_voice_channel_by_id(before_channel.id, session)

            if created_channel:
                # member left but there are still members in vc
                if before_channel.members:
                    # remove user from left linked channel
                    await update_channel_overwrites(before_channel, created_channel, bot_member_on_guild)

                # left channel is now empty
                else:
                    # fetch needed information
                    before_channel_id: int = before_channel.id  # extract id before deleting, needed for db deletion
                    text_channel: Union[discord.TextChannel, None] = guild.get_channel(created_channel.text_channel_id)

                    # delete channels - catch AttributeErrors to still do the db access and the logging

                    # delete VC only if it's not a static_channel
                    if created_channel.internal_type != 'static_channel':
                        try:
                            await before_channel.delete(reason="Channel is empty")
                        except AttributeError:
                            pass

                    # archive or delete linked text channel
                    try:
                        archived_channel = await delete_text_channel(text_channel, self.bot, archive=archive_category)

                    except AttributeError:
                        archived_channel = None

                    except discord.errors.HTTPException:
                        # occurs when category that the channel shall be moved to is full
                        archived_channel = None
                        await log_channel.send(
                            embed=utl.make_embed(
                                name="ERROR handling linked text channel",
                                value=f"This error probably means that the archive `{archive_category.mention}` is full.\n"
                                      "Please check the category and it and set a new one or delete older channels.\n"
                                      "Text channel was not deleted",
                                color=utl.red))

                    if log_channel:
                        static = True if created_channel.internal_type == 'static_channel' else False  # helper variable

                        await log_channel.send(
                            embed=utl.make_embed(
                                name=f"Removed {text_channel.name}" if static else f"Deleted {before_channel.name}",
                                value=f"{text_channel.mention} was linked to {before_channel.name} and is " if static
                                      else f"The linked text channel {text_channel.mention} is "
                                      f"{'moved to archive' if archived_channel is not None and archive_category else 'deleted'}",
                                color=utl.green
                            )
                        )

                    if created_channel.internal_type == 'static_channel':
                        # remove reference to now archived channel
                        created_channel.text_channel_id = None
                        session.add(created_channel)
                        session.flush()

                    else:
                        # remove deleted channel from database
                        channels_db.del_channel(before_channel_id)

        session.commit()
        session.close()