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
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
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
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()
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()
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.")
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
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)'
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.")
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.")
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.")
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)
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.")
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)
async def on_ready(bot): logger.info("on_ready was called from dummy.py!")
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()
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')
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)
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)'
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!")
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)
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))
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))
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)) '''
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!")