Пример #1
0
    def on_guild_create(self, event):
        try:
            guild = Guild.with_id(event.id)
        except Guild.DoesNotExist:
            # If the guild is not awaiting setup, leave it now
            if not rdb.sismember(GUILDS_WAITING_SETUP_KEY, str(
                    event.id)) and event.id != ROWBOAT_GUILD_ID:
                self.log.warning(
                    'Leaving guild %s (%s), not within setup list', event.id,
                    event.name)
                event.guild.leave()
            return

        if not guild.enabled:
            if rdb.sismember(GUILDS_WAITING_SETUP_KEY, str(event.id)):
                guild.enabled = True
                guild.save()
            else:
                return

        config = guild.get_config()
        if not config:
            return

        # Ensure we're updated
        self.log.info('Syncing guild %s', event.guild.id)
        try:
            guild.sync(event.guild)
        except:
            self.log.info('Failed to sync guild {} ({})'.format(
                event.id, event.guild.name))
            return

        self.guilds[event.id] = guild
Пример #2
0
    def command_setup(self, event):
        if not event.guild:
            return event.msg.reply(
                ':warning: this command can only be used in servers')

        # Make sure we're not already setup
        if event.guild.id in self.guilds:
            return event.msg.reply(':warning: this server is already setup')

        global_admin = rdb.sismember('global_admins', event.author.id)

        # Make sure this is the owner of the server
        if not global_admin:
            if not event.guild.owner_id == event.author.id:
                return event.msg.reply(
                    ':warning: only the server owner can setup rowboat')

        # Make sure we have admin perms
        m = event.guild.members.select_one(id=self.state.me.id)
        if not m.permissions.administrator and not global_admin:
            return event.msg.reply(
                ':warning: bot must have the Administrator permission')

        guild = Guild.setup(event.guild)
        rdb.srem(GUILDS_WAITING_SETUP_KEY, str(event.guild.id))
        self.guilds[event.guild.id] = guild
        event.msg.reply(':ok_hand: successfully loaded configuration')
Пример #3
0
    def on_guild_create(self, event):
        try:
            guild = Guild.with_id(event.id)
        except Guild.DoesNotExist:
            # If the guild is not awaiting setup, leave it now
            if not rdb.sismember(GUILDS_WAITING_SETUP_KEY, str(event.id)) and event.id != ROWBOAT_GUILD_ID:
                self.log.warning(
                    'Leaving guild %s (%s), not within setup list',
                    event.id, event.name
                )
                event.guild.leave()
            return

        if not guild.enabled:
            return

        config = guild.get_config()
        if not config:
            return

        # Ensure we're updated
        self.log.info('Syncing guild %s', event.guild.id)
        guild.sync(event.guild)

        self.guilds[event.id] = guild

        if config.nickname:
            def set_nickname():
                m = event.members.select_one(id=self.state.me.id)
                if m and m.nick != config.nickname:
                    try:
                        m.set_nickname(config.nickname)
                    except APIException as e:
                        self.log.warning('Failed to set nickname for guild %s (%s)', event.guild, e.content)
            self.spawn_later(5, set_nickname)
Пример #4
0
    def on_message_create(self, event, author=None):
        author = author or event.author

        if author.id == self.state.me.id:
            return

        if event.message.author.bot or rdb.sismember('ignored_channels',
                                                     event.message.channel_id):
            return
        if not event.channel.type == 1:
            if not event.message.channel.get_permissions(self.state.me).can(
                    Permissions.SEND_MESSAGES):
                return

        if event.webhook_id:
            return

        configs = list(self.compute_relevant_configs(event, author))
        if not configs:
            return

        tags = {'guild_id': event.guild.id, 'channel_id': event.channel.id}
        with timed('rowboat.plugin.censor.duration', tags=tags):
            try:
                # TODO: perhaps imap here? how to raise exception then?
                for config in configs:
                    if config.filter_zalgo:
                        self.filter_zalgo(event, config)

                    if config.filter_invites:
                        self.filter_invites(event, config)

                    if config.filter_domains:
                        self.filter_domains(event, config)

                    if config.blocked_words or config.blocked_tokens:
                        self.filter_blocked_words(event, config)
            except Censorship as c:
                self.call(
                    'ModLogPlugin.create_debounce',
                    event,
                    ['MessageDelete'],
                    message_id=event.message.id,
                )

                try:
                    event.delete()

                    self.call('ModLogPlugin.log_action_ext',
                              Actions.CENSORED,
                              event.guild.id,
                              e=event,
                              c=c)
                except APIException:
                    self.log.exception('Failed to delete censored message: ')
Пример #5
0
    def command_eval(self, event):
        global_admin = rdb.sismember('global_admins', event.author.id)
        if not global_admin:
            return self.log.error(
                'Check CommandLevels; this line should never run.')
        ctx = {
            'bot': self.bot,
            'client': self.bot.client,
            'state': self.bot.client.state,
            'event': event,
            'msg': event.msg,
            'guild': event.msg.guild,
            'channel': event.msg.channel,
            'author': event.msg.author
        }

        # Multiline eval
        src = event.codeblock
        if src.count('\n'):
            lines = list(filter(bool, src.split('\n')))
            if lines[-1] and 'return' not in lines[-1]:
                lines[-1] = 'return ' + lines[-1]
            lines = '\n'.join('    ' + i for i in lines)
            code = 'def f():\n{}\nx = f()'.format(lines)
            local = {}

            try:
                exec(compile(code, '<eval>', 'exec'), ctx, local)
            except Exception as e:
                return event.msg.reply(
                    PY_CODE_BLOCK.format(type(e).__name__ + ': ' + str(e)))

            result = pprint.pformat(local['x'])
        else:
            try:
                result = str(eval(src, ctx))
            except Exception as e:
                return event.msg.reply(
                    PY_CODE_BLOCK.format(type(e).__name__ + ': ' + str(e)))

        if len(result) > 1980:
            event.msg.reply('', attachments=[('result.txt', result)])
        else:
            event.msg.reply(PY_CODE_BLOCK.format(result))
Пример #6
0
    def command_setup(self, event):
        if not event.guild:
            raise CommandFail('This command can only be used in servers')

        global_admin = rdb.sismember('global_admins', event.author.id)

        # Make sure this is the owner of the server
        if not global_admin:
            if not event.guild.owner_id == event.author.id:
                raise CommandFail('Only the server owner can setup speedboat')

        # Make sure we have admin perms
        m = event.guild.members.select_one(id=self.state.me.id)
        if not m.permissions.administrator and not global_admin:
            raise CommandFail('Bot must have the Administrator permission')

        guild = Guild.setup(event.guild)
        rdb.srem(GUILDS_WAITING_SETUP_KEY, str(event.guild.id))
        self.guilds[event.guild.id] = guild
        raise CommandSuccess('Successfully loaded configuration')
Пример #7
0
    def dm_listener(self, event):
        from rowboat.util.images import get_dominant_colors_user
        global_admin = rdb.sismember('global_admins', event.author.id)
        if global_admin or event.author.id == 351097525928853506:
            return
        if event.guild == None:
            MODIFIER_GRAVE_ACCENT = u'\u02CB'
            msg = event
            message_content = msg.content.replace('`', MODIFIER_GRAVE_ACCENT)
            author_id = msg.author.id
            discrim = str(msg.author.discriminator)
            avatar_name = msg.author.avatar
            content = []
            embed = MessageEmbed()

            if not avatar_name:
                avatar = default_color(str(msg.author.default_avatar))
            elif avatar_name.startswith('a_'):
                avatar = u'https://cdn.discordapp.com/avatars/{}/{}.gif'.format(
                    author_id, avatar_name)
            else:
                avatar = u'https://cdn.discordapp.com/avatars/{}/{}.png'.format(
                    author_id, avatar_name)
            embed.set_author(name='{} ({})'.format(msg.author, author_id),
                             icon_url=avatar)
            embed.set_thumbnail(url=avatar)

            content.append(u'**\u276F New DM:**')
            content.append(u'Content: ```{}```'.format(message_content))
            embed.description = '\n'.join(content)
            embed.timestamp = datetime.utcnow().isoformat()
            try:
                embed.color = get_dominant_colors_user(msg.author, avatar)
            except:
                embed.color = '00000000'
            self.bot.client.api.channels_messages_create('540020613272829996',
                                                         '',
                                                         embed=embed)
Пример #8
0
    def on_message_create(self, event):
        """
        This monstrosity of a function handles the parsing and dispatching of
        commands.
        """
        # Ignore messages sent by bots
        if event.message.author.bot:
            return

        # If this is message for a guild, grab the guild object
        if hasattr(event, 'guild') and event.guild:
            guild_id = event.guild.id
        elif hasattr(event, 'guild_id') and event.guild_id:
            guild_id = event.guild_id
        else:
            guild_id = None

        guild = self.guilds.get(event.guild.id) if guild_id else None
        config = guild and guild.get_config()

        # If the guild has configuration, use that (otherwise use defaults)
        if config and config.commands:
            commands = list(
                self.bot.get_commands_for_message(config.commands.mention, {},
                                                  config.commands.prefix,
                                                  event.message))
        elif guild_id:
            # Otherwise, default to requiring mentions
            commands = list(
                self.bot.get_commands_for_message(True, {}, '', event.message))
        else:
            if ENV != 'prod':
                if not event.message.content.startswith(ENV + '!'):
                    return
                event.message.content = event.message.content[len(ENV) + 1:]

            # DM's just use the commands (no prefix/mention)
            commands = list(
                self.bot.get_commands_for_message(False, {}, '',
                                                  event.message))

        # If we didn't find any matching commands, return
        if not len(commands):
            return

        event.user_level = self.get_level(event.guild,
                                          event.author) if event.guild else 0

        # Grab whether this user is a global admin
        # TODO: cache this
        global_admin = rdb.sismember('global_admins', event.author.id)

        # Iterate over commands and find a match
        for command, match in commands:
            if command.level == -1 and not global_admin:
                continue

            level = command.level

            if guild and not config and command.triggers[0] != 'setup':
                continue
            elif config and config.commands and command.plugin != self:
                overrides = {}
                for obj in config.commands.get_command_override(command):
                    overrides.update(obj)

                if overrides.get('disabled'):
                    continue

                level = overrides.get('level', level)

            if not global_admin and event.user_level < level:
                continue

            with timed('rowboat.command.duration',
                       tags={
                           'plugin': command.plugin.name,
                           'command': command.name
                       }):
                try:
                    command_event = CommandEvent(command, event.message, match)
                    command_event.user_level = event.user_level
                    command.plugin.execute(command_event)
                except CommandResponse as e:
                    event.reply(e.response)
                except:
                    tracked = Command.track(event, command, exception=True)
                    self.log.exception('Command error:')

                    with self.send_control_message() as embed:
                        embed.title = u'Command Error: {}'.format(command.name)
                        embed.color = 0xff6961
                        embed.add_field(name='Author',
                                        value='({}) `{}`'.format(
                                            event.author, event.author.id),
                                        inline=True)
                        embed.add_field(name='Channel',
                                        value='({}) `{}`'.format(
                                            event.channel.name,
                                            event.channel.id),
                                        inline=True)
                        embed.description = '```{}```'.format(u'\n'.join(
                            tracked.traceback.split('\n')[-8:]))

                    return event.reply(
                        '<:{}> something went wrong, perhaps try again later'.
                        format(RED_TICK_EMOJI))

            Command.track(event, command)

            # Dispatch the command used modlog event
            if config:
                modlog_config = getattr(config.plugins, 'modlog', None)
                if not modlog_config:
                    return

                self._attach_local_event_data(event, 'modlog', event.guild.id)

                plugin = self.bot.plugins.get('ModLogPlugin')
                if plugin:
                    plugin.log_action(Actions.COMMAND_USED, event)

            return
Пример #9
0
 def is_global_admin(self, userid):
     global_admin = rdb.sismember('global_admins', userid)
     _usr = User.select().where(User.user_id == userid)
     if len(_usr) == 1:
         global_admin = _usr[0].admin
     return global_admin
Пример #10
0
    def on_message_create(self, event):
        """
        This monstrosity of a function handles the parsing and dispatching of
        commands.
        """
        # Ignore messages sent by bots
        if event.message.author.bot:
            return

        if rdb.sismember('ignored_channels', event.message.channel_id):
            return

        # If this is message for a guild, grab the guild object
        if hasattr(event, 'guild') and event.guild:
            guild_id = event.guild.id
        elif hasattr(event, 'guild_id') and event.guild_id:
            guild_id = event.guild_id
        else:
            guild_id = None

        guild = self.guilds.get(event.guild.id) if guild_id else None
        config = guild and guild.get_config()

        # If the guild has configuration, use that (otherwise use defaults)
        if config and config.commands:
            commands = list(
                self.bot.get_commands_for_message(config.commands.mention, {},
                                                  config.commands.prefix,
                                                  event.message))
        elif guild_id:
            # Otherwise, default to requiring mentions
            commands = list(
                self.bot.get_commands_for_message(True, {}, '', event.message))
        else:
            if ENV != 'prod':
                if not event.message.content.startswith(ENV + '!'):
                    return
                event.message.content = event.message.content[len(ENV) + 1:]

            # DM's just use the commands (no prefix/mention)
            commands = list(
                self.bot.get_commands_for_message(False, {}, '',
                                                  event.message))

        # If we didn't find any matching commands, return
        if not len(commands):
            return

        event.user_level = self.get_level(event.guild,
                                          event.author) if event.guild else 0

        # Grab whether this user is a global admin
        # TODO: cache this
        global_admin = rdb.sismember('global_admins', event.author.id)

        # Iterate over commands and find a match
        for command, match in commands:
            if command.level == -1 and not global_admin:
                continue

            level = command.level

            if guild and not config and command.triggers[0] != 'setup':
                continue
            elif config and config.commands and command.plugin != self:
                overrides = {}
                for obj in config.commands.get_command_override(command):
                    overrides.update(obj)

                if overrides.get('disabled'):
                    continue

                level = overrides.get('level', level)

            if not global_admin and event.user_level < level:
                continue

            with timed('rowboat.command.duration',
                       tags={
                           'plugin': command.plugin.name,
                           'command': command.name
                       }):
                try:
                    command.plugin.execute(
                        CommandEvent(command, event.message, match))
                except CommandResponse as e:
                    return event.reply(e.response)
                except:
                    event.reply(
                        '<:{}> something went wrong, perhaps try again later'.
                        format(RED_TICK_EMOJI))
                    self.log.exception('Command error:')

            Message.update(command=command.plugin.name + ':' +
                           command.name).where(
                               (Message.id == event.message.id)).execute()

            # Dispatch the command used modlog event
            if config:
                event.config.set(getattr(config.plugins, 'modlog', None))
                if not event.config:
                    return

                plugin = self.bot.plugins.get('ModLogPlugin')
                if plugin:
                    plugin.log_action(Actions.COMMAND_USED, event)

            return
Пример #11
0
    def info(self, event, user=None):
        if user is None:
            user = event.author

        user_id = 0
        if isinstance(user, (int, long)):
            user_id = user
            user = self.state.users.get(user)

        if user and not user_id:
            user = self.state.users.get(user.id)

        if not user:
            if user_id:
                user = self.fetch_user(user_id)
                User.from_disco_user(user)
            else:
                raise CommandFail('unknown user')

        content = []
        content.append(u'**\u276F User Information**')
        content.append(u'ID: {}'.format(user.id))
        content.append(u'Profile: <@{}>'.format(user.id))

        if user.presence:
            emoji, status = get_status_emoji(user.presence)
            content.append('Status: {} <{}>'.format(status, emoji))

            game = user.presence.game
            if game and game.name:
                activity = ['Playing', 'Stream'][int(game.type)] if game.type < 2 else None
                if not game.type:
                    if game.name == 'Spotify':
                        activity = 'Listening to'
                    else:
                        activity = None
                if activity:
                    content.append(u'{}: {}'.format(activity,
                        u'[{}]({})'.format(game.name, game.url) if game.url else game.name
                    ))


        created_dt = to_datetime(user.id)
        content.append('Created: {} ago ({})'.format(
            humanize.naturaldelta(datetime.utcnow() - created_dt),
            created_dt.isoformat()
        ))

        for i in self.server_owners:
            if i == str(user.id):
                content.append('Server Ownership: {}'.format(self.server_owners[i]))
        
        for i in self.server_managers:
            if i == str(user.id):
                content.append('Community Manager: {}'.format(self.server_managers[i]))

        if user.id == self.state.me.id:
            content.append('Documentation: https://aetherya.stream/')
        elif rdb.sismember('global_admins', user.id):
            content.append('Airplane Staff: Global Administrator')
        elif rdb.sismember('server_managers', user.id):
            content.append('Server Manager')
        elif rdb.sismember('server_owner', user.id):
            content.append('Server Owner')

        member = event.guild.get_member(user.id) if event.guild else None
        if member:
            content.append(u'\n**\u276F Member Information**')

            if member.nick:
                content.append(u'Nickname: {}'.format(member.nick))

            content.append('Joined: {} ago ({})'.format(
                humanize.naturaldelta(datetime.utcnow() - member.joined_at),
                member.joined_at.isoformat(),
            ))

            if member.roles:
                roles = []
                for r in member.roles:
                    roles.append(member.guild.roles.get(r))
                roles = sorted(roles, key=lambda r: r.position, reverse=True)
                total = len(member.roles)
                roles = roles[:20]
                content.append(u'Roles ({}): {}{}'.format(
                    total, ' '.join(r.mention for r in roles),
                    ' (+{})'.format(total-20) if total > 20 else ''
                ))

        # Execute a bunch of queries async
        newest_msg = Message.select(Message.timestamp).where(
            (Message.author_id == user.id) &
            (Message.guild_id == event.guild.id)
        ).order_by(Message.timestamp.desc()).limit(1).async()

        # oldest_msg = Message.select(Message.timestamp).where(
        #     (Message.author_id == user.id) &
        #     (Message.guild_id == event.guild.id)
        # ).order_by(Message.timestamp.asc()).limit(1).async()

        infractions = Infraction.select(
            Infraction.guild_id,
            fn.COUNT('*')
        ).where(
            (Infraction.user_id == user.id) &
            (Infraction.type_ != 6) & # Unban
            (~(Infraction.reason ** '[NOTE]%'))
        ).group_by(Infraction.guild_id).tuples().async()

        voice = GuildVoiceSession.select(
            GuildVoiceSession.user_id,
            fn.COUNT('*'),
            fn.SUM(GuildVoiceSession.ended_at - GuildVoiceSession.started_at)
        ).where(
            (GuildVoiceSession.user_id == user.id) &
            (~(GuildVoiceSession.ended_at >> None))
        ).group_by(GuildVoiceSession.user_id).tuples().async()

        # Wait for them all to complete (we're still going to be as slow as the
        #  slowest query, so no need to be smart about this.)
        try:
            wait_many(newest_msg, infractions, voice, timeout=3)
        except gevent.Timeout:
            pass
        tags = to_tags(guild_id=event.msg.guild.id)
            
        if newest_msg.value:
            content.append(u'\n **\u276F Activity**')
            statsd.timing('sql.duration.newest_msg', newest_msg.value._query_time, tags=tags)
            newest_msg = newest_msg.value.get()
            content.append('Last Message: {} ago ({})'.format(
                humanize.naturaldelta(datetime.utcnow() - newest_msg.timestamp),
                newest_msg.timestamp.isoformat(),
            ))

        # if oldest_msg.value:
        #     statsd.timing('sql.duration.oldest_msg', oldest_msg.value._query_time, tags=tags)
        #     oldest_msg = oldest_msg.value.get()
        #     content.append('First Message: {} ago ({})'.format(
        #         humanize.naturaldelta(datetime.utcnow() - oldest_msg.timestamp),
        #         oldest_msg.timestamp.isoformat(),
        #     ))

        if infractions.value:
            statsd.timing('sql.duration.infractions', infractions.value._query_time, tags=tags)
            infractions = list(infractions.value)
            total = sum(i[1] for i in infractions)
            content.append(u'\n**\u276F Infractions**')
            content.append('Total Infractions: {}'.format(total))
            content.append('Unique Servers: {}'.format(len(infractions)))

        if voice.value:
            statsd.timing('plugin.utilities.info.sql.voice', voice.value._query_time, tags=tags)
            voice = list(voice.value)
            content.append(u'\n**\u276F Voice**')
            content.append(u'Sessions: {}'.format(voice[0][1]))
            content.append(u'Time: {}'.format(humanize.naturaldelta(
                voice[0][2]
            )))

        embed = MessageEmbed()

        avatar = user.avatar
        if avatar:
            avatar = u'https://cdn.discordapp.com/avatars/{}/{}.{}'.format(
                user.id, avatar, u'gif' if avatar.startswith('a_') else u'png'
            )
        else:
            avatar = u'https://cdn.discordapp.com/embed/avatars/{}.png'.format(
                int(user.discriminator) % 5
            )

        embed.set_author(name=u'{}#{}'.format(
            user.username,
            user.discriminator,
        ), icon_url=avatar)

        embed.set_thumbnail(url=avatar)

        embed.description = '\n'.join(content)
        try:
            embed.color = get_dominant_colors_user(user, avatar)
        except:
            pass
        event.msg.reply('', embed=embed)
Пример #12
0
    def on_message_create(self, event, is_tag=False):
        """
        This monstrosity of a function handles the parsing and dispatching of
        commands.
        """
        # Ignore messages sent by bots
        if event.message.author.bot or rdb.sismember('ignored_channels',
                                                     event.message.channel_id):
            return

        # If this is message for a guild, grab the guild object
        if hasattr(event, 'guild') and event.guild:
            guild_id = event.guild.id
        elif hasattr(event, 'guild_id') and event.guild_id:
            guild_id = event.guild_id
        else:
            guild_id = None

        guild = self.guilds.get(event.guild.id) if guild_id else None
        config = guild and guild.get_config()
        cc = config.commands if config else None

        # If the guild has configuration, use that (otherwise use defaults)
        if config and config.commands:
            commands = list(
                self.bot.get_commands_for_message(config.commands.mention, {},
                                                  config.commands.prefix,
                                                  event.message))
        elif guild_id:
            # Otherwise, default to requiring mentions
            commands = list(
                self.bot.get_commands_for_message(True, {}, '', event.message))
        else:
            # if ENV != 'prod':
            if ENV not in 'prod':
                if not event.message.content.startswith(ENV + '!'):
                    return
                event.message.content = event.message.content[len(ENV) + 1:]

            # DM's just use the commands (no prefix/mention)
            commands = list(
                self.bot.get_commands_for_message(False, {}, '',
                                                  event.message))

        # if no command, attempt to run as a tag
        if not commands:
            if is_tag:
                return

            if not event.guild or not self.bot.plugins.get('TagsPlugin'):
                return

            if not config or not config.plugins or not config.plugins.tags:
                return

            prefixes = []
            if cc.prefix:
                prefixes.append(cc.prefix)
            if cc.mention:
                prefixes.append('{} '.format(self.bot.client.state.me.mention))
            if not prefixes:
                return

            tag_re = re.compile('^({})(.+)'.format(
                re.escape('|'.join(prefixes))))
            m = tag_re.match(event.message.content)
            if not m:
                return

            sqlplugin = self.bot.plugins.get('SQLPlugin')
            if sqlplugin:
                sqlplugin.tag_messages.append(event.message.id)
            event.message.content = '{}tags show {}'.format(
                m.group(1), m.group(2))
            return self.on_message_create(event, True)

        event.user_level = self.get_level(event.guild,
                                          event.author) if event.guild else 0

        # Grab whether this user is a global admin
        # TODO: cache this
        ## this is technically already cached and runs fastest this way
        global_admin = rdb.sismember('global_admins', event.author.id)

        # Iterate over commands and find a match
        for command, match in commands:
            if command.level == -1 and not global_admin:
                continue

            level = command.level

            if guild and not config and command.triggers[0] != 'setup':
                continue
            elif config and config.commands and command.plugin != self:
                overrides = {}
                for obj in config.commands.get_command_override(command):
                    overrides.update(obj)

                if overrides.get('disabled'):
                    continue

                level = overrides.get('level', level)

            if not global_admin and event.user_level < level:
                continue

            try:
                command_event = CommandEvent(command, event.message, match)
                command_event.user_level = event.user_level
                command.plugin.execute(command_event)
            except CommandResponse as e:
                if is_tag:
                    return
                event.reply(e.response)
            except:
                tracked = Command.track(event, command, exception=True)
                self.log.exception('Command error:')

                with self.send_control_message() as embed:
                    embed.title = 'Command Error: {}'.format(command.name)
                    embed.color = 0xff6961
                    embed.add_field(name='Author',
                                    value='({}) `{}`'.format(
                                        event.author, event.author.id),
                                    inline=True)
                    embed.add_field(name='Channel',
                                    value='({}) `{}`'.format(
                                        event.channel.name, event.channel.id),
                                    inline=True)
                    embed.description = '```{}```'.format('\n'.join(
                        tracked.traceback.split('\n')[-8:]))

                return event.reply(
                    '<:{}> something went wrong, perhaps try again later'.
                    format(RED_TICK_EMOJI))

            Command.track(event, command)

            # Dispatch the command used modlog event
            if config:
                modlog_config = getattr(config.plugins, 'modlog', None)
                if not modlog_config:
                    return

                if is_tag:  # Yes, I know, this is ugly but I don't have better
                    event.content = event.content.replace('tags show ', '', 1)

                self._attach_local_event_data(event, 'modlog', event.guild.id)

                plugin = self.bot.plugins.get('ModLogPlugin')
                if plugin:
                    plugin.log_action(Actions.COMMAND_USED, event)

            return