def barf(self) -> None: console.info('{0:>25} {1:>5} {2:>9} {3:>9} {4:>6}\n'.format( self.catname, self.number_of_races, self.mean_str, self.stdev_str, int(self.winrate * 100)))
async def post_login_init(self, client: discord.Client, server_id: int, load_config_fn) -> None: """Initializes object; call after client has been logged in to discord""" self._load_config_fn = load_config_fn # Find the correct server the_guild = None # type: Optional[discord.Guild] for s in client.guilds: if s.id == server_id: the_guild = s if the_guild is None: console.warning('Could not find guild with ID {guild_id}.'.format( guild_id=server_id)) exit(1) server.init(client, the_guild) if not self._initted: await self._load_config_fn(self) self._initted = True for manager in self._managers: await manager.initialize() else: await self.refresh() console.info('\n' '-Logged in---------------\n' ' User name: {0}\n' ' Server name: {1}\n' '-------------------------'.format( the_guild.me.display_name, the_guild.name))
async def _countdown_to_match_start(self, warn: bool = False) -> None: """Does things at certain times before the match Posts alerts to racers in this channel, and sends NecroEvents at alert times. Begins the match at the appropriate time. This is stored as a future in this object, and is meant to be canceled if this object closes. """ try: if not self.match.is_scheduled: return time_until_match = self.match.time_until_match # Begin match now if appropriate if time_until_match < datetime.timedelta(seconds=0): if not self.played_all_races: if warn: await self.write( 'I believe that I was just restarted; an error may have occurred. I am ' 'beginning a new race and attempting to pick up this match where we left ' 'off. If this is an error, or if there are unrecorded races, please contact ' 'an admin.') await self._begin_new_race() return # Wait until the first warning if time_until_match > Config.MATCH_FIRST_WARNING: await asyncio.sleep( (time_until_match - Config.MATCH_FIRST_WARNING).total_seconds()) await self.alert_racers() await NEDispatch().publish('match_alert', match=self.match, final=False) # Wait until the final warning time_until_match = self.match.time_until_match if time_until_match > Config.MATCH_FINAL_WARNING: await asyncio.sleep( (time_until_match - Config.MATCH_FINAL_WARNING).total_seconds()) # At this time, we've either just passed the FINAL_MATCH_WARNING or the function was just called # (happens if the call comes sometime after the FINAL_MATCH_WARNING but before the match). await self.alert_racers() await NEDispatch().publish('match_alert', match=self.match, final=True) await asyncio.sleep(self.match.time_until_match.total_seconds()) await self._begin_new_race() except asyncio.CancelledError: console.info( 'MatchRoom._countdown_to_match_start() was cancelled.') raise
async def initialize(self): self._main_channel = server.find_channel(channel_id=Config.MAIN_CHANNEL_ID) self._notifications_channel = server.find_channel(channel_name=Config.NOTIFICATIONS_CHANNEL_NAME) self._schedule_channel = server.find_channel(channel_name=Config.SCHEDULE_CHANNEL_NAME) self._client = server.client if Config.LEAGUE_NAME: try: await self.set_event(Config.LEAGUE_NAME) console.info('Event recovered: "{0}"'.format(self._event.schema_name)) except necrobot.exception.SchemaDoesNotExist: console.warning('League "{0}" does not exist.'.format(Config.LEAGUE_NAME)) else: console.warning('No league given in Config.')
async def execute(self, cmd: Command) -> None: """If the Command's command is this object's command, calls the (virtual) method _do_execute on it Parameters ---------- cmd: Command The command to maybe execute. """ if cmd.command in self.command_name_list \ and ((not self.admin_only) or self.bot_channel.is_admin(cmd.author)) \ and (not self.testing_command or Config.testing()): async with self.execution_id_lock: self.execution_id += 1 this_id = self.execution_id if isinstance(cmd.channel, discord.TextChannel): # noinspection PyUnresolvedReferences channel_name = cmd.channel.name elif isinstance(cmd.channel, discord.DMChannel): # noinspection PyUnresolvedReferences channel_name = 'DM-{}'.format( cmd.channel.recipient.display_name) else: channel_name = "<Unknown channel>" console.info( 'Call {0}: <ID={1}> <Caller={2}> <Channel={3}> <Message={4}>'. format( type(self).__name__, this_id, cmd.author.name, channel_name, cmd.content)) try: await self._do_execute(cmd) console.info('Exit {0}: <ID={1}>'.format( type(self).__name__, this_id)) except Exception as e: console.warning( 'Error exiting {name} <ID={id}>: {error_msg}'.format( name=type(self).__name__, id=this_id, error_msg=repr(e))) asyncio.ensure_future( cmd.channel.send( "Unexpected error while executing command `{mention}`." .format(mention=self.mention))) raise
async def post_login_init( self, client: discord.Client, server_id: int, load_config_fn ) -> None: """Initializes object; call after client has been logged in to discord""" self._load_config_fn = load_config_fn # Find the correct server try: int(server_id) id_is_int = True except ValueError: id_is_int = False the_server = None # type: discord.Server for s in client.servers: if id_is_int and s.id == server_id: the_server = s elif s.name == server_id: the_server = s if the_server is None: console.warning('Could not find the server.') exit(1) server.init(client, the_server) if not self._initted: await self._load_config_fn(self) self._initted = True for manager in self._managers: await manager.initialize() else: await self.refresh() console.info( '\n' '-Logged in---------------\n' ' User name: {0}\n' ' Server name: {1}\n' '-------------------------'.format(the_server.me.display_name, the_server.name) )
async def execute(self, command: Command) -> None: """If the Command's command is this object's command, calls the (virtual) method _do_execute on it Parameters ---------- command: Command The command to maybe execute. """ if command.command in self.command_name_list \ and ((not self.admin_only) or self.bot_channel.is_admin(command.author)) \ and (not self.testing_command or Config.testing()): async with self.execution_id_lock: self.execution_id += 1 this_id = self.execution_id console.info( 'Call {0}: <ID={1}> <Caller={2}> <Channel={3}> <Message={4}>'. format( type(self).__name__, this_id, command.author.name, command.channel.name, command.content)) await self._do_execute(command) console.info('Exit {0}: <ID={1}>'.format( type(self).__name__, this_id))
async def execute(self, command: Command) -> None: """If the Command's command is this object's command, calls the (virtual) method _do_execute on it Parameters ---------- command: Command The command to maybe execute. """ if command.command in self.command_name_list \ and ((not self.admin_only) or self.bot_channel.is_admin(command.author)) \ and (not self.testing_command or Config.testing()): async with self.execution_id_lock: self.execution_id += 1 this_id = self.execution_id console.info( 'Call {0}: <ID={1}> <Caller={2}> <Channel={3}> <Message={4}>'. format( type(self).__name__, this_id, command.author.name, command.channel.name, command.content)) try: await self._do_execute(command) console.info('Exit {0}: <ID={1}>'.format( type(self).__name__, this_id)) except Exception as e: console.warning( 'Error exiting {name} <ID={id}>: {error_msg}'.format( name=type(self).__name__, id=this_id, error_msg=repr(e))) asyncio.ensure_future( self.client.send_message( command.channel, "Unexpected error while executing command `{mention}`." .format(mention=self.mention))) raise
async def _recover_stored_match_rooms() -> None: """Recover MatchRoom objects on bot init Creates MatchRoom objects for `Match`es in the database which are registered (via their `channel_id`) to some discord.Channel on the server. """ console.info('Recovering stored match rooms------------') for row in await matchdb.get_channeled_matches_raw_data(): channel_id = int(row[13]) channel = server.find_channel(channel_id=channel_id) if channel is not None: match = await matchutil.make_match_from_raw_db_data(row=row) new_room = MatchRoom(match_discord_channel=channel, match=match) Necrobot().register_bot_channel(channel, new_room) await new_room.initialize() console.info(' Channel ID: {0} Match: {1}'.format(channel_id, match)) else: console.info(' Couldn\'t find channel with ID {0}.'.format(channel_id)) console.info('-----------------------------------------')
async def _do_execute(self, cmd: Command): if len(cmd.args) != 1: await self.client.send_message( cmd.channel, 'Wrong number of arguments for `{0}`.'.format(self.mention)) return wks_name = cmd.args[0] await self.client.send_message( cmd.channel, 'Creating matches from worksheet `{0}`...'.format(wks_name)) await self.client.send_typing(cmd.channel) match_info = LeagueMgr().league.match_info console.info('MakeFromSheet: Getting GSheet info...') try: matchup_sheet = await sheetlib.get_sheet( gsheet_id=LeagueMgr().league.gsheet_id, wks_name=wks_name, sheet_type=sheetlib.SheetType.MATCHUP) # type: MatchupSheet matches = await matchup_sheet.get_matches(register=False, match_info=match_info) except (googleapiclient.errors.Error, necrobot.exception.NecroException) as e: await self.client.send_message( cmd.channel, 'Error while making matchups: `{0}`'.format(e)) return console.info('MakeFromSheet: Creating Match objects...') not_found_matches = matchup_sheet.uncreated_matches() matches_with_channels = await matchutil.get_matches_with_channels() channeled_matchroom_names = dict() for match in matches_with_channels: if match.matchroom_name in channeled_matchroom_names: channeled_matchroom_names[match.matchroom_name] += 1 else: channeled_matchroom_names[match.matchroom_name] = 1 console.info('MakeFromSheet: Removing duplicate matches...') # Remove matches that have the same name as current channels (but only one per channel) unchanneled_matches = [] for match in matches: channeled_name = match.matchroom_name in channeled_matchroom_names if not channeled_name or channeled_matchroom_names[ match.matchroom_name] <= 0: unchanneled_matches.append(match) if channeled_name: channeled_matchroom_names[match.matchroom_name] -= 1 console.info('MakeFromSheet: Sorting matches...') # Sort the remaining matches unchanneled_matches = sorted(unchanneled_matches, key=lambda m: m.matchroom_name) console.debug( 'MakeFromSheet: Matches to make: {0}'.format(unchanneled_matches)) console.info('MakeFromSheet: Creating match channels...') for match in unchanneled_matches: console.info('MakeFromSheet: Creating {0}...'.format( match.matchroom_name)) new_room = await matchutil.make_match_room(match=match, register=True) await new_room.send_channel_start_text() uncreated_str = '' for match_str in not_found_matches: uncreated_str += match_str + ', ' if uncreated_str: uncreated_str = uncreated_str[:-2] if uncreated_str: report_str = 'Done creating matches. The following matches were not made: {0}'.format( uncreated_str) else: report_str = 'All matches created successfully.' await self.client.send_message(cmd.channel, report_str)
async def publish(self, event_type: str, **kwargs): ev = NecroEvent(event_type, **kwargs) console.info('Processing event of type {0}.'.format(ev.event_type)) for subber in self._subscribers: await subber.ne_process(ev)
async def _do_execute(self, cmd: Command): console.info("--CHANNEL LIST:---------------------------------------") for channel in server.guild.channels: console.info(str(channel)) console.info("--END CHANNEL LIST------------------------------------") console.info("--MEMBER LIST:---------------------------------------") for member in server.guild.members: console.info(str(member)) console.info("--END MEMBER LIST------------------------------------")
def logon( config_filename: str, logging_prefix: str, load_config_fn: types.FunctionType, on_ready_fn: types.FunctionType = None ) -> None: """Log on to Discord. Block until logout. Parameters ---------- config_filename: str The filename of the config file to use. logging_prefix: str A prefix to append to all logfile outputs. load_config_fn: [coro] (Necrobot) -> None A coroutine to be called after first login, which should set up the Necrobot with the desired BotChannels and Managers. on_ready_fn: [coro] (Necrobot) -> None A coroutine to be called after every login. Useful for unit testing. """ # Initialize config file---------------------------------- config.init(config_filename) # Asyncio debug setup------------------------------------- if config.Config.testing(): asyncio.get_event_loop().set_debug(True) warnings.simplefilter("always", ResourceWarning) # Logging-------------------------------------------------- log_timestr_format = '%Y-%m-%d-%H-%M-%S' log_file_format = '{prefix}-{timestr}.log' log_output_filename = os.path.join( 'logging', log_file_format.format(prefix=logging_prefix, timestr=datetime.datetime.utcnow().strftime(log_timestr_format)) ) # Set up logger if config.Config.full_debugging(): asyncio_level = logging.DEBUG discord_level = logging.DEBUG necrobot_level = logging.DEBUG elif config.Config.debugging(): asyncio_level = logging.INFO discord_level = logging.INFO necrobot_level = logging.DEBUG elif config.Config.testing(): asyncio_level = logging.INFO discord_level = logging.INFO necrobot_level = logging.INFO else: # if config.Config.TEST_LEVEL == config.TestLevel.RUN: asyncio_level = logging.WARNING discord_level = logging.WARNING necrobot_level = logging.INFO stream_formatter = logging.Formatter('%(levelname)s:%(name)s: %(message)s') file_formatter = logging.Formatter('[%(asctime)s] %(levelname)s:%(name)s: %(message)s') stdout_handler = logging.StreamHandler(stream=sys.stdout) stderr_handler = logging.StreamHandler() file_handler = logging.FileHandler(filename=log_output_filename, encoding='utf-8', mode='w') # stdout_handler.setLevel(logging.INFO) # stderr_handler.setLevel(logging.INFO) stdout_handler.setFormatter(stream_formatter) stderr_handler.setFormatter(stream_formatter) file_handler.setFormatter(file_formatter) logging.getLogger('discord').setLevel(discord_level) logging.getLogger('discord').addHandler(file_handler) logging.getLogger('discord').addHandler(stderr_handler) logging.getLogger('asyncio').setLevel(asyncio_level) logging.getLogger('asyncio').addHandler(file_handler) logging.getLogger('asyncio').addHandler(stderr_handler) logger = logging.getLogger('necrobot') logger.setLevel(necrobot_level) logger.addHandler(file_handler) logger.addHandler(stdout_handler) console.info('Initializing necrobot...') # Run client--------------------------------------------- retry = backoff.ExponentialBackoff() try: while True: logger.info('Beginning main loop.') # Create the discord.py Client object and the Necrobot---- client = discord.Client() the_necrobot = Necrobot() the_necrobot.clean_init() the_necrobot.ready_client_events(client=client, load_config_fn=load_config_fn, on_ready_fn=on_ready_fn) while not client.is_logged_in: try: asyncio.get_event_loop().run_until_complete(client.login(config.Config.LOGIN_TOKEN)) except (discord.HTTPException, aiohttp.ClientError): logger.exception('Exception while logging in.') asyncio.get_event_loop().run_until_complete(asyncio.sleep(retry.delay())) else: break while client.is_logged_in: if client.is_closed: # noinspection PyProtectedMember client._closed.clear() client.http.recreate() try: logger.info('Connecting.') asyncio.get_event_loop().run_until_complete(client.connect()) except (discord.HTTPException, aiohttp.ClientError, discord.GatewayNotFound, discord.ConnectionClosed, websockets.InvalidHandshake, websockets.WebSocketProtocolError) as e: if isinstance(e, discord.ConnectionClosed) and e.code == 4004: raise # Do not reconnect on authentication failure logger.exception('Exception while running.') finally: for task in asyncio.Task.all_tasks(asyncio.get_event_loop()): task.cancel() asyncio.get_event_loop().run_until_complete(asyncio.sleep(retry.delay())) if the_necrobot.quitting: break finally: asyncio.get_event_loop().close() config.Config.write()
def register_manager(self, manager: Manager) -> None: """Register a manager""" console.info('Registering a manager of type {0}.'.format( type(manager).__name__)) self._managers.append(manager)
async def _makematches_from_pairs(cmd, league, desired_match_pairs): status_message = await cmd.channel.send( 'Creating matches... (Checking usernames)') match_info = league.match_info async with cmd.channel.typing(): # Find all racers all_racers = dict() # type: Dict[str, List[NecroUser]] for racerpair in desired_match_pairs: all_racers[racerpair[0]] = [] all_racers[racerpair[1]] = [] await userlib.fill_user_dict(all_racers) console.debug( '_makematches_from_pairs: Filled user dict: {}'.format(all_racers)) not_found_racers = [] doublename_racers = [] for username, userlist in all_racers.items(): if len(userlist) == 0: not_found_racers.append(username) elif len(userlist) > 1: doublename_racers.append(username) # Create Match objects matches = [] not_found_matches = [] # type: List[Tuple[str, str]] async def make_single_match(racers): console.debug( '_makematches_from_pairs: Making match {0}-{1}'.format( racers[0], racers[1])) racer_1 = all_racers[racers[0]][0] if len( all_racers[racers[0]]) == 1 else None racer_2 = all_racers[racers[1]][0] if len( all_racers[racers[1]]) == 1 else None if racer_1 is None or racer_2 is None: console.warning( 'Couldn\'t find racers for match {0}-{1}.'.format( racers[0], racers[1])) not_found_matches.append((racers[0], racers[1])) return new_match = await matchutil.make_match(register=True, racer_1_id=racer_1.user_id, racer_2_id=racer_2.user_id, match_info=match_info, league_tag=league.tag, autogenned=True) if new_match is None: console.debug( '_makematches_from_pairs: Match {0}-{1} not created.'. format(racers[0], racers[1])) not_found_matches.append((racers[0], racers[1])) return matches.append(new_match) console.debug('_makematches_from_pairs: Created {0}-{1}'.format( new_match.racer_1.matchroom_name, new_match.racer_2.matchroom_name)) for racer_pair in desired_match_pairs: await make_single_match(racer_pair) await asyncio.sleep(0) matches = sorted(matches, key=lambda m: m.matchroom_name) await status_message.edit( content='Creating matches... (Creating race rooms)') console.debug( '_makematches_from_pairs: Matches to make: {0}'.format(matches)) # Create match channels for match in matches: console.info('MakeMatchesFromFile: Creating {0}...'.format( match.matchroom_name)) new_room = await matchchannelutil.make_match_room(match=match, register=False) await new_room.send_channel_start_text() # Report on uncreated matches if not_found_matches: filename = leagueutil.get_unmade_matches_filename( league_tag=league.tag) with open(filename, 'w') as file: for r1, r2 in not_found_matches: file.write(f'{r1},{r2}\n') uncreated_str = ', '.join(f'`{t[0]}-{t[1]}`' for t in not_found_matches) report_str = f'The following matches were not made: {uncreated_str}. These matches were written to ' \ f'`{filename}`. Call `.make-unmade-matches` to attempt to remake these easily.' else: report_str = 'All matches created successfully.' unfound_racers_str = ', '.join(f'`{n}`' for n in not_found_racers) doubled_racers_str = ', '.join(f'`{n}`' for n in doublename_racers) if not_found_racers: report_str += \ f'\n\nThe following racers could not be found: {unfound_racers_str}.' if doubled_racers_str: report_str += \ f'\n\nThe following names were associated to more than one Discord account: {doubled_racers_str}.' await status_message.edit( content=f'Creating matches... done.\n\n{report_str}')
def logon(config_filename: str, logging_prefix: str, load_config_fn: types.FunctionType, on_ready_fn: types.FunctionType = None) -> None: """Log on to Discord. Block until logout. Parameters ---------- config_filename: str The filename of the config file to use. logging_prefix: str A prefix to append to all logfile outputs. load_config_fn: [coro] (Necrobot) -> None A coroutine to be called after first login, which should set up the Necrobot with the desired BotChannels and Managers. on_ready_fn: [coro] (Necrobot) -> None A coroutine to be called after every login. Useful for unit testing. """ # Initialize config file---------------------------------- config.init(config_filename) # Asyncio debug setup------------------------------------- if config.Config.testing(): asyncio.get_event_loop().set_debug(True) warnings.simplefilter("always", ResourceWarning) # Logging-------------------------------------------------- log_timestr_format = '%Y-%m-%d-%H-%M-%S' log_file_format = '{prefix}-{timestr}.log' log_output_filename = os.path.join( 'logging', log_file_format.format( prefix=logging_prefix, timestr=datetime.datetime.utcnow().strftime(log_timestr_format))) # Set up logger if config.Config.full_debugging(): asyncio_level = logging.DEBUG discord_level = logging.DEBUG necrobot_level = logging.DEBUG elif config.Config.debugging(): asyncio_level = logging.INFO discord_level = logging.INFO necrobot_level = logging.DEBUG elif config.Config.testing(): asyncio_level = logging.INFO discord_level = logging.INFO necrobot_level = logging.INFO else: # if config.Config.TEST_LEVEL == config.TestLevel.RUN: asyncio_level = logging.WARNING discord_level = logging.WARNING necrobot_level = logging.INFO stream_formatter = logging.Formatter('%(levelname)s:%(name)s: %(message)s') file_formatter = logging.Formatter( '[%(asctime)s] %(levelname)s:%(name)s: %(message)s') stdout_handler = logging.StreamHandler(stream=sys.stdout) stderr_handler = logging.StreamHandler() file_handler = logging.FileHandler(filename=log_output_filename, encoding='utf-8', mode='w') # stdout_handler.setLevel(logging.INFO) # stderr_handler.setLevel(logging.INFO) stdout_handler.setFormatter(stream_formatter) stderr_handler.setFormatter(stream_formatter) file_handler.setFormatter(file_formatter) logging.getLogger('discord').setLevel(discord_level) logging.getLogger('discord').addHandler(file_handler) logging.getLogger('discord').addHandler(stderr_handler) logging.getLogger('asyncio').setLevel(asyncio_level) logging.getLogger('asyncio').addHandler(file_handler) logging.getLogger('asyncio').addHandler(stderr_handler) logger = logging.getLogger('necrobot') logger.setLevel(necrobot_level) logger.addHandler(file_handler) logger.addHandler(stdout_handler) console.info('Initializing necrobot...') # Seed the random number generator------------------------ seedgen.init_seed() # Run client--------------------------------------------- try: logger.info('Beginning main loop.') # Create the discord.py Client object and the Necrobot---- client = discord.Client() the_necrobot = Necrobot() the_necrobot.clean_init() the_necrobot.ready_client_events(client=client, load_config_fn=load_config_fn, on_ready_fn=on_ready_fn) client.run(config.Config.LOGIN_TOKEN) finally: # VodRecorder().end_all_async_unsafe() config.Config.write()
async def _do_execute(self, cmd: Command): if len(cmd.args) != 1: await cmd.channel.send( 'Wrong number of arguments for `{0}`.'.format(self.mention)) return wks_name = cmd.args[0] status_message = await cmd.channel.send( 'Creating matches from worksheet `{0}`... (Getting GSheet info)'. format(wks_name)) async with cmd.channel.typing(): match_info = LeagueMgr().league.match_info console.info('MakeFromSheet: Getting GSheet info...') try: matchup_sheet = await sheetlib.get_sheet( gsheet_id=LeagueMgr().league.gsheet_id, wks_name=wks_name, sheet_type=sheetlib.SheetType.MATCHUP ) # type: MatchupSheet matches = await matchup_sheet.get_matches( register=True, match_info=match_info) except (googleapiclient.errors.Error, necrobot.exception.NecroException) as e: await cmd.channel.send( 'Error while making matchups: `{0}`'.format(e)) return console.info('MakeFromSheet: Creating Match objects...') await status_message.edit( content= 'Creating matches from worksheet `{0}`... (Creating match list)' .format(wks_name)) not_found_matches = matchup_sheet.uncreated_matches() matches_with_channels = await matchchannelutil.get_matches_with_channels( ) console.info('MakeFromSheet: Removing duplicate matches...') # Remove matches from the list that already have channels unchanneled_matches = [] for match in matches: found = False for channeled_match in matches_with_channels: if match.match_id == channeled_match.match_id: found = True if not found: unchanneled_matches.append(match) console.info('MakeFromSheet: Sorting matches...') # Sort the remaining matches unchanneled_matches = sorted(unchanneled_matches, key=lambda m: m.matchroom_name) await status_message.edit( content= 'Creating matches from worksheet `{0}`... (Creating race rooms)' .format(wks_name)) console.debug('MakeFromSheet: Matches to make: {0}'.format( unchanneled_matches)) console.info('MakeFromSheet: Creating match channels...') for match in unchanneled_matches: console.info('MakeFromSheet: Creating {0}...'.format( match.matchroom_name)) new_room = await matchchannelutil.make_match_room( match=match, register=False) await new_room.send_channel_start_text() uncreated_str = '' for match_str in not_found_matches: uncreated_str += match_str + ', ' if uncreated_str: uncreated_str = uncreated_str[:-2] if uncreated_str: report_str = 'The following matches were not made: {0}'.format( uncreated_str) else: report_str = 'All matches created successfully.' await status_message.edit( content='Creating matches from worksheet `{0}`... done. {1}'. format(wks_name, report_str))
async def _do_execute(self, cmd): if len(cmd.args) != 1: await cmd.channel.send( 'Wrong number of arguments for `{0}`.'.format(self.mention) ) return filename = cmd.args[0] if not filename.endswith('.csv'): await cmd.channel.send( 'Matchup file should be a `.csv` file.' ) return file_path = os.path.join(filename) if not os.path.isfile(file_path): await cmd.channel.send( 'Cannot find file `{}`.'.format(filename) ) return match_info = LeagueMgr().league.match_info status_message = await cmd.channel.send( 'Creating matches from file `{0}`... (Reading file)'.format(filename) ) async with cmd.channel.typing(): # Store file data desired_match_pairs = [] with open(file_path) as file: for line in file: racernames = line.rstrip('\n').split(',') desired_match_pairs.append((racernames[0].lower(), racernames[1].lower(),)) # Find all racers all_racers = dict() for racerpair in desired_match_pairs: all_racers[racerpair[0]] = None all_racers[racerpair[1]] = None await userlib.fill_user_dict(all_racers) console.debug('MakeMatchesFromFile: Filled user dict: {}'.format(all_racers)) # Create Match objects matches = [] not_found_matches = [] async def make_single_match(racers): console.debug('MakeMatchesFromFile: Making match {0}-{1}'.format(racers[0], racers[1])) racer_1 = all_racers[racers[0]] racer_2 = all_racers[racers[1]] if racer_1 is None or racer_2 is None: console.warning('Couldn\'t find racers for match {0}-{1}.'.format( racers[0], racers[1] )) not_found_matches.append('`{0}`-`{1}`'.format(racers[0], racers[1])) return new_match = await matchutil.make_match( register=True, racer_1_id=racer_1.user_id, racer_2_id=racer_2.user_id, match_info=match_info, autogenned=True ) if new_match is None: console.debug('MakeMatchesFromFile: Match {0}-{1} not created.'.format(racers[0], racers[1])) not_found_matches.append('{0}-{1}'.format(racers[0], racers[1])) return matches.append(new_match) console.debug('MakeMatchesFromFile: Created {0}-{1}'.format( new_match.racer_1.rtmp_name, new_match.racer_2.rtmp_name) ) for racer_pair in desired_match_pairs: await make_single_match(racer_pair) matches = sorted(matches, key=lambda m: m.matchroom_name) await status_message.edit( content='Creating matches from file `{0}`... (Creating race rooms)'.format(filename) ) console.debug('MakeMatchesFromFile: Matches to make: {0}'.format(matches)) # Create match channels for match in matches: console.info('MakeMatchesFromFile: Creating {0}...'.format(match.matchroom_name)) new_room = await matchchannelutil.make_match_room(match=match, register=False) await new_room.send_channel_start_text() # Report on uncreated matches uncreated_str = '' for match_str in not_found_matches: uncreated_str += match_str + ', ' if uncreated_str: uncreated_str = uncreated_str[:-2] if uncreated_str: report_str = 'The following matches were not made: {0}'.format(uncreated_str) else: report_str = 'All matches created successfully.' await status_message.edit( content='Creating matches from file `{0}`... done. {1}'.format(filename, report_str) )