Example #1
0
async def check_warns(bot, message):
    """Checks for issued warnings."""
    if (not message.guild or
            message.guild.id != configurations.get(bot, __name__, 'guild_id')
            or not message.content.lower().startswith('!warn ')
            or not data.is_mod(bot, member=message.author)
            or 'autolog.py' not in bot.plugins):
        return

    split, quotes = parser.split_parameters(message.content,
                                            include_quotes=True,
                                            quote_list=True)
    if len(split) < 3:  # Not enough arguments
        return
    name = split[2][1:-1] if 2 in quotes else split[2]
    member = data.get_member(bot,
                             name,
                             guild=message.guild,
                             safe=True,
                             strict=True)
    if not member or data.is_mod(
            bot, member=member):  # Member not found or is another mod
        return

    details = '{0} (<@{0.id}>) was warned by {1} (<@{1.id}>): {2}'.format(
        member, message.author, ''.join(split[4:]) or "No warn reason given")
    await bot.plugins['autolog.py'].automated_dump_message(
        bot,
        message.guild,
        details,
        query=member.id,
        moderator_id=message.author.id)
Example #2
0
def get_commands(bot):
    new_commands = []

    new_commands.append(Command(
        'gdq', subcommands=[
            SubCommand(doc='Shows the GDQ menu.'),
            SubCommand(Opt('about'), doc='Shows some basic information about GDQ.'),
            SubCommand(
                Opt('status'),
                doc='Shows the stream status and total amount amount of money raised.'),
            SubCommand(Opt('current'), doc='Shows the current game being played.'),
            SubCommand(
                Opt('next'),
                Arg('number', convert=int, check=lambda b, m, v, *a: 1 <= v <= 5,
                    check_error='Must be between 1 and 5 inclusive.', default=1,
                    argtype=ArgTypes.OPTIONAL, quotes_recommended=False),
                doc='Shows the next game(s). If a number is given (between 1 and 5 '
                    'inclusive), it will show the next number of games.'),
            SubCommand(
                Opt('search'), Arg('title', argtype=ArgTypes.MERGED),
                doc='Searches for the given game.'),
            SubCommand(
                Opt('notify'),
                Opt('channel', optional=True,
                    check=lambda b, m, v, *a: data.is_mod(b, m.guild, m.author.id),
                    check_error='Only bot moderators can notify the channel.'),
                Arg('title', argtype=ArgTypes.MERGED),
                doc='Sends a message to either the user or the channel for the given '
                    'game when it is about to be streamed (approximately 5-10 minutes '
                    'beforehand).\nOnly bot moderators can use the channel option.')],
        description='Games Done Quick for Discord.', category='service'))

    return new_commands
Example #3
0
async def can_interact(bot, member, channel_id=None):
    """Checks that the given member can be interacted with.

    This ensures that the user is:
    Not a bot
    Not blocked in the server

    Additionally, if the user is a member (guild exists):
    Not in a blocked channel
    Not blacklisted by the botowners

    If given a channel ID, also checks that the bot is not muted in there

    This also checks for maintenace mode
    """
    if data.is_owner(bot, member.id):
        return True
    elif member.bot or member.id in data.get(bot, 'core', 'blacklist', default=[]):
        return False
    elif bot.maintenance_mode:
        return False

    # Guild specific check
    guild = getattr(member, 'guild', None)
    if guild:
        if data.is_mod(bot, member=member):
            return True
        guild_data = data.get(bot, 'core', None, guild.id, default={})
        if (guild_data.get('muted', False) or
                (channel_id in guild_data.get('muted_channels', [])) or
                (member.id in guild_data.get('blocked', []))):
            return False

    return True
Example #4
0
async def check_warns(bot, message):
    """Checks for issued warnings."""
    if (not message.guild or
            message.guild.id != configurations.get(bot, __name__, 'guild_id') or
            not message.content.lower().startswith('!warn ') or
            not data.is_mod(bot, member=message.author) or
            'autolog.py' not in bot.plugins):
        return

    split, quotes = parser.split_parameters(message.content, include_quotes=True, quote_list=True)
    if len(split) < 3:  # Not enough arguments
        return
    name = split[2][1:-1] if 2 in quotes else split[2]
    member = data.get_member(bot, name, guild=message.guild, safe=True, strict=True)
    if not member or data.is_mod(bot, member=member):  # Member not found or is another mod
        return

    details = '{0} (<@{0.id}>) was warned by {1} (<@{1.id}>): {2}'.format(
        member, message.author, ''.join(split[4:]) or "No warn reason given")
    await bot.plugins['autolog.py'].automated_dump_message(
        bot, message.guild, details, query=member.id, moderator_id=message.author.id)
Example #5
0
def get_commands(bot):
    new_commands = []

    new_commands.append(
        Command(
            'gdq',
            subcommands=[
                SubCommand(doc='Shows the GDQ menu.'),
                SubCommand(Opt('about'),
                           doc='Shows some basic information about GDQ.'),
                SubCommand(
                    Opt('status'),
                    doc=
                    'Shows the stream status and total amount amount of money raised.'
                ),
                SubCommand(Opt('current'),
                           doc='Shows the current game being played.'),
                SubCommand(
                    Opt('next'),
                    Arg('number',
                        convert=int,
                        check=lambda b, m, v, *a: 1 <= v <= 5,
                        check_error='Must be between 1 and 5 inclusive.',
                        default=1,
                        argtype=ArgTypes.OPTIONAL,
                        quotes_recommended=False),
                    doc=
                    'Shows the next game(s). If a number is given (between 1 and 5 '
                    'inclusive), it will show the next number of games.'),
                SubCommand(Opt('search'),
                           Arg('title', argtype=ArgTypes.MERGED),
                           doc='Searches for the given game.'),
                SubCommand(
                    Opt('notify'),
                    Opt('channel',
                        optional=True,
                        check=lambda b, m, v, *a: data.is_mod(
                            b, m.guild, m.author.id),
                        check_error=
                        'Only bot moderators can notify the channel.'),
                    Arg('title', argtype=ArgTypes.MERGED),
                    doc=
                    'Sends a message to either the user or the channel for the given '
                    'game when it is about to be streamed (approximately 5-10 minutes '
                    'beforehand).\nOnly bot moderators can use the channel option.'
                )
            ],
            description='Games Done Quick for Discord.',
            category='service'))

    return new_commands
Example #6
0
async def character_forceremove(bot, context):
    """Forcibly removes the character of the given user."""
    owner = context.arguments[0]
    if data.is_mod(bot, member=owner):
        if not data.is_admin(bot, context.guild, context.author.id):
            raise CBException("Cannot remove characters of other bot moderators.")
    character_search = utilities.clean_text(context.arguments[1])
    search_result = _user_character_search(bot, context.author, owner, character_search)
    character = search_result[1][search_result[0]]

    data.db_delete(
        bot, 'characters', where_arg='clean_name=%s AND owner_id=%s',
        input_args=(character.clean_name, owner.id))

    return Response(content="Character forcefully deleted.")
Example #7
0
async def owner_wrapper(bot, message, base, blueprint_index, options,
                        arguments, keywords, cleaned_content):
    response, tts, message_type, extra = ('', False, 0, None)
    mod_action = ''

    send_notifications = data.get(bot,
                                  'base',
                                  'notifications',
                                  server_id=message.server.id,
                                  default=True)

    if blueprint_index in (0, 1):  # Add or remove moderator
        user = data.get_member(bot, arguments[0], server=message.server)
        user_is_mod = data.is_mod(bot, message.server, user.id, strict=True)
        user_is_elevated = data.is_mod(bot, message.server, user.id)
        blocked = data.is_blocked(bot, message.server, user.id, strict=True)
        mod_action = 'Added {}' if blueprint_index == 0 else 'Removed {}'
        mod_action = mod_action.format(
            '{0} ({0.id}) as a moderator'.format(user))
        if blocked:
            raise BotException(EXCEPTION, "User is blocked.")
        elif blueprint_index == 0:  # add
            if user_is_mod or user_is_elevated:
                raise BotException(EXCEPTION, "User is already a moderator.")
            else:
                data.list_data_append(bot,
                                      'base',
                                      'moderators',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is now a moderator."
        else:  # remove
            if not user_is_mod:
                raise BotException(EXCEPTION,
                                   "User is not in the moderators list.")
            else:
                data.list_data_remove(bot,
                                      'base',
                                      'moderators',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is no longer a moderator."

    elif blueprint_index == 2:  # Send feedback
        if data.get(bot, 'base', 'feedbackdisabled', default=False):
            response = ("Feedback has been temporarily disabled, probably "
                        "due to some troll spammers.")
        else:
            text = arguments[0]
            if len(text) > 1500:
                raise BotException(
                    EXCEPTION, "Whoa! That's a lot of feedback. "
                    "1500 characters or fewer, please.")
            text = ('{0} ({0.id}) on {1.timestamp}:'
                    '\n\t{2}').format(message.author, message, text)
            await utilities.notify_owners(bot, text, user_id=message.author.id)
            response = "Message sent to bot owners."

    elif blueprint_index == 3:  # Toggle notifications
        response = ("Bot moderator activity notifications are now turned "
                    "{}").format("OFF." if send_notifications else "ON.")
        data.add(bot,
                 'base',
                 'notifications',
                 not send_notifications,
                 server_id=message.server.id)

    # Send notification if configured
    if mod_action and send_notifications:
        if message.edited_timestamp:
            timestamp = message.edited_timestamp
        else:
            timestamp = message.timestamp
        notification = 'From {0.server} on {1}, you:\n\t{2}'.format(
            message.author, timestamp, mod_action)
        logs = await utilities.get_log_text(bot,
                                            message.channel,
                                            limit=20,
                                            before=message)
        logs += '\n{}'.format(utilities.get_formatted_message(message))
        await bot.send_message(message.server.owner, notification)
        await utilities.send_text_as_file(bot, message.server.owner, logs,
                                          'context')

    return (response, tts, message_type, extra)
Example #8
0
async def mod_wrapper(bot, message, base, blueprint_index, options, arguments,
                      keywords, cleaned_content):
    response, tts, message_type, extra = ('', False, 0, None)
    mod_action = ''

    if blueprint_index == 0:  # info
        server_data = data.get(bot,
                               'base',
                               None,
                               server_id=message.server.id,
                               default={})
        disabled_commands = server_data.get('disabled', [])
        display_list = []
        for disabled_command in disabled_commands:
            display_list.append('{0} ({1})'.format(
                disabled_command[0],
                'all' if disabled_command[1] == -1 else disabled_command[1] +
                1))
        response = ('```\n'
                    'Information for server {0}\n'
                    'ID: {0.id}\n'
                    'Owner: {0.owner.id}\n'
                    'Moderators: {1}\n'
                    'Blocked users: {2}\n'
                    'Muted: {3}\n'
                    'Muted channels: {4}\n'
                    'Command invoker: {5}\n'
                    'Mention mode: {6}\n'
                    'Disabled commands: {7}```').format(
                        message.server, server_data.get('moderators', []),
                        server_data.get('blocked', []),
                        server_data.get('muted', []),
                        server_data.get('muted_channels', []),
                        server_data.get('command_invoker', None),
                        server_data.get('mention_mode', False), display_list)

    elif blueprint_index == 1:  # Toggle command
        try:  # Explicit index
            split_arguments = arguments[0].split()
            command = bot.commands[split_arguments[0]]
            guess = [command.base, int(split_arguments[1]) - 1]
            assert -1 < guess[1] < len(command.blueprints)
        except IndexError:  # No index
            guess = [command.base, -1]
        except:  # Guess the help index
            guess = list(parser.guess_index(bot, arguments[0]))
        if guess[0] is None:
            raise BotException(EXCEPTION, "Invalid base.")

        command = bot.commands[guess[0]]
        if command.plugin is bot.commands['base'].plugin:
            raise BotException(EXCEPTION,
                               "The base commands cannot be disabled.")
        pass_in = (bot, 'base', 'disabled', guess)
        pass_in_keywords = {'server_id': message.server.id}
        disabled_commands = data.get(*pass_in[:-1],
                                     **pass_in_keywords,
                                     default=[])
        if guess in disabled_commands:
            data.list_data_remove(*pass_in, **pass_in_keywords)
            response = "Enabled"
        else:
            data.list_data_append(*pass_in, **pass_in_keywords)
            response = "Disabled"
        response += " the `{0}` command {1}.".format(
            guess[0], "and all associated subcommands"
            if guess[1] == -1 else "(subcommand {})".format(guess[1] + 1))
        mod_action = response

    elif blueprint_index in (2, 3):  # Block or unblock
        user = data.get_member(bot, arguments[0], message.server)
        block = blueprint_index == 2
        mod_action = 'Blocked {}' if block else 'Unblocked {}'
        mod_action = mod_action.format('{0} ({0.id})'.format(user))
        blocked = data.is_blocked(bot, message.server, user.id, strict=True)
        mod = data.is_mod(bot, message.server, user.id)
        if mod:
            raise BotException(EXCEPTION,
                               "Cannot block or unblock a moderator.")
        elif block:
            if blocked:
                raise BotException(EXCEPTION, "User is already blocked.")
            else:
                data.list_data_append(bot,
                                      'base',
                                      'blocked',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is now blocked."
        else:
            if not blocked:
                raise BotException(EXCEPTION, "User is already unblocked.")
            else:
                data.list_data_remove(bot,
                                      'base',
                                      'blocked',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is now unblocked."

    elif blueprint_index == 4:  # Clear
        response = ('​' + '\n' * 80 +
                    "The chat was pushed up by a bot moderator.")

    elif blueprint_index in (5, 6):  # Mute or unmute
        server_id = message.server.id
        mute = blueprint_index == 5
        mod_action = 'Muted {}' if mute else 'Unmuted {}'

        if arguments[0]:
            channel = data.get_channel(bot, arguments[0], message.server)
            muted = channel.id in data.get(bot,
                                           'base',
                                           'muted_channels',
                                           server_id=server_id,
                                           default=[])
            mod_action = mod_action.format(channel.name)
            if mute:
                if muted:
                    raise BotException(EXCEPTION, "Channel is already muted.")
                else:
                    data.list_data_append(bot,
                                          'base',
                                          'muted_channels',
                                          channel.id,
                                          server_id=server_id)
                    if str(channel.type) == 'voice':  # disconnect
                        await utilities.leave_and_stop(bot, message.server)
                    response = "Channel muted."
            else:  # unmute
                if not muted:
                    raise BotException(EXCEPTION,
                                       "Channel is already unmuted.")
                else:
                    data.list_data_remove(bot,
                                          'base',
                                          'muted_channels',
                                          channel.id,
                                          server_id=server_id)
                    response = "Channel unmuted."

        else:  # server
            mod_action = mod_action.format('the server')
            muted = data.get(bot,
                             'base',
                             'muted',
                             server_id=server_id,
                             default=False)
            if not (muted ^ mute):
                response = "Server is already {}muted.".format(
                    '' if muted else 'un')
                raise BotException(EXCEPTION, response)
            else:
                data.add(bot, 'base', 'muted', mute, server_id=server_id)
                response = "Server {}muted.".format('' if mute else 'un')

    elif blueprint_index == 7:  # Invoker
        if len(arguments[0]) > 10:
            raise BotException(
                EXCEPTION,
                "The invoker can be a maximum of 10 characters long.")
        data.add(bot,
                 'base',
                 'command_invoker',
                 arguments[0] if arguments[0] else None,
                 server_id=message.server.id)
        response = "Custom command invoker {}.".format(
            'set' if arguments[0] else 'cleared')
        if arguments[0]:
            response = "Custom command invoker set."
            mod_action = "Set the server command invoker to '{}'.".format(
                arguments[0])
        else:
            response = "Custom command invoker cleared."
            mod_action = "Removed the custom command invoker."

    elif blueprint_index == 8:  # Mention
        current_mode = data.get(bot,
                                'base',
                                'mention_mode',
                                server_id=message.server.id,
                                default=False)
        data.add(bot,
                 'base',
                 'mention_mode',
                 not current_mode,
                 server_id=message.server.id)
        response = "Mention mode {}activated.".format(
            'de' if current_mode else '')
        mod_action = "{}activated mention mode.".format(
            'de' if current_mode else '').capitalize()

    # Send notification if configured
    send_notifications = data.get(bot,
                                  'base',
                                  'notifications',
                                  server_id=message.server.id,
                                  default=True)
    if mod_action and send_notifications:
        if message.edited_timestamp:
            timestamp = message.edited_timestamp
        else:
            timestamp = message.timestamp
        notification = ('Moderator {0} ({0.id}) from {0.server} on {1}:\n\t'
                        '{2}').format(message.author, timestamp, mod_action)
        logs = await utilities.get_log_text(bot,
                                            message.channel,
                                            limit=20,
                                            before=message)
        logs += '\n{}'.format(utilities.get_formatted_message(message))
        await bot.send_message(message.server.owner, notification)
        await utilities.send_text_as_file(bot, message.server.owner, logs,
                                          'context')

    return (response, tts, message_type, extra)
Example #9
0
async def base_wrapper(bot, message, base, blueprint_index, options, arguments,
                       keywords, cleaned_content):
    response, tts, message_type, extra = ('', False, 0, None)

    if blueprint_index == 0:  # version
        response = '`{}`\n{}'.format(bot.version, bot.date)

    elif blueprint_index == 1:  # source
        response = random.choice([
            "It's shit. I'm sorry.",
            "You want to see what the Matrix is like?",
            "Script kiddie level stuff in here.",
            "Beware the lack of PEP 8 guidelines inside!",
            "Snarky comments inside and out.",
            "Years down the road, this will all just be a really "
            "embarrassing but funny joke.", "Made with ~~love~~ pure hatred.",
            "At least he's using version control.",
            "Yes, I know I'm not very good. Sorry...",
            "Take it easy on me, okay?", "You're going to groan. A lot.",
            "You might be better off *not* looking inside."
        ])
        response += ("\nhttps://github.com/jkchen2/JshBot\n"
                     "https://github.com/jkchen2/JshBot-plugins")

    elif blueprint_index == 2:  # uptime
        uptime_total_seconds = int(time.time()) - bot.time
        uptime_struct = time.gmtime(uptime_total_seconds)
        days = int(uptime_total_seconds / 86400)
        hours = uptime_struct.tm_hour
        minutes = uptime_struct.tm_min
        seconds = uptime_struct.tm_sec
        response = ("The bot has been on since **{0}**\n{1} days, {2} hours, "
                    "{3} minutes, and {4} seconds").format(
                        bot.readable_time, days, hours, minutes, seconds)

    elif blueprint_index == 3:  # Announcement
        announcement = data.get(bot, 'base', 'announcement')
        if not announcement:
            response = "No announcement right now!"
        else:
            response = announcement

    elif blueprint_index == 4:  # Invite
        if bot.selfbot:
            raise BotException(EXCEPTION, "Nope.")
        response_list = []

        if 'details' in options:
            for plugin in bot.plugins.keys():
                permission_items = data.get(bot,
                                            plugin,
                                            'permissions',
                                            volatile=True,
                                            default={}).items()
                if permission_items:
                    response_list.append('***`{}`***'.format(plugin))
                    response_list.append('\t' + '\n\t'.join([
                        '**`{0[0]}`** -- {0[1]}'.format(item)
                        for item in permission_items
                    ]) + '\n')

        permissions_number = utilities.get_permission_bits(bot)
        app_id = (await bot.application_info()).id
        response_list.append(
            'https://discordapp.com/oauth2/authorize?&client_id={0}'
            '&scope=bot&permissions={1}\n**Remember: you must have the '
            '"Administrator" role on the server you are trying to add the '
            'bot to.**'.format(app_id, permissions_number))
        response = '\n'.join(response_list)

    elif blueprint_index in (5, 6):  # Join/leave voice channel
        if message.channel.is_private:
            raise BotException(
                EXCEPTION, "This command cannot be used in direct messages.")
        voice_channel = message.author.voice_channel
        if not voice_channel:
            raise BotException(EXCEPTION, "You are not in a voice channel.")
        try:
            if blueprint_index == 5:
                await utilities.join_and_ready(bot,
                                               voice_channel,
                                               reconnect=True,
                                               is_mod=data.is_mod(
                                                   bot, message.server,
                                                   message.author.id))
                response = "Joined {}.".format(voice_channel.name)
            else:
                await utilities.leave_and_stop(bot,
                                               message.server,
                                               member=message.author,
                                               safe=False)
                response = "Left {}.".format(voice_channel.name)
        except BotException as e:
            raise e  # Pass up
        except Exception as e:
            action = 'join' if blueprint_index == 5 else 'leave'
            raise BotException(
                EXCEPTION,
                "Failed to {} the voice channel.".format(action),
                e=e)

    return (response, tts, message_type, extra)
Example #10
0
File: core.py Project: sasma/JshBot
        async def on_message(self, message, replacement_message=None):
            # Ensure bot can respond properly
            try:
                initial_data = self.can_respond(message)
            except Exception as e:  # General error
                logger.error(e)
                logger.error(traceback.format_exc())
                self.last_exception = e
                return
            if not initial_data:
                return

            # Ensure command is valid
            content = initial_data[0]
            elevation = 3 - (initial_data[4:0:-1] + [True]).index(True)
            split_content = content.split(' ', 1)
            if len(split_content) == 1:  # No spaces
                split_content.append('')
            base, parameters = split_content
            base = base.lower()
            try:
                command = self.commands[base]
            except KeyError:
                logger.debug("Suitable command not found: " + base)
                return

            # Check that user is not spamming
            author_id = message.author.id
            direct = isinstance(message.channel, PrivateChannel)
            spam_value = self.spam_dictionary.get(author_id, 0)
            if elevation > 0 or direct:  # Moderators ignore custom limit
                spam_limit = self.spam_limit
            else:
                spam_limit = min(
                    self.spam_limit,
                    data.get(self,
                             'core',
                             'spam_limit',
                             guild_id=message.guild.id,
                             default=self.spam_limit))
            if spam_value >= spam_limit:
                if spam_value == spam_limit:
                    self.spam_dictionary[author_id] = spam_limit + 1
                    plugins.broadcast_event(self, 'bot_on_user_ratelimit',
                                            message.author)
                    await self.send_message(
                        message.channel,
                        "{0}, you appear to be issuing/editing "
                        "commands too quickly. Please wait {1} seconds.".
                        format(message.author.mention, self.spam_timeout))
                return

            # Parse command and reply
            try:
                context = None
                with message.channel.typing():
                    logger.debug(message.author.name + ': ' + message.content)
                    subcommand, options, arguments = parser.parse(
                        self, command, parameters, message)
                    context = self.Context(
                        message, base, subcommand, options, arguments,
                        subcommand.command.keywords, initial_data[0],
                        elevation, message.guild, message.channel,
                        message.author, direct, subcommand.index,
                        subcommand.id, self)
                    plugins.broadcast_event(self, 'bot_on_command', context)
                    logger.info([subcommand, options, arguments])
                    response = await commands.execute(self, context)
                    if response is None:
                        response = Response()
                    if self.selfbot and response.content:
                        response.content = '\u200b' + response.content
            except Exception as e:  # General error
                response = Response()
                destination = message.channel
                message_reference = await self.handle_error(
                    e,
                    message,
                    context,
                    response,
                    edit=replacement_message,
                    command_editable=True)

            else:  # Attempt to respond
                send_arguments = response.get_send_kwargs(replacement_message)
                try:
                    destination = response.destination if response.destination else message.channel
                    message_reference = None
                    if replacement_message:
                        try:
                            await replacement_message.edit(**send_arguments)
                            message_reference = replacement_message
                        except discord.NotFound:  # Message deleted
                            response = Response()
                            message_reference = None
                    elif (not response.is_empty() and
                          not (self.selfbot and
                               response.message_type is MessageTypes.REPLACE)):
                        message_reference = await destination.send(
                            **send_arguments)
                    response.message = message_reference
                    plugins.broadcast_event(self, 'bot_on_response', response,
                                            context)
                except Exception as e:
                    message_reference = await self.handle_error(
                        e,
                        message,
                        context,
                        response,
                        edit=replacement_message,
                        command_editable=True)

            # Incremement the spam dictionary entry
            if author_id in self.spam_dictionary:
                self.spam_dictionary[author_id] += 1
            else:
                self.spam_dictionary[author_id] = 1

            # MessageTypes:
            # NORMAL - Normal. The issuing command can be edited.
            # PERMANENT - Message is not added to the edit dictionary.
            # REPLACE - Deletes the issuing command after 'extra' seconds. Defaults
            #   to 0 seconds if 'extra' is not given.
            # ACTIVE - The message reference is passed back to the function defined
            #   with 'extra_function'. If 'extra_function' is not defined, it will call
            #   plugin.handle_active_message.
            # INTERACTIVE - Assembles reaction buttons given by extra['buttons'] and
            #   calls 'extra_function' whenever one is pressed.
            # WAIT - Wait for event. Calls 'extra_function' with the result, or None
            #   if the wait timed out.
            #
            # Only the NORMAL message type can be edited.

            response.message = message_reference
            if message_reference and isinstance(message_reference.channel,
                                                PrivateChannel):
                permissions = self.user.permissions_in(
                    message_reference.channel)
            elif message_reference:
                permissions = message_reference.guild.me.permissions_in(
                    message_reference.channel)
            else:
                permissions = None
            self.last_response = message_reference

            if response.message_type is MessageTypes.NORMAL:
                # Edited commands are handled in base.py
                if message_reference is None:  # Forbidden exception
                    return
                wait_time = self.edit_timeout
                if wait_time:
                    self.edit_dictionary[str(message.id)] = message_reference
                    await asyncio.sleep(wait_time)
                    if str(message.id) in self.edit_dictionary:
                        del self.edit_dictionary[str(message.id)]
                        if message_reference.embeds:
                            embed = message_reference.embeds[0]
                            if embed.footer.text and embed.footer.text.startswith(
                                    '\u200b' * 3):
                                embed.set_footer()
                                try:
                                    await message_reference.edit(embed=embed)
                                except:
                                    pass

            elif response.message_type is MessageTypes.REPLACE:
                try:
                    if self.selfbot and not replacement_message:  # Edit instead
                        await message.edit(**send_arguments)
                    else:
                        if response.extra:
                            await asyncio.sleep(response.extra)
                        try:
                            await message.delete(reason='Automatic')
                        except:  # Ignore permissions errors
                            pass
                except Exception as e:
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            elif response.message_type is MessageTypes.ACTIVE:
                if message_reference is None:  # Forbidden exception
                    return
                try:
                    await response.extra_function(self, context, response)
                except Exception as e:  # General error
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            elif response.message_type is MessageTypes.INTERACTIVE:
                try:
                    buttons = response.extra['buttons']
                    kwargs = response.extra.get('kwargs', {})
                    if 'timeout' not in kwargs:
                        kwargs['timeout'] = 300
                    if 'check' not in kwargs:
                        kwargs['check'] = (lambda r, u: r.message.id ==
                                           message_reference.id and not u.bot)
                    for button in buttons:
                        await message_reference.add_reaction(button)
                    reaction_check = await destination.get_message(
                        message_reference.id)
                    for reaction in reaction_check.reactions:
                        if not reaction.me or reaction.count > 1:
                            async for user in reaction.users():
                                if user != self.user and permissions.manage_messages:
                                    asyncio.ensure_future(
                                        message_reference.remove_reaction(
                                            reaction, user))
                    await response.extra_function(self, context, response,
                                                  None, False)
                    process_result = True
                    while process_result is not False:
                        try:
                            if not permissions.manage_messages:
                                add_task = self.wait_for(
                                    'reaction_add', **kwargs)
                                remove_task = self.wait_for(
                                    'reaction_remove', **kwargs)
                                done, pending = await asyncio.wait(
                                    [add_task, remove_task],
                                    return_when=FIRST_COMPLETED)
                                result = next(iter(done)).result()
                                for future in pending:
                                    future.cancel()
                            else:  # Can remove reactions
                                result = await self.wait_for(
                                    'reaction_add', **kwargs)
                                if result[1] != self.user:
                                    asyncio.ensure_future(
                                        message_reference.remove_reaction(
                                            *result))
                                else:
                                    continue
                            is_mod = data.is_mod(self, message.guild,
                                                 result[1].id)
                            if (response.extra.get('reactionlock', True)
                                    and not result[0].me or
                                (response.extra.get('userlock', True) and
                                 not (result[1] == message.author or is_mod))):
                                continue
                        except (asyncio.futures.TimeoutError,
                                asyncio.TimeoutError):
                            await response.extra_function(
                                self, context, response, None, True)
                            process_result = False
                        else:
                            process_result = await response.extra_function(
                                self, context, response, result, False)
                    try:
                        await response.message.clear_reactions()
                    except:
                        pass
                except Exception as e:
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            elif response.message_type is MessageTypes.WAIT:
                try:
                    kwargs = response.extra.get('kwargs', {})
                    if 'timeout' not in kwargs:
                        kwargs['timeout'] = 300
                    process_result = True
                    while process_result is not False:
                        try:
                            result = await self.wait_for(
                                response.extra['event'], **kwargs)
                        except asyncio.TimeoutError:
                            await response.extra_function(
                                self, context, response, None)
                            process_result = False
                        else:
                            process_result = await response.extra_function(
                                self, context, response, result)
                        if not response.extra.get('loop', False):
                            process_result = False

                except Exception as e:
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            else:
                logger.error("Unknown message type: {}".format(
                    response.message_type))
            '''