Exemple #1
0
async def get_response(bot, context):
    response = Response()
    if context.author.voice:
        voice_channel = context.author.voice.channel
        voice_client = await utilities.join_and_ready(bot, voice_channel)

        options = {'format': 'bestaudio/best', 'noplaylist': True}
        downloader = YoutubeDL(options)
        url = context.arguments[0]
        try:
            file_location = data.get_from_cache(bot, None, url=url)
            if not file_location:
                logger.info("Not found in cache. Downloading...")
                info = await utilities.future(downloader.extract_info,
                                              url,
                                              download=False)
                download_url = info['formats'][0]['url']
                file_location = await data.add_to_cache(bot,
                                                        download_url,
                                                        name=url)
            ffmpeg_options = '-protocol_whitelist "file,http,https,tcp,tls"'
            voice_client.play(
                discord.FFmpegPCMAudio(file_location,
                                       before_options=ffmpeg_options))
        except BotException as e:
            raise e  # Pass up
        except Exception as e:
            raise CBException("Something bad happened.", e=e)
        response.content = "Playing your stuff."
    else:
        raise CBException("You're not in a voice channel.")

    return response
Exemple #2
0
async def get_response(bot, context):
    response = Response()
    if context.author.voice:
        voice_channel = context.author.voice.channel
        voice_client = await utilities.join_and_ready(bot, voice_channel)

        options = {'format': 'bestaudio/best', 'noplaylist': True}
        downloader = YoutubeDL(options)
        url = context.arguments[0]
        try:
            file_location = data.get_from_cache(bot, None, url=url)
            if not file_location:
                logger.info("Not found in cache. Downloading...")
                info = await utilities.future(downloader.extract_info, url, download=False)
                download_url = info['formats'][0]['url']
                file_location = await data.add_to_cache(bot, download_url, name=url)
            ffmpeg_options = '-protocol_whitelist "file,http,https,tcp,tls"'
            voice_client.play(discord.FFmpegPCMAudio(file_location, before_options=ffmpeg_options))
        except BotException as e:
            raise e  # Pass up
        except Exception as e:
            raise CBException("Something bad happened.", e=e)
        response.content = "Playing your stuff."
    else:
        raise CBException("You're not in a voice channel.")

    return response
Exemple #3
0
async def add_to_cache(bot, url, name=None, file_location=None):
    """Downloads the URL and saves to the audio cache folder.

    If the cache folder has surpassed the cache size, this will continually
    remove the least used file (by date) until there is enough space. If the
    downloaded file is more than half the size of the total cache, it will not
    be stored. Returns the final location of the downloaded file.

    If name is specified, it will be stored under that name instead of the url.
    If file_location is specified, it will move that file instead of
    downloading the URL.
    """
    if file_location:
        cleaned_name = utilities.get_cleaned_filename(file_location)
    else:
        file_location, cleaned_name = await utilities.download_url(bot, url, include_name=True)
    if name:
        cleaned_name = utilities.get_cleaned_filename(name)
    try:
        download_stat = os.stat(file_location)
    except FileNotFoundError:
        raise CBException("The audio could not be saved. Please try again later.")
    cache_limit = configurations.get(bot, 'core', 'cache_size_limit') * 1000 * 1000
    store = cache_limit > 0 and download_stat.st_size < cache_limit / 2

    # Ensure that the target destination does not exist (remove it if it does)
    if store:
        cached_location = '{0}/audio_cache/{1}'.format(bot.path, cleaned_name)
    else:
        cached_location = '{}/temp/tempsound'.format(bot.path)
    try:
        os.remove(cached_location)
    except:  # Doesn't matter if file doesn't exist
        pass
    os.rename(file_location, cached_location)
    os.utime(cached_location)

    if store:
        cache_entries = []
        total_size = 0
        for entry in os.scandir('{}/audio_cache'.format(bot.path)):
            stat = entry.stat()
            cache_entries.append((stat.st_atime, stat.st_size, entry.path))
            total_size += stat.st_size
        cache_entries.sort(reverse=True)

        while total_size > cache_limit:
            _, size, path = cache_entries.pop()
            logger.info("Removing from cache: %s", path)
            os.remove(path)
            total_size -= size
    else:
        logger.info("Not storing %s. Download size: %s", file_location, download_stat.st_size)

    try:
        download_stat = os.stat(cached_location)
    except FileNotFoundError:
        raise CBException("The file was not moved properly.")
    return cached_location
Exemple #4
0
 def shutdown(self):
     logger.debug("Writing data on shutdown...")
     if self.fresh_boot is not None:  # Don't write blank data
         self.save_data(force=True)
     logger.info("Closing down!")
     try:
         self.loop.close()
     except:
         pass
     sys.exit()
Exemple #5
0
 def shutdown(self):
     logger.debug("Writing data on shutdown...")
     if self.fresh_boot is not None:  # Don't write blank data
         self.save_data(force=True)
     logger.info("Closing down!")
     try:
         self.loop.close()
     except:
         pass
     logging.shutdown()
     sys.exit()
Exemple #6
0
def restore_backup(bot, backup_file):
    """Restores a backup file given the backup filename."""
    logger.info("Restoring from a backup file...")
    try:
        core.bot_data = {'global_users': {}, 'global_plugins': {}}
        core.volatile_data = {'global_users': {}, 'global_plugins': {}}
        shutil.unpack_archive(backup_file, '{}/data'.format(bot.path))
        data.check_all(bot)
        data.load_data(bot)
    except Exception as e:
        raise CBException("Failed to extract backup.", e=e)
    logger.info("Finished data restore.")
Exemple #7
0
def restore_backup(bot, backup_file):
    """Restores a backup file given the backup filename."""
    logger.info("Restoring from a backup file...")
    try:
        core.bot_data = {'global_users': {}, 'global_plugins': {}}
        core.volatile_data = {'global_users': {}, 'global_plugins': {}}
        shutil.unpack_archive(backup_file, '{}/data'.format(bot.path))
        data.check_all(bot)
        data.load_data(bot)
    except Exception as e:
        raise CBException("Failed to extract backup.", e=e)
    logger.info("Finished data restore.")
Exemple #8
0
 async def _parse_command(
         self, message, command, parameters, initial_data, elevation, direct):
     """Parses the command and builds a context."""
     subcommand, options, arguments = await parser.parse(self, command, parameters, message)
     context = self.Context(
         message, command.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])
     return context
Exemple #9
0
        def __init__(self, path, debug):
            self.version = '0.4.0-rewrite'
            self.date = 'July 3rd, 2017'
            self.time = int(time.time())
            self.readable_time = time.strftime('%c')
            self.path = path
            self.debug = debug

            logger.info("=== {0: ^40} ===".format("Starting up JshBot " +
                                                  self.version))
            logger.info("=== {0: ^40} ===".format(self.readable_time))

            super().__init__()

            self.configurations = {}
            plugins.add_configuration(self, 'core', 'core', base)
            data.check_folders(self)

            logger.debug("Connecting to database...")
            self.db_templates = {}
            self.db_connection = None
            data.db_connect(self)

            logger.debug("Loading plugins...")
            self.data = {'global_users': {}, 'global_plugins': {}}
            self.volatile_data = {'global_users': {}, 'global_plugins': {}}
            self.data_changed = []
            self.plugins = OrderedDict()
            self.manuals = OrderedDict()
            self.commands = {}
            plugins.add_plugins(self)

            config = self.configurations['core']
            self.edit_dictionary = {}
            self.spam_dictionary = {}
            self.spam_limit = config['command_limit']
            self.spam_timeout = config['command_limit_timeout']
            self.command_invokers = config['command_invokers']
            self.locked_commands = config['locked_commands']
            self.edit_timeout = config['edit_timeout']
            self.selfbot = config['selfbot_mode']
            self.owners = config['owners']
            self.schedule_timer = None
            self.last_exception = None
            self.last_traceback = None
            self.last_response = None
            self.fresh_boot = None
            self.extra = None

            # Extras
            config['token'] = '(redacted)'
            config['database_credentials'] = '(redacted)'
Exemple #10
0
def make_backup(bot):
    """Makes a backup of the data directory."""
    logger.info("Making backup...")
    db_backup(bot)
    backup_indices = '{0}/temp/backup{{}}.zip'.format(bot.path)
    if os.path.isfile(backup_indices.format(5)):
        os.remove(backup_indices.format(5))
    for it in range(1, 5):
        backup_file_from = backup_indices.format(5-it)
        backup_file_to = backup_indices.format(6-it)
        if os.path.isfile(backup_file_from):
            os.rename(backup_file_from, backup_file_to)
    shutil.make_archive(backup_indices.format(1)[:-4], 'zip', '{}/data'.format(bot.path))
    logger.info("Finished making backup.")
Exemple #11
0
def make_backup(bot):
    """Makes a backup of the data directory."""
    logger.info("Making backup...")
    db_backup(bot)
    backup_indices = '{0}/temp/backup{{}}.zip'.format(bot.path)
    if os.path.isfile(backup_indices.format(5)):
        os.remove(backup_indices.format(5))
    for it in range(1, 5):
        backup_file_from = backup_indices.format(5 - it)
        backup_file_to = backup_indices.format(6 - it)
        if os.path.isfile(backup_file_from):
            os.rename(backup_file_from, backup_file_to)
    shutil.make_archive(
        backup_indices.format(1)[:-4], 'zip', '{}/data'.format(bot.path))
    logger.info("Finished making backup.")
Exemple #12
0
def restore_db_backup(bot, tables=[]):
    """Restores a database dump backup file.

    If tables is specified, this will restore those instead of the entire database.
    """
    logger.info("Restoring database...")
    try:
        if tables:
            specific_tables = '-t "' + '" -t "'.join(tables) + '"'
        else:
            specific_tables = ''
        command = 'pg_restore -U postgres -d postgres {} /external/temp/db_dump'.format(specific_tables)
        docker_send_command(command)
        return docker_receive_exit_code()
    except Exception as e:
        raise CBException("Failed to restore backup.", e=e)
    logger.info("Finished database restore.")
Exemple #13
0
async def notify_owners(bot, message, user_id=None):
    """Sends all owners a direct message with the given text.

    If user_id is specified, this will check that the user is not in the
    blacklist.
    """
    if bot.selfbot:
        logger.info("Notification:\n{}".format(message))
    else:
        if user_id:
            blacklist = data.get(bot, 'core', 'blacklist', default=[])
            if user_id in blacklist:
                await asyncio.sleep(0.5)
                return
        for owner in bot.owners:
            member = data.get_member(bot, owner)
            if len(message) > 1998:
                await send_text_as_file(member, message, 'notification')
            else:
                await member.send(message)
Exemple #14
0
 def save_data(self, force=False):
     if force:
         logger.info("Forcing data save...")
     else:
         logger.info("Saving data...")
     data.save_data(self, force=force)
     logger.info("Save complete.")
Exemple #15
0
 def save_data(self, force=False):
     if force:
         logger.info("Forcing data save...")
     else:
         logger.info("Saving data...")
     data.save_data(self, force=force)
     logger.info("Save complete.")
Exemple #16
0
async def notify_owners(bot, message, user_id=None):
    """Sends all owners a direct message with the given text.

    If user_id is specified, this will check that the user is not in the
    blacklist.
    """
    if bot.selfbot:
        logger.info("Owner notification:\n{}".format(message))
    else:
        if user_id:
            blacklist = data.get(bot, 'core', 'blacklist', default=[])
            if user_id in blacklist:
                await asyncio.sleep(0.5)
                return
        for owner in bot.owners:
            try:
                member = data.get_member(bot, owner)
                if len(message) > 1990:
                    await send_text_as_file(member, message, 'notification')
                else:
                    await member.send(message)
            except Exception as e:
                logger.error("Failed to notify owner %s: %s", owner, e)
Exemple #17
0
async def on_ready(bot):
    logger.info("on_ready was called from dummy.py!")
Exemple #18
0
def start(start_file=None, debug=False):
    if start_file:
        path = os.path.split(os.path.realpath(start_file))[0]
        logger.debug("Setting directory to " + path)
    else:  # Use Docker setup
        path = '/external'
        logger.info("Bot running in Docker mode.")
        logger.debug("Using Docker setup path, " + path)

    try:
        config_file_location = path + '/config/core-config.yaml'
        with open(config_file_location, 'rb') as config_file:
            config = yaml.load(config_file)
            selfbot_mode, token = config['selfbot_mode'], config['token']
    except Exception as e:
        logger.error("Could not determine token /or selfbot mode.")
        raise e

    if selfbot_mode:
        client_type = discord.Client
        logger.debug("Using standard client (selfbot enabled).")
    else:
        client_type = discord.AutoShardedClient
        logger.debug("Using autosharded client (selfbot disabled).")

    if debug:
        log_file = '{}/temp/logs.txt'.format(path)
        if os.path.isfile(log_file):
            shutil.copy2(log_file, '{}/temp/last_logs.txt'.format(path))
        logging.basicConfig(level=logging.DEBUG,
                            handlers=[
                                RotatingFileHandler(log_file,
                                                    maxBytes=1000000,
                                                    backupCount=3),
                                logging.StreamHandler()
                            ])

    def safe_exit():
        loop = asyncio.get_event_loop()
        try:  # From discord.py client.run
            loop.run_until_complete(bot.logout())
            pending = asyncio.Task.all_tasks()
            gathered = asyncio.gather(*pending)
        except Exception as e:
            logger.error("Failed to log out. %s", e)
        try:
            gathered.cancel()
            loop.run_until_complete(gathered)
            gathered.exception()
        except:
            pass
        logger.warn("Bot disconnected. Shutting down...")
        bot.shutdown()  # Calls sys.exit

    def exception_handler(loop, context):
        e = context.get('exception')
        if e and e.__traceback__:
            traceback_text = ''.join(traceback.format_tb(e.__traceback__))
        else:
            traceback_text = traceback.format_exc()
            if not traceback_text:
                traceback_text = '(No traceback available)'
        error_message = '{}\n{}'.format(e, traceback_text)
        logger.error("An uncaught exception occurred.\n" + error_message)
        with open(path + '/temp/error.txt', 'w') as error_file:
            error_file.write(error_message)
        logger.error("Error file written.")
        if bot.is_closed():
            safe_exit()

    loop = asyncio.get_event_loop()
    bot = get_new_bot(client_type, path, debug)
    start_task = bot.start(token, bot=not selfbot_mode)
    loop.set_exception_handler(exception_handler)
    try:
        loop.run_until_complete(start_task)
    except KeyboardInterrupt:
        logger.warn("Interrupted!")
        safe_exit()
Exemple #19
0
 def restart(self):
     logger.info("Attempting to restart the bot...")
     self.save_data(force=True)
     asyncio.ensure_future(self.logout())
     os.system('python3.5 ' + self.path + '/start.py')
Exemple #20
0
        async def on_ready(self):
            if self.fresh_boot is None:
                app_info = await self.application_info()
                if app_info.owner.id not in self.owners:
                    self.owners.append(app_info.owner.id)
                if self.selfbot:  # Selfbot safety checks
                    if len(self.owners) != 1:
                        raise CBException(
                            "There can be only one owner for "
                            "a selfbot.",
                            error_type=ErrorTypes.STARTUP)
                    elif self.owners[0] != self.user.id:
                        raise CBException("Token does not match the owner.",
                                          error_type=ErrorTypes.STARTUP)
                # Start scheduler
                asyncio.ensure_future(utilities._start_scheduler(self))
                # Make sure guild data is ready
                data.check_all(self)
                data.load_data(self)
                self.fresh_boot = True
                plugins.broadcast_event(self, 'bot_on_ready_boot')

            elif self.fresh_boot:
                self.fresh_boot = False

            if self.debug:
                debug_channel = self.get_channel(
                    self.configurations['core']['debug_channel'])
                if self.fresh_boot and debug_channel is not None:
                    log_file = '{}/temp/last_logs.txt'.format(self.path)
                    error_file = '{}/temp/error.txt'.format(self.path)
                    if not os.path.isfile(log_file):
                        await debug_channel.send(content="Started up fresh.")
                    else:
                        discord_file = discord.File(log_file)
                        await debug_channel.send(content="Logs:",
                                                 file=discord_file)
                    if not os.path.isfile(error_file):
                        await debug_channel.send(content="No error log.")
                    else:
                        discord_file = discord.File(error_file)
                        await debug_channel.send(content="Last error:",
                                                 file=discord_file)
                elif debug_channel is not None:
                    await debug_channel.send("Reconnected.")

            logger.info("=== {0: ^40} ===".format(self.user.name + ' online'))

            if self.fresh_boot:
                asyncio.ensure_future(self.spam_clear_loop())
                asyncio.ensure_future(self.save_loop())
                asyncio.ensure_future(self.backup_loop())

                if self.selfbot:
                    asyncio.ensure_future(self.selfbot_away_loop())
                elif len(self.guilds) == 0:
                    link = (
                        'https://discordapp.com/oauth2/authorize?&client_id={}'
                        '&scope=bot&permissions=8').format(app_info.id)
                    first_start_note = (
                        "It appears that this is the first time you are starting up the bot. "
                        "In order to have it function properly, you must add the bot to the "
                        "server with the specified debug channel. Invite link:\n{}\n\nIt is "
                        "highly recommended that you update the core using [{}botowner update] "
                        "to not only update the bot, but also add the core manual."
                    ).format(link, self.command_invokers[0])
                    logger.info(first_start_note)
Exemple #21
0
        def __init__(self, path, debug, docker_mode):
            self.version = core_version
            self.date = core_date
            self.time = int(time.time())
            self.readable_time = time.strftime('%c')
            self.path = path
            self.debug = debug
            self.docker_mode = docker_mode

            logger.info("=== {0: ^40} ===".format("Starting up JshBot " + self.version))
            logger.info("=== {0: ^40} ===".format(self.readable_time))

            super().__init__()

            self.configurations = {}
            plugins.add_configuration(self, 'core', 'core', base)
            data.check_folders(self)

            logger.debug("Connecting to database...")
            self.db_templates = {}
            self.db_connection = None
            data.db_connect(self)

            logger.debug("Loading plugins...")
            self.data = {'global_users': {}, 'global_plugins': {}}
            self.volatile_data = {'global_users': {}, 'global_plugins': {}}
            self.data_changed = []
            self.tables_changed = []
            self.dump_exclusions = []
            self.plugins = OrderedDict()
            self.manuals = OrderedDict()
            self.commands = {}
            self.event_functions = {}
            plugins.add_plugins(self)

            config = self.configurations['core']
            self.edit_dictionary = {}
            self.spam_dictionary = {}
            self.spam_limit = config['command_limit']
            self.spam_timeout = config['command_limit_timeout']
            self.use_verbose_output = config['use_verbose_output']
            self.exception_messages = config['exception_messages']
            self.command_invokers = config['command_invokers']
            self.locked_commands = config['locked_commands']
            self.single_command = config['single_command']
            self.edit_timeout = config['edit_timeout']
            self.selfbot = config['selfbot_mode']
            self.owners = config['owners']
            self.schedule_timer = None
            self.response_deque = deque(maxlen=50)
            self.error_deque = deque(maxlen=50)
            self.maintenance_message = ''
            self.maintenance_mode = 0
            self.last_exception = None
            self.last_traceback = None
            self.last_response = None
            self.last_context = None
            self.fresh_boot = None
            self.ready = False
            self.extra = None

            # Extras
            config['token'] = '(redacted)'
            config['database_credentials'] = '(redacted)'
Exemple #22
0
async def set_status_on_boot(bot):
    """Checks to see if the status was set previously."""
    previous_status = data.get(bot, __name__, 'status')
    if previous_status:
        await bot.change_presence(activity=discord.Game(name=previous_status))
        logger.info("Detected old status - setting it now!")
Exemple #23
0
        async def on_ready(self):

            if self.fresh_boot is None:
                if self.selfbot:  # Selfbot safety checks
                    self.owners = [self.user.id]
                else:
                    app_info = await self.application_info()
                    if app_info.owner.id not in self.owners:
                        self.owners.append(app_info.owner.id)

                # Make sure guild data is ready
                data.check_all(self)
                data.load_data(self)

                # Set single command notification
                if self.single_command:
                    try:
                        command = self.commands[self.single_command]
                    except KeyError:
                        raise CBException(
                            "Invalid single command base.", error_type=ErrorTypes.STARTUP)
                    command.help_embed_fields.append((
                        '[Single command mode]',
                        'The base `{}` can be omitted when invoking these commands.'.format(
                            self.single_command)))

                self.fresh_boot = True
                self.ready = True
                plugins.broadcast_event(self, 'bot_on_ready_boot')

                # Start scheduler
                asyncio.ensure_future(utilities._start_scheduler(self))

            elif self.fresh_boot:
                self.fresh_boot = False

            if self.debug:
                debug_channel = self.get_channel(
                    self.configurations['core']['debug_channel'])
                if self.fresh_boot and debug_channel is not None:
                    log_file = '{}/temp/last_logs.txt'.format(self.path)
                    error_file = '{}/temp/error.txt'.format(self.path)
                    if not os.path.isfile(log_file):
                        await debug_channel.send(content="Started up fresh.")
                    else:
                        discord_file = discord.File(log_file, filename='last_logs.txt')
                        await debug_channel.send(content="Logs:", file=discord_file)
                    if not os.path.isfile(error_file):
                        await debug_channel.send(content="No error log.")
                    else:
                        discord_file = discord.File(error_file)
                        await debug_channel.send(content="Last error:", file=discord_file)
                elif debug_channel is not None:
                    await debug_channel.send("Reconnected.")

            logger.info("=== {0: ^40} ===".format(self.user.name + ' online'))

            if self.fresh_boot:
                asyncio.ensure_future(self.spam_clear_loop())
                asyncio.ensure_future(self.save_loop())
                asyncio.ensure_future(self.backup_loop())

                if self.selfbot:
                    asyncio.ensure_future(self.selfbot_away_loop())
                elif len(self.guilds) == 0:
                    link = (
                        'https://discordapp.com/oauth2/authorize?&client_id={}'
                        '&scope=bot&permissions=8').format(app_info.id)
                    first_start_note = (
                        "It appears that this is the first time you are starting up the bot. "
                        "In order to have it function properly, you must add the bot to the "
                        "server with the specified debug channel. Invite link:\n{}\n\nIt is "
                        "highly recommended that you update the core using [{}botowner update] "
                        "to not only update the bot, but also add the core manual.").format(
                            link, self.command_invokers[0])
                    logger.info(first_start_note)
Exemple #24
0
async def on_message_edit(bot, before, after):
    if (before.author != bot.user
            and configurations.get(bot, __name__, key='show_edited_messages')):
        logger.info(
            "Somebody edited their message from '{0}' to '{1}'.".format(
                before.content, after.content))
Exemple #25
0
 def restart(self):
     logger.info("Attempting to restart the bot...")
     self.save_data(force=True)
     asyncio.ensure_future(self.logout())
     os.system('python3.5 ' + self.path + '/start.py')
Exemple #26
0
async def show_edits(bot, before, after):
    if (before.author != bot.user and
            configurations.get(bot, __name__, key='show_edited_messages')):
        logger.info("Somebody edited their message from '{0}' to '{1}'.".format(
            before.content, after.content))
Exemple #27
0
        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))
            '''
Exemple #28
0
async def demo_on_boot(bot):
    """This is called only once every time the bot is started (or reloaded)."""
    logger.info("demo_on_boot was called from dummy.py!")