<commit_before>import logging
from functools import partial

import discord
from discord.ext.commands import BucketType, has_permissions
from sqlalchemy.exc import SQLAlchemyError

from bot.bot import command, group, cooldown
from bot.formatter import Paginator
from bot.globals import BlacklistTypes, PermValues
import typing
from discord.ext import commands
from bot.converters import CommandConverter
from cogs.cog import Cog
from utils.utilities import (check_channel_mention, check_role_mention,
                             check_user_mention,
                             split_string, get_role, send_paged_message)

logger = logging.getLogger('debug')
perms = discord.Permissions(8)


class CommandBlacklist(Cog):
    def __init__(self, bot):
        super().__init__(bot)

    @group(ignore_extra=True, no_pm=True, invoke_without_command=True)
    @has_permissions(administrator=True)
    @cooldown(1, 5, type=BucketType.guild)
    async def blacklist(self, ctx, commands: commands.Greedy[CommandConverter], mention: typing.Union[discord.TextChannel, discord.Role, discord.User]=None):
        """Blacklist a command for a user, role or channel
        To blacklist multiple commands at the same time wrap the command names in quotes
        like this {prefix}{name} \"command1 command2 command3\" #channel
        The hierarchy of `blacklist` and `whitelist` is as follows
        Whitelist always overrides blacklist of the same level

        Then levels of scope it can have are as follows
        `User` > `Role` > `Channel` > `Server` where each level overrides every scope perm after it
        e.g. Blacklisting command ping for role Member and whitelisting it for role Mod
        would make it so people with Member role wouldn't be able to use it unless they had Mod role

        Also if you further whitelisted ping from a single member
        that user would be able to use the command always
        since user whitelist overrides every other scope

        To blacklist a command server wide specify the commands and don't specify the mention param
        like this `{prefix}blacklist "cmd1 cmd2 etc"` which would blacklist those commands
        for everyone in the server unless they have it whitelisted
        Whitelisting server wide isn't possible

        For dangers of whitelisting see `{prefix}help whitelist`"""
        guild = ctx.guild

        async def _blacklist(name):
            if mention is None:
                whereclause = 'guild=%s AND type IN (%s, %s) AND command="%s" AND channel IS NULL AND role IS NULL AND user IS NULL' % (
                               guild.id, BlacklistTypes.BLACKLIST, BlacklistTypes.WHITELIST, name)
                success = await self._set_blacklist(ctx, whereclause, guild=guild.id, command=name)
                if success:
                    return 'Blacklisted command {} from this server'.format(name)
                elif success is None:
                    return 'Removed blacklist for command {} on this server'.format(name)

            elif isinstance(mention, discord.User):
                return await self._add_user_blacklist(ctx, name, mention, guild)

            elif isinstance(mention, discord.Role):
                return await self._add_role_blacklist(ctx, name, mention, guild)

            elif isinstance(mention, discord.TextChannel):
                return await self._add_channel_blacklist(ctx, name, mention, guild)

        s = ''
        for command in commands:
            if command.name == 'privacy':
                await ctx.send("Cannot blacklist privacy command as it's required that anyone can see it")
                continue

            val = await _blacklist(command.name)
            if isinstance(val, str):
                s += val + '\n'

        if not s:
            return

        for msg in split_string(s, splitter='\n'):
            await ctx.send(msg)

    @blacklist.command(ignore_extra=True, no_pm=True)
    @has_permissions(administrator=True)
    async def toggle(self, ctx):
        """
        Disable all commands on this server (owner will still be able to use them)
        Whitelisting commands also overrides this rule
        Won't override existing commands that have been blacklisted so when you toggle
        again the commands that have been specifically blacklisted are still blacklisted
        """

        guild = ctx.guild
        values = {'command': None, 'guild': guild.id, 'type': BlacklistTypes.BLACKLIST}
        where = 'guild=%s AND command IS NULL AND NOT type=%s AND user IS NULL AND role IS NULL AND channel IS NULL' % (guild.id, BlacklistTypes.GLOBAL)
        success = await self._set_blacklist(where, **values)
        if success:
            msg = 'All commands disabled on this server for non whitelisted users'
        elif success is None:
            msg = 'Commands are usable on this server again'
        else:
            return

        await ctx.send(msg)

    async def _set_all_commands(self, ctx, guild, msg, mention, type=BlacklistTypes.BLACKLIST):
        values = {'command': None, 'guild': guild.id, 'type': type}
        role = check_role_mention(msg, mention, guild)
        where = 'guild=%s AND command IS NULL AND NOT type=%s AND ' % (guild.id, BlacklistTypes.GLOBAL)
        type_string = 'Blacklisted' if type == BlacklistTypes.BLACKLIST else 'Whitelisted'
        type_string2 = 'blacklist' if type == BlacklistTypes.BLACKLIST else 'whitelist'
        message = None
        if check_user_mention(msg, mention):
            userid = msg.mentions[0].id
            success = await self._set_blacklist(ctx, where + 'user=%s' % userid, user=userid, **values)
            if success:
                message = '%s all commands for user %s' % (type_string, msg.mentions[0])
            elif success is None:
                message = 'removed %s from user %s' % (type_string2, msg.mentions[0])

        elif role:
            success = await self._set_blacklist(ctx, where + 'role=%s' % role.id, role=role.id, **values)
            if success:
                message = '{0} all commands from role {1} `{1.id}`'.format(type_string, role)
            elif success is None:
                message = 'Removed {0} from role {1} `{1.id}`'.format(type_string2, role)

        elif check_channel_mention(msg, mention):
            channel = msg.channel_mentions[0]
            success = await self._set_blacklist(ctx, where + 'channel=%s' % channel.id,
                                                channel=channel.id, **values)
            if success:
                message = '{0} all commands from channel {1} `{1.id}`'.format(type_string, channel)
            elif success is None:
                message = 'Removed {0} from channel {1} `{1.id}`'.format(type_string2, channel)

        else:
            return False

        try:
            if message:
                await msg.channel.send(message)
        except discord.HTTPException:
            pass

        return True

    @command(ignore_extra=True, no_pm=True)
    @has_permissions(administrator=True)
    @cooldown(1, 5, type=BucketType.guild)
    async def whitelist(self, ctx, commands: commands.Greedy[CommandConverter], mention: typing.Union[discord.TextChannel, discord.Role, discord.User]):
        """Whitelist a command for a user, role or channel
        To whitelist multiple commands at the same time wrap the command names in quotes
        like this {prefix}{name} \"command1 command2 command3\" #channel

        To see specifics on the hierarchy of whitelist/blacklist see `{prefix}help blacklist`

        **WHITELISTING COULD BE DANGEROUS IF YOU DON'T KNOW WHAT YOU ARE DOING!**
        Before whitelisting read the following

        Whitelisting WILL OVERRIDE ANY REQUIRED PERMS for the command being called
        If a command requires ban perms and you whitelist it for a role
        everyone with that role can use that command even when they don't have ban perms

        Due to safety reasons whitelisting commands from this module is not allowed.
        Give the users correct discord perms instead
        """
        msg = ctx.message
        guild = msg.guild

        async def _whitelist(_command):
            name = _command.name
            if _command.cog_name == self.__class__.__name__:
                return f"Due to safety reasons commands from {_command.cog_name} module can't be whitelisted"

            elif isinstance(mention, discord.User):
                await self._add_user_whitelist(ctx, name, mention, guild)

            elif isinstance(mention, discord.Role):
                await self._add_role_whitelist(ctx, name, mention, guild)

            elif isinstance(mention, discord.TextChannel):
                await self._add_channel_whitelist(ctx, name, mention, guild)

        s = ''
        for command in commands:
            val = await _whitelist(command)
            if isinstance(val, str):
                s += val + '\n'

        if not s:
            return

        for msg in split_string(s, splitter='\n'):
            await ctx.send(msg)

    async def _set_blacklist(self, ctx, whereclause, type_=BlacklistTypes.BLACKLIST, **values):
        """<commit_msg>black/whitelisting now only sends 1 message and uses special converters<commit_after>import logging
from functools import partial

import discord
from discord.ext.commands import BucketType, has_permissions
from sqlalchemy.exc import SQLAlchemyError

from bot.bot import command, group, cooldown
from bot.formatter import Paginator
from bot.globals import BlacklistTypes, PermValues
import typing
from discord.ext import commands
from bot.converters import CommandConverter
from cogs.cog import Cog
from utils.utilities import (check_channel_mention, check_role_mention,
                             check_user_mention,
                             split_string, get_role, send_paged_message)

logger = logging.getLogger('debug')
perms = discord.Permissions(8)


class CommandBlacklist(Cog):
    def __init__(self, bot):
        super().__init__(bot)

    @group(ignore_extra=True, no_pm=True, invoke_without_command=True)
    @has_permissions(administrator=True)
    @cooldown(1, 5, type=BucketType.guild)
    async def blacklist(self, ctx, commands: commands.Greedy[CommandConverter], *, mention: typing.Union[discord.TextChannel, discord.Role, discord.User]=None):
        """Blacklist a command for a user, role or channel
        To blacklist multiple commands at the same time wrap the command names in quotes
        like this {prefix}{name} \"command1 command2 command3\" #channel
        The hierarchy of `blacklist` and `whitelist` is as follows
        Whitelist always overrides blacklist of the same level

        Then levels of scope it can have are as follows
        `User` > `Role` > `Channel` > `Server` where each level overrides every scope perm after it
        e.g. Blacklisting command ping for role Member and whitelisting it for role Mod
        would make it so people with Member role wouldn't be able to use it unless they had Mod role

        Also if you further whitelisted ping from a single member
        that user would be able to use the command always
        since user whitelist overrides every other scope

        To blacklist a command server wide specify the commands and don't specify the mention param
        like this `{prefix}blacklist "cmd1 cmd2 etc"` which would blacklist those commands
        for everyone in the server unless they have it whitelisted
        Whitelisting server wide isn't possible

        For dangers of whitelisting see `{prefix}help whitelist`"""
        guild = ctx.guild

        async def _blacklist(name):
            if mention is None:
                whereclause = 'guild=%s AND type IN (%s, %s) AND command="%s" AND channel IS NULL AND role IS NULL AND user IS NULL' % (
                               guild.id, BlacklistTypes.BLACKLIST, BlacklistTypes.WHITELIST, name)
                success = await self._set_blacklist(ctx, whereclause, guild=guild.id, command=name)
                if success:
                    return 'Blacklisted command {} from this server'.format(name)
                elif success is None:
                    return 'Removed blacklist for command {} on this server'.format(name)

            elif isinstance(mention, discord.User):
                return await self._add_user_blacklist(ctx, name, mention, guild)

            elif isinstance(mention, discord.Role):
                return await self._add_role_blacklist(ctx, name, mention, guild)

            elif isinstance(mention, discord.TextChannel):
                return await self._add_channel_blacklist(ctx, name, mention, guild)

        s = ''
        for command in commands:
            if command.name == 'privacy':
                await ctx.send("Cannot blacklist privacy command as it's required that anyone can see it")
                continue

            val = await _blacklist(command.name)
            if isinstance(val, str):
                s += val + '\n'

        if not s:
            return

        for msg in split_string(s, splitter='\n'):
            await ctx.send(msg)

    @blacklist.command(ignore_extra=True, no_pm=True)
    @has_permissions(administrator=True)
    async def toggle(self, ctx):
        """
        Disable all commands on this server (owner will still be able to use them)
        Whitelisting commands also overrides this rule
        Won't override existing commands that have been blacklisted so when you toggle
        again the commands that have been specifically blacklisted are still blacklisted
        """

        guild = ctx.guild
        values = {'command': None, 'guild': guild.id, 'type': BlacklistTypes.BLACKLIST}
        where = 'guild=%s AND command IS NULL AND NOT type=%s AND user IS NULL AND role IS NULL AND channel IS NULL' % (guild.id, BlacklistTypes.GLOBAL)
        success = await self._set_blacklist(where, **values)
        if success:
            msg = 'All commands disabled on this server for non whitelisted users'
        elif success is None:
            msg = 'Commands are usable on this server again'
        else:
            return

        await ctx.send(msg)

    async def _set_all_commands(self, ctx, guild, msg, mention, type=BlacklistTypes.BLACKLIST):
        values = {'command': None, 'guild': guild.id, 'type': type}
        role = check_role_mention(msg, mention, guild)
        where = 'guild=%s AND command IS NULL AND NOT type=%s AND ' % (guild.id, BlacklistTypes.GLOBAL)
        type_string = 'Blacklisted' if type == BlacklistTypes.BLACKLIST else 'Whitelisted'
        type_string2 = 'blacklist' if type == BlacklistTypes.BLACKLIST else 'whitelist'
        message = None
        if check_user_mention(msg, mention):
            userid = msg.mentions[0].id
            success = await self._set_blacklist(ctx, where + 'user=%s' % userid, user=userid, **values)
            if success:
                message = '%s all commands for user %s' % (type_string, msg.mentions[0])
            elif success is None:
                message = 'removed %s from user %s' % (type_string2, msg.mentions[0])

        elif role:
            success = await self._set_blacklist(ctx, where + 'role=%s' % role.id, role=role.id, **values)
            if success:
                message = '{0} all commands from role {1} `{1.id}`'.format(type_string, role)
            elif success is None:
                message = 'Removed {0} from role {1} `{1.id}`'.format(type_string2, role)

        elif check_channel_mention(msg, mention):
            channel = msg.channel_mentions[0]
            success = await self._set_blacklist(ctx, where + 'channel=%s' % channel.id,
                                                channel=channel.id, **values)
            if success:
                message = '{0} all commands from channel {1} `{1.id}`'.format(type_string, channel)
            elif success is None:
                message = 'Removed {0} from channel {1} `{1.id}`'.format(type_string2, channel)

        else:
            return False

        try:
            if message:
                await msg.channel.send(message)
        except discord.HTTPException:
            pass

        return True

    @command(ignore_extra=True, no_pm=True)
    @has_permissions(administrator=True)
    @cooldown(1, 5, type=BucketType.guild)
    async def whitelist(self, ctx, commands: commands.Greedy[CommandConverter], *, mention: typing.Union[discord.TextChannel, discord.Role, discord.User]):
        """Whitelist a command for a user, role or channel
        To whitelist multiple commands at the same time wrap the command names in quotes
        like this {prefix}{name} \"command1 command2 command3\" #channel

        To see specifics on the hierarchy of whitelist/blacklist see `{prefix}help blacklist`

        **WHITELISTING COULD BE DANGEROUS IF YOU DON'T KNOW WHAT YOU ARE DOING!**
        Before whitelisting read the following

        Whitelisting WILL OVERRIDE ANY REQUIRED PERMS for the command being called
        If a command requires ban perms and you whitelist it for a role
        everyone with that role can use that command even when they don't have ban perms

        Due to safety reasons whitelisting commands from this module is not allowed.
        Give the users correct discord perms instead
        """
        msg = ctx.message
        guild = msg.guild

        async def _whitelist(_command):
            name = _command.name
            if _command.cog_name == self.__class__.__name__:
                return f"Due to safety reasons commands from {_command.cog_name} module can't be whitelisted"

            elif isinstance(mention, discord.User):
                return await self._add_user_whitelist(ctx, name, mention, guild)

            elif isinstance(mention, discord.Role):
                return await self._add_role_whitelist(ctx, name, mention, guild)

            elif isinstance(mention, discord.TextChannel):
                return await self._add_channel_whitelist(ctx, name, mention, guild)

        s = ''
        for command in commands:
            val = await _whitelist(command)
            if isinstance(val, str):
                s += val + '\n'

        if not s:
            return

        for msg in split_string(s, splitter='\n'):
            await ctx.send(msg)

    async def _set_blacklist(self, ctx, whereclause, type_=BlacklistTypes.BLACKLIST, **values):
        """
{'fraction_non_alphanumeric': 0.07927060224013852, 'fraction_numerical': 0.0036253449488664034, 'mean_word_length': 2.455871353777113, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7525019', 'n_tokens_mistral': 5085, 'n_tokens_neox': 4864, 'n_words': 1772}