def get_players() -> list[dict]: """Gets all the players from the sheet and their associated ids for finding ballchasing replays Returns: list[dict]: List of {username: str, ids: list[str]} dicts each representing a unique player """ sheet = Sheet(sheet_p4, refresh_cooldown=300) players = sheet.to_df('Players!A1:AH') players = players.loc[(players['Not Playing'] == "FALSE") & ( players['Departed'] == "FALSE") & (~players['Team'].isin( ['Waitlist', 'Below MMR', 'Ineligible', 'Future Star', 'Banned']) & (players['Username'] != ''))] players.set_index('Username', inplace=True) processed_players: list[dict] = [] for player in players.index: trackers = players.loc[player, 'Tracker'] ids = get_ids_from_trackers(trackers) processed_players.append({ "username": player, "ids": ids, }) return processed_players
def fix_failed(self): sheet = Sheet(gdstats_sheet) failed = sheet.to_df('Failed Players!A1:AN') fixed = pd.DataFrame(columns=failed.columns) if failed.empty: return print("No players found!") for row in failed.index: username = failed.loc[row, 'Username'] known = input(f"Do you know who {username} is? (y/n) ") if known == "y": _id = input("Please type their discord id: ") # Add rl id to their account found = self.session.all_players.find_one_and_update( {"_id": _id}, {"$push": { "rl_id": failed.loc[row, "Discord ID"] }}) if found: fixed = fixed.append(failed.loc[row]) fixed.loc[fixed['Username'] == username, 'Discord ID'] = _id fixed.loc[fixed['Username'] == username, 'Username'] = found['username'] failed.drop(row) self.unknown = failed['Username'].values sheet.clear("Failed Players!A2:AN") self.log_data(fixed.set_index("Discord ID"), f'{dates[get_latest_gameday()]}!A2:Z') self.upload_stats(fixed.set_index("Discord ID"))
def from_db_old(cls, id: ObjectId, session: Session, sheet: Sheet) -> Player: doc = session.players.find_one({"_id": id}) team = session.teams.find_one({"_id": doc['info']['team']}) if team: team_data = TeamData( team['team'], League.from_str(team['league']), 17, JoinMethod.OTHER, None, LeaveMethod.OTHER ) else: team_data = None players_df = sheet.to_df('Players!A1:Z').drop_duplicates(subset="Username").set_index("Username") links = players_df.loc[doc['username'], "Tracker"] return Player( players_df.loc[doc['username'], 'Discord ID'], # Some of the ids in the database were wrong for some reason doc['username'], None, doc['info']['id'], links.split(', '), Region.from_str(doc['info']['region']), Platform.from_str(doc['info']['platform']), MMRData(doc['info']['mmr'], doc['info']['mmr'], {datetime.now().strftime("%m/%d/%y"): doc['info']['mmr']}), team['team'] if team else doc['info']['team'], [team_data], [PlayerSeason( 17, [team_data], Stats.from_db_old(doc), Stats(), [], False, False, False, )] )
def season2(): s2_sheet = Sheet('1fjXBT93zTRSHSWnG01G7Q1EAxIeXOiftGHfNAqU09a4') games = [] # Get Major players on each team major_players = s2_sheet.to_df('Major League Player Sheet !A4:K51') for row in major_players.index: if major_players.loc[row, "Team Name"] == "": major_players.loc[row, "Team Name"] = major_players.loc[row - 1, "Team Name"] major_players['Team Name'] = major_players['Team Name'].str.strip() major_players = major_players.loc[(major_players['Gamertag'] != '') & ( major_players['ODC Confirmed'] != "Coach") & (major_players['Sub'] != "Yes")] major_players['MMR'] = major_players['MMR'].str.replace(',', '').astype(int) major_players = major_players.loc[major_players['MMR'] > 805] # Get Major schedule major_schedule = s2_sheet.to_df('Major League Schedule!A3:D102') major_schedule = major_schedule.loc[major_schedule['Score'].str.len() == 3] major_schedule['Winner'] = major_schedule['Winner'].str.strip() major_schedule['Teams Playing'] = major_schedule[ 'Teams Playing'].str.strip() for i in major_schedule.index: row = major_schedule.loc[i] teams = row['Teams Playing'].split(' vs. ') if i == 2: teams = row['Teams Playing'].split(' vs ') # Annoying misspelling game_3 = row['Score'][-1] == 1 winner = row['Winner'] loser = teams[1] if teams[0] == row['Winner'] else teams[0] series = { 'league': 'major', 'teams': teams, 'score': { winner: 2, loser: 1 if game_3 else 0 }, 'games': [], } # Games 1 and 2 are the winner's wins series['games'].append({ 'winner': winner, teams[0]: { 'players': list(major_players.loc[major_players['Team Name'] == teams[0], 'Gamertag'].values), 'goals': 1 if winner == teams[0] else 0 }, teams[1]: { 'players': list(major_players.loc[major_players['Team Name'] == teams[1], 'Gamertag'].values), 'goals': 1 if winner == teams[1] else 0 } }) # Do this again for game 2 series['games'].append({ 'winner': winner, teams[0]: { 'players': list(major_players.loc[major_players['Team Name'] == teams[0], 'Gamertag'].values), 'goals': 1 if winner == teams[0] else 0 }, teams[1]: { 'players': list(major_players.loc[major_players['Team Name'] == teams[1], 'Gamertag'].values), 'goals': 1 if winner == teams[1] else 0 } }) # Add the loser's game if it went to game 3 if game_3: series['games'].append({ 'winner': teams[1] if teams[0] == winner else teams[0], teams[0]: { 'players': list(major_players.loc[major_players['Team Name'] == teams[0], 'Gamertag'].values), 'goals': 1 if winner == teams[1] else 0 }, teams[1]: { 'players': list(major_players.loc[major_players['Team Name'] == teams[1], 'Gamertag'].values), 'goals': 1 if winner == teams[0] else 0 } }) games.append(series) # Get Minor players on each team minor_players = s2_sheet.to_df('Minor League Player Sheet!A4:K51') for row in minor_players.index: if minor_players.loc[row, "Team Name"] == "": minor_players.loc[row, "Team Name"] = minor_players.loc[row - 1, "Team Name"] minor_players['Team Name'] = minor_players['Team Name'].str.strip() minor_players = minor_players.loc[(minor_players['Gamertag'] != '') & ( minor_players['ODC Confirmed'] != "Coach") & (minor_players['Sub'] != "Yes")] minor_players['MMR'] = minor_players['MMR'].str.replace(',', '').astype(int) # Get Minor schedule minor_schedule = s2_sheet.to_df('Minor League Schedule!A3:D102') minor_schedule = minor_schedule.loc[minor_schedule['Score'].str.len() == 3] minor_schedule['Winner'] = minor_schedule['Winner'].str.strip() minor_schedule['Teams Playing'] = minor_schedule[ 'Teams Playing'].str.strip() for i in minor_schedule.index: row = minor_schedule.loc[i] teams = row['Teams Playing'].split(' vs. ') if '.' not in row['Teams Playing']: teams = row['Teams Playing'].split(' vs ') # Annoying misspelling game_3 = row['Score'][-1] == 1 winner = row['Winner'] loser = teams[1] if teams[0] == row['Winner'] else teams[0] series = { 'league': 'minor', 'teams': teams, 'score': { winner: 2, loser: 1 if game_3 else 0 }, 'games': [], } # Games 1 and 2 are the winner's wins series['games'].append({ 'winner': winner, teams[0]: { 'players': list(minor_players.loc[minor_players['Team Name'] == teams[0], 'Gamertag'].values), 'goals': 1 if winner == teams[0] else 0 }, teams[1]: { 'players': list(minor_players.loc[minor_players['Team Name'] == teams[1], 'Gamertag'].values), 'goals': 1 if winner == teams[1] else 0 } }) # Do this again for game 2 series['games'].append({ 'winner': winner, teams[0]: { 'players': list(minor_players.loc[minor_players['Team Name'] == teams[0], 'Gamertag'].values), 'goals': 1 if winner == teams[0] else 0 }, teams[1]: { 'players': list(minor_players.loc[minor_players['Team Name'] == teams[1], 'Gamertag'].values), 'goals': 1 if winner == teams[1] else 0 } }) # Add the loser's game if it went to game 3 if game_3: series['games'].append({ 'winner': teams[1] if teams[0] == winner else teams[0], teams[0]: { 'players': list(minor_players.loc[minor_players['Team Name'] == teams[0], 'Gamertag'].values), 'goals': 1 if winner == teams[1] else 0 }, teams[1]: { 'players': list(minor_players.loc[minor_players['Team Name'] == teams[1], 'Gamertag'].values), 'goals': 1 if winner == teams[0] else 0 } }) games.append(series) return games
class Newsbot(commands.Bot): def __init__(self, token: Literal = BOT_TOKEN): intents = discord.Intents.default() intents.message_content = True super().__init__(command_prefix=prefix, intents=intents, help_command=None, case_insensitive=True) self.session = Session() self.p4sheet = Sheet(sheet_p4, refresh_cooldown=60) self.fc_sheet = Sheet(forecast_sheet) self.indysheet = Sheet(sheet_indy, refresh_cooldown=60) self.gdsheet = Sheet(gdstats_sheet) self.pr_sheet = Sheet(power_rankings_sheet) self.identifier = Identifier(self.session, self.p4sheet) self.elo = EloHandler(self.session, self.identifier) self.fantasy = FantasyHandler(self.session) self.players = PlayersHandler(self.session, self.p4sheet, self.identifier) self.teams = TeamsHandler(session=self.session) self.stats = StatsHandler( session=self.session, p4sheet=self.p4sheet, indysheet=self.indysheet, powerrankings=self.pr_sheet, teams=self.teams, identifier=self.identifier, ) self.token = token self.COGS = [ Elo( self, session=self.session, identifier=self.identifier, fc_sheet=self.fc_sheet, elo=self.elo, ), # Fantasy( # self, session=self.session, fantasy=self.fantasy, p4_sheet=self.p4sheet # ), Help(self), Links(self), Reddit(self), Stats( self, session=self.session, p4sheet=self.p4sheet, indysheet=self.indysheet, gdsheet=self.gdsheet, identifier=self.identifier, players=self.players, stats=self.stats, teams=self.teams, ), Stocks( self, session=self.session, ), Misc( self, session=self.session, identifier=self.identifier, p4sheet=self.p4sheet, indysheet=self.indysheet, teams=self.teams, ), ] async def setup_hook(self): await self.load_cogs() async def load_cogs(self) -> None: for cog in self.COGS: await self.add_cog(cog) async def on_command_error( self, ctx: Context, error: discord.errors.DiscordException ): if isinstance(error, commands.NoPrivateMessage): await ctx.send("This command cannot be used in private messages.") elif isinstance(error, commands.DisabledCommand): await ctx.send("Sorry. This command is disabled and cannot be used.") elif isinstance(error, commands.CommandInvokeError): if isinstance(error.original, discord.errors.Forbidden): return await ctx.send( "This bot doesn't have adequate permissions in this channel or server. You may need to re-invite the bot to your server: https://discord.com/api/oauth2/authorize?client_id=635188576446840858&permissions=380104723520&scope=applications.commands%20bot" ) else: await self.log_error( error.original, ctx.channel, ctx.command, ctx.kwargs ) async def log_error( self, error: commands.CommandInvokeError, channel: discord.ChannelType, command: commands.Command, args: dict, ): await channel.send("There was an unexpected error using this command.") error_channel: discord.TextChannel = self.get_channel(862730357371305995) if isinstance(channel, discord.TextChannel): await error_channel.send( "**" + str(type(error)) + " in " + f"<#{channel.id}>" + "**" ) elif isinstance(channel, discord.DMChannel): await error_channel.send( "**" + str(type(error)) + " in DM with " + channel.recipient.name + "**" ) await error_channel.send(f"*Command: {command.name}*") await error_channel.send(error) await error_channel.send(args) async def on_ready(self): print("Logged in as") print(f"Username: {self.user.name}") print(f"User ID: {self.user.id}") print("---------------------------------") await self.change_presence(activity=discord.Game(f"Commands are slash commands now!")) async def on_message(self, message: discord.Message): if message.type.value == 0: if message.content.split()[0] in ( '$schedule', '$stats', '$top', '$ts', '$gdstats' ): await message.reply("This bot has converted all commands to slash commands! Type '/' to see all of the slash commands available in this server. If you don't see any slash commands, you may need to reinvite the bot to your server: https://discord.com/api/oauth2/authorize?client_id=635188576446840858&permissions=380104723520&scope=applications.commands%20bot") channels = { 598237603254239238: "Major", 598237794762227713: "AAA", 598237830824591490: "AA", 598237861837537304: "A", 715549072936796180: "Indy", 715551351236722708: "Mav", 757714221759987792: "Ren", 757719107041755286: "Pal", } if int(message.channel.id) in list(channels): # Criteria is either 'record' or 'rating', followed by the threshold for an upset criteria, threshold = ("record", 4) # Parse messages league = channels[message.channel.id] if "forfeit" in message.content: return game = message.content.split("\n")[2:4] team1 = game[0].split(": ")[0] team1_score = int(game[0][-1]) team2 = game[1].split(": ")[0] team2_score = int(game[1][-1]) # This is needed to put records in alert_message, even if using rating for the criteria records = self.pr_sheet.to_df("Team Wins!A1:AE17") records = { "Major": records.iloc[:, 0:3], "AAA": records.iloc[:, 4:7], "AA": records.iloc[:, 8:11], "A": records.iloc[:, 12:15], "Indy": records.iloc[:, 16:19], "Mav": records.iloc[:, 20:23], "Ren": records.iloc[:, 24:27], "Pal": records.iloc[:, 28:31] }[league] records = records.set_index(f"{league} Teams") team1_record = ( f"({records.loc[team1, 'Wins']}-{records.loc[team1, 'Losses']})" ) team2_record = ( f"({records.loc[team2, 'Wins']}-{records.loc[team2, 'Losses']})" ) team1_wins = int(records.loc[team1, "Wins"]) team2_wins = int(records.loc[team2, "Wins"]) if criteria == "rating": team1_rating = self.elo.get_elo(team1) team2_rating = self.elo.get_elo(team2) upset = False if criteria == "record" and team2_wins - team1_wins >= threshold: upset = True elif criteria == "rating" and team2_rating - team1_rating >= threshold: upset = True # Send to #game-scores channel gamescores_channel = self.get_channel(836784966221430805) await gamescores_channel.send( f"**{league} result**\n{team1} {team1_record}: {team1_score}\n{team2} {team2_record}: {team2_score}" ) descriptors = [ "have taken down", "have defeated", "beat", "were victorious over", "thwarted", "have upset", "have overpowered", "got the better of", "overcame", "triumphed over", ] if upset: UPSET_ALERT_MESSAGE = f"""**UPSET ALERT**\n{team1} {team1_record} {choice(descriptors)} {team2} {team2_record} with a score of {team1_score} - {team2_score}""" # Send the message out to subscribed channels await self.wait_until_ready() send_to = self.session.admin.find_one({"purpose": "channels"})[ "channels" ]["upset_alerts"] for channel in send_to: channel: discord.TextChannel = self.get_channel(int(channel)) if channel == None: continue new_message = UPSET_ALERT_MESSAGE for role in channel.guild.roles: role: discord.Role if role.name.lower() == "upset alerts": new_message += ( f"\n{role.mention}" ) #await channel.send(new_message) (TEMPORARILY DISABLED) await self.process_commands(message) async def close(self): await super().close() self.session.close() def run(self): # return await super().start(self.token, reconnect=True) super().run(self.token, reconnect=True)
class Misc(commands.Cog, name="Misc"): def __init__( self, bot: Newsbot, session: Session = None, identifier: Identifier = None, p4sheet: Sheet = None, indysheet: Sheet = None, teams: TeamsHandler = None, ): self.bot = bot self.session = session if session else Session() self.identifier = identifier if identifier else Identifier() self.p4sheet = p4sheet if p4sheet else Sheet(sheet_p4) self.indysheet = indysheet if indysheet else Sheet(sheet_indy) self.teams = teams if teams else TeamsHandler(session=self.session, p4sheet=self.p4sheet) self.streamsheet = Sheet(stream_sheet) super().__init__() @commands.command() @commands.is_owner() async def sync(self, ctx: Context) -> None: await self.bot.tree.sync() # await self.bot.tree.sync(guild=self.bot.get_guild(224552524173148171)) await ctx.send("Synced!") @app_commands.command(name="ping") async def ping(self, interaction: discord.Interaction): """Shows the bot's latency""" await interaction.response.send_message( f"Pong! {round(self.bot.latency * 1000)}ms", ephemeral=True) @app_commands.command(name="upset_alerts") @has_permissions(manage_channels=True) async def upset_alerts(self, interaction: discord.Interaction) -> str: """Subscribes the given channel to upset alerts Args: ctx (Context): discord context object Returns: str: response sent in discord """ async with interaction.channel.typing(): channels = self.bot.session.admin.find_one( {"purpose": "channels"})["channels"]["upset_alerts"] if interaction.channel_id in channels: self.bot.session.admin.find_one_and_update( {"purpose": "channels"}, { "$pull": { "channels.upset_alerts": interaction.channel_id } }, ) return await interaction.response.send_message( "This channel will no longer receive alerts.") else: self.bot.session.admin.find_one_and_update( {"purpose": "channels"}, { "$push": { "channels.upset_alerts": interaction.channel_id } }, ) return await interaction.response.send_message( "This channel will now receive alerts!") return @upset_alerts.error async def upset_alerts_error(self, ctx: Context, error: commands.CommandError): if isinstance(error, commands.CheckFailure): return await ctx.send( "You don't have manage_channel perms for this server.") @app_commands.command(name="schedule") async def schedule(self, interaction: discord.Interaction, *, team: str): """Shows the schedule for any team. Args: team (str): Team name """ async with interaction.channel.typing(): team: str = team.title() if team not in divisions.keys(): return await interaction.response.send_message( f"Couldn't find team `{team}`", ephemeral=True) league: str = self.identifier.find_league(team) sheet: Sheet = (self.p4sheet if league.lower() in ["major", "aaa", "aa", "a"] else self.indysheet) all_games: pd.DataFrame = sheet.to_df(f"{league} Schedule!O4:X188") if all_games.empty: return await interaction.response.send_message( "Schedules couldn't be found, possibly because they aren't on the sheet. Contact arco if you believe this is an error." ) # If any non-preseason results are in, get rid of the preseason games if 'N' in all_games[all_games['Score'] != ""]['Preseason'].values: all_games = all_games[all_games['Preseason'] == "N"] all_games.columns.values[3] = "Team 1" all_games.columns.values[5] = "Team 2" all_games.columns.values[8] = "Logs" all_games.drop( columns=["Preseason", "Playoff", "Game Logs Processed"], inplace=True) schedule: pd.DataFrame = all_games.loc[( all_games["Team 1"] == team) | (all_games["Team 2"] == team)] schedule.set_index("Day", drop=True, inplace=True) dfi.export(schedule, "schedule.png", table_conversion="matplotlib") path = os.path.abspath("schedule.png") file = discord.File(path) await interaction.response.send_message(file=file) file.close() return os.remove(path) @app_commands.command(name="roster") async def roster(self, interaction: discord.Interaction, *, team: str): """Shows a team's roster as well as other useful information about the team Args: team (str): Team name """ async with interaction.channel.typing(): try: data = self.teams.get_data(team) except TeamNotFoundError: return await interaction.response.send_message( f"Couldn't find team: {team}", ephemeral=True) embed = discord.Embed(title=team.title(), color=0x00008B) embed.set_thumbnail(url=self.teams.get_logo_url(data)) embed.add_field(name="**GM**", value=self.teams.get_gm(data), inline=True) embed.add_field(name="**AGM**", value=self.teams.get_agm(data), inline=True) embed.add_field(name="**Captain**", value=self.teams.get_captain(data), inline=True) embed.add_field(name="**Org**", value="\n".join(self.teams.get_org(data)["Teams"])) embed.add_field(name="**Roster**", value="\n".join(self.teams.get_roster(team))) embed.add_field(name="**League**", value=self.teams.get_league(data), inline=True) for i, field in enumerate(embed.fields): if field.value == "": embed.set_field_at(i, name=field.name, value="-") return await interaction.response.send_message(embed=embed) @app_commands.command() async def stream(self, interaction: discord.Interaction): """Shows the upcoming stream schedule""" async with interaction.channel.typing(): try: data = self.streamsheet.to_df("S17 Stream Schedule!D3:K") except: return await interaction.response.send_message( "Couldn't find the stream schedule :(") data = data.rename( columns={ "Date:": "Date", "League:": "League", "Series:": "Series", "Time:": "Time", "Streamer:": "Streamer", "Play by Play:": "PBP", "Color:": "Color", }) data = data[(data["League"].str.lower().isin(leagues.keys())) & (data['Series'].str.strip() != "-") & (data['Date'].str.strip() != "")] # Get rid of empty rows and TBD rows data["Date"] = pd.to_datetime(data["Date"], format="%m/%d/%y", errors="coerce") monday = datetime.today() - timedelta( days=datetime.today().weekday()) week = timedelta(days=7) sched = data[data["Date"] > datetime.today()].set_index("Date") filename = "stream_schedule.png" dfi.export(sched, filename, table_conversion="matplotlib") path = os.path.abspath(filename) file = discord.File(path) await interaction.response.send_message(file=file) return os.remove(path)
class TeamsHandler: def __init__(self, refresh_cooldown: int = 30, session: Session = None): self.cooldown = refresh_cooldown if not session: self.session = Session() else: self.session = session self.sheet = Sheet(sheet_p4, refresh_cooldown=self.cooldown) def get_data(self, team: str) -> pd.Series: data = self.sheet.to_df('Teams!A1:AD129') # ALL the team data, not really necessary but good to have data.set_index("Team", inplace=True) team = team.title() try: data = data.loc[team] except KeyError: raise TeamNotFoundError(team) return data def get_gm(self, data: pd.Series) -> str: return data.loc['GM'] def get_agm(self, data: pd.Series) -> str: return data.loc['AGM'] def get_captain(self, data: pd.Series) -> str: captain = data.loc['Captain'] if captain == '': return None else: return captain def get_league(self, data: pd.Series) -> str: return data.loc['League'] def get_org(self, data: pd.Series) -> dict: system = data['League System'] org = { 'System': system, 'Teams': [] } org['Teams'] = data.loc['Organization Name'].split('/') return org def get_logo_url(self, data: pd.Series) -> str: # path = '/'.join(os.getcwd().split('\\')) + f"/Image_templates/RLPC_Logos/{team.title()}.png" # return path return data.loc['Logo'] def get_roster(self, team: str) -> list: players = self.sheet.to_df("Players!A1:AG") players = players[players['Departed'] == "FALSE"] players = players[players['Not Playing'] == "FALSE"] players = players[players['Team'] == team.title()] players.drop_duplicates(subset="Username", inplace=True) roster = list(players['Username'].values) return roster
class Identifier: def __init__(self, session: Session = None, p4sheet: Sheet = None): if not p4sheet: self.p4sheet = Sheet(sheet_p4) else: self.p4sheet = p4sheet if not session: self.session = Session() else: self.session = session self.ids = {} self.leagues = {} self.tracker_ids = {} @staticmethod def is_discord_id(string: str) -> bool: if len(string) in (17, 18) and string.isnumeric(): return True else: return False def identify(self, id: str) -> str: """ Determines which player matches a given ID Parameters ---------- id : str The unique Rocket Leauge ID of any given player. Returns ------- str The name of the player as spelled on the RLPC Spreadsheet. """ if self.ids.get(id): return self.ids.get(id) player = self.session.all_players.find_one({'rl_id': id}) if not player: return None else: self.ids[id] = player['username'] return player['username'] def find_team(self, names: list, id_players: bool = False, choices: list = None) -> str: """ Determines which team most likely matches a given set of three players Parameters ---------- names : list List of player names (should be verified as the same on the sheet before using find_team()). id_players : bool Whether or not this function should identify players first (if given a list of ids rather than names) choices : list A list of possible teams that this can be. This is used to find the correct team in case of subs or call downs Returns ------- str Team name. """ if id_players: new_names = [] for id in names: name = self.identify(id) if not name: continue new_names.append(name) names = new_names found_teams = [] teams = self.p4sheet.to_df("Teams!F1:P129") for player in names: if self.is_discord_id(player): doc = self.session.all_players.find_one({"_id": player}) else: doc = self.session.all_players.find_one({'username': player}) team: str = doc['current_team'] if team in ['Free Agent', 'Not Playing', 'Departed', 'Waitlist']: continue # Not sure why this was here, keeping it just in case # try: # team = self.session.teams.find_one({'_id': team}) # except TypeError: # continue if choices: choice_league = self.find_league(choices[0]) team_league = self.find_league(team) affiliate_columns = { 'Major': 'Major Affiliate', 'AAA': 'AAA Affiliate', 'AA': 'AA Affiliate', 'A': 'A Affiliate', 'Independent': 'Major Affiliate', 'Maverick': 'AAA Affiliate', 'Renegade': 'AA Affiliate', 'Paladin': 'A Affiliate' } if teams.loc[teams['Team'] == team, affiliate_columns[choice_league]].values[0] != '': # This player is from a different league, so is likely a sub team = teams.loc[teams['Team'] == team, affiliate_columns[choice_league]].values[0] found_teams.append(team) for team in found_teams: if found_teams.count(team) > (len(found_teams)/2): return team # This will return if more than half of the players are on the same team return "Undetermined" # If every player belongs to a different team def find_league(self, team: str) -> str: """ Finds out what league a team plays in Parameters ---------- team : str Name of the team. Returns ------- str Name of the league. """ team = team.title() if self.leagues.get(team): return self.leagues.get(team) if team in ['Not Playing', 'Departed', 'Waitlist', 'Free Agent', 'Undetermined']: return None doc = self.session.teams.find_one({"_id": team}) self.leagues[team] = doc['league'] return doc['league'] def tracker_identify(self, name: str) -> str: """ Finds a player's username based on the name used in-game by seeing if it matches any rltracker links Parameters ---------- name : str The in-game name of a player. Returns ------- str The name of the player as spelled on the RLPC Spreadsheet. """ sheetdata = self.p4sheet.to_df('Players!A1:AE') player = sheetdata.loc[sheetdata['Tracker'].str.contains(name.replace(' ', '%20').replace('(', '\('), case=False), 'Username'] try: return player.values[0] except: return None
class PlayersHandler: def __init__(self, session: Session = None, p4sheet: Sheet = None, identifier: Identifier = None): if not p4sheet: self.p4sheet = Sheet(sheet_p4) else: self.p4sheet = p4sheet if not session: self.session = Session() else: self.session = session if not identifier: self.identifier = Identifier() else: self.identifier = identifier def move_teams(self, id: str, old: str, new: str, join_method: models.JoinMethod = models.JoinMethod.OTHER, leave_method: models.LeaveMethod = models.LeaveMethod.OTHER): player_doc = self.session.all_players.find_one({"_id": id}) old_doc = self.session.teams.find_one({"_id": old}) new_doc = self.session.teams.find_one({"_id": new}) if old_doc and new_doc: leave_method = models.LeaveMethod.TRADE join_method = models.JoinMethod.TRADE new_team = { "name": new, "league": self.identifier.find_league(new), "join_season": current_season, "join_method": join_method.value, "leave_season": None, "leave_method": None } new_player = { "playerid": id, "join_date": datetime.now(), "join_method": join_method.value, "leave_date": None, "leave_method": None } if not player_doc['team_history']: self.session.all_players.update_one({"_id": id}, { "$set": {"team_history": []} }) # Update player self.session.all_players.find_one_and_update({"_id": id}, { "$set": { "current_team": new, } }) if old_doc: if not player_doc['team_history']: team_history_basic = { "name": old, "league": old_doc['league'], "join_season": current_season, "join_method": None, "leave_season": None, "leave_method": None, } self.session.all_players.update_one({"_id": id}, { "$push": {"team_history": team_history_basic} }) player_doc['team_history'] = [team_history_basic] self.session.all_players.find_one_and_update({"_id": id}, { "$set": { f"team_history.{len(player_doc['team_history']) - 1}.leave_season": current_season, f"team_history.{len(player_doc['team_history']) - 1}.leave_method": leave_method.value, "seasons.$[season].teams.$[team].leave_season": current_season, "seasons.$[season].teams.$[team].leave_method": leave_method.value, }}, array_filters = [{'season.season_num': current_season}, {"team.name": old, "team.leave_season": None}]) if new_doc: self.session.all_players.find_one_and_update({"_id": id}, { "$push": { "seasons.$[season].teams": new_team, "team_history": new_team, } }, array_filters = [{'season.season_num': current_season}]) # Update old team (assuming the old team isn't a non-playing team) if old_doc: self.session.teams.find_one_and_update({"_id": old}, { "$pull": {"current_players": id}, "$push": {"previous_players": id}, "$set": { "seasons.$[season].players.$[player].leave_date": datetime.now(), "seasons.$[season].players.$[player].leave_method": leave_method.value, } }, array_filters = [{'season.season_num': current_season}, {"player.playerid": id, "player.leave_date": None}]) # Update new team (assuming the new team isn't a non_playing team) if new_doc: self.session.teams.find_one_and_update({"_id": new}, { "$push": { "current_players": id, "seasons.$[season].players": new_player } }, array_filters = [{"season.season_num": current_season}]) def check_players(self): """ Compares the rlpc sheet to the database to ensure that all players are present and have accurate info Returns ------- None. """ logger.info("Checking players...") sheetdata = self.p4sheet.to_df("Players!A1:AG") sheetdata['Unique IDs'] = sheetdata['Unique IDs'].map(lambda x: x.split(",")) sheetdata['Tracker'] = sheetdata['Tracker'].map(lambda x: x.split(', ')) sheetdata['Sheet MMR'] = sheetdata['Sheet MMR'].map(lambda x: int(x)) sheetdata['Tracker MMR'] = sheetdata['Tracker MMR'].map(lambda x: int(x)) sheetdata = sheetdata.loc[sheetdata['Username'] != ""] sheetdata = sheetdata.loc[sheetdata['Team'] != "N/A"] # This should never happen unless sheets team puts it here, /shrug sheetdata = sheetdata.loc[sheetdata['Departed'] != "TRUE"] sheetdata = sheetdata.loc[sheetdata['Not Playing'] != "TRUE"] sheetdata = sheetdata.set_index('Discord ID') all_players = self.session.all_players teams = self.session.teams # NEW CHECKS for discord_id in sheetdata.index: player: str = sheetdata.loc[discord_id, 'Username'] # First check if the player is already in the database doc = self.session.all_players.find_one({"_id": discord_id}) # If the player is not in the database, add them as a player if doc == None: logger.debug(f"Adding {player} to database") data = models.Player( _id = discord_id, username = player, league = sheetdata.loc[discord_id, 'League'], date_joined = datetime.now(), rl_id = sheetdata.loc[discord_id, 'Unique IDs'], tracker_links = sheetdata.loc[discord_id, 'Tracker'], region = models.Region.from_str(sheetdata.loc[discord_id, 'Region']), platform = models.Platform.from_str(sheetdata.loc[discord_id, 'Platform']), mmr = models.MMRData(sheetdata.loc[discord_id, 'Sheet MMR'], sheetdata.loc[discord_id, 'Tracker MMR'], {datetime.now().strftime("%m/%d/%y"): sheetdata.loc[discord_id, 'Tracker MMR']}), current_team = sheetdata.loc[discord_id, 'Team'], team_history = None, seasons = None # Will be added later in this function if needed ).insert_new(self.session) # Otherwise, check fields to see if any of them changed else: data = models.Player.from_db(doc) if not data.league: all_players.find_one_and_update({"_id": discord_id}, {"$set": {"league": sheetdata.loc[discord_id, 'League']}}) # Season doesn't exist if not data.seasons: season = models.PlayerSeason( season_num = current_season, teams = [], season_stats = models.Stats(), playoff_stats = models.Stats(), games = [], made_playoffs = False, finalists = False, champions = False ) data.seasons = [season] season_dict = season.to_dict() all_players.find_one_and_update({"_id": discord_id}, {"$set": {"seasons": [season_dict]}}) logger.debug(f"{player} has joined this season.") elif filter(lambda x: x.season_num == current_season, data.seasons) == 0: season = models.PlayerSeason( season_num = current_season, teams = [], season_stats = models.Stats(), playoff_stats = models.Stats(), games = [], made_playoffs = False, finalists = False, champions = False ) data.seasons = [*data.seasons, season] season_dict = season.to_dict() all_players.find_one_and_update({"_id": discord_id}, {"$push": {"seasons": season_dict}}) logger.debug(f"{player} has joined this season.") # Username if data.username != player: data.username = player all_players.find_one_and_update({"_id": discord_id}, {"$set": {"username": player}}) logger.debug(f"{data.username} has changed their name to {player}") # League league = sheetdata.loc[discord_id, 'League'] if data.league != league and league.lower() not in ("below mmr", "future star"): data.league = models.League.from_str(league) all_players.find_one_and_update({"_id": discord_id}, {"$set": {"league": league}}) logger.debug(f"{player} has changed league to {data.league}.") # rl ids for playerid in sheetdata.loc[discord_id, 'Unique IDs']: if playerid == '': continue # No ids are available, so just ignore elif playerid not in data.rl_id: data.rl_id.append(playerid) all_players.find_one_and_update({"_id": discord_id}, {"$push": {"rl_id": playerid}}) logger.debug(f"Added id '{playerid}' for {player}") # tracker links for tracker in sheetdata.loc[discord_id, 'Tracker']: if tracker == '': continue # No trackers are available, so just ignore elif tracker not in data.tracker_links: data.tracker_links.append(tracker) all_players.find_one_and_update({"_id": discord_id}, {"$push": {"tracker_links": tracker}}) logger.debug(f"Added tracker {tracker} for {player}") # Region if data.region != models.Region.from_str(sheetdata.loc[discord_id, 'Region']): data.region = models.Region.from_str(sheetdata.loc[discord_id, 'Region']) all_players.find_one_and_update({"_id": discord_id}, {"$set": {"region": sheetdata.loc[discord_id, 'Region']}}) logger.debug(f"Moved {player} to {data.region}") # Platform if data.platform != models.Platform.from_str(sheetdata.loc[discord_id, 'Platform']): data.platform = models.Platform.from_str(sheetdata.loc[discord_id, 'Platform']) all_players.find_one_and_update({"_id": discord_id}, {"$set": {"platform": sheetdata.loc[discord_id, 'Platform']}}) logger.debug(f"Switched {player} to {data.platform}") # MMR if data.mmr.current_official_mmr != sheetdata.loc[discord_id, 'Sheet MMR']: data.mmr.current_official_mmr = sheetdata.loc[discord_id, 'Sheet MMR'] all_players.find_one_and_update({"_id": discord_id}, {"$set": {"mmr.current_official_mmr": sheetdata.loc[discord_id, 'Sheet MMR'].item()}}) logger.debug(f"Updated {player}'s Sheet MMR to {data.mmr.current_official_mmr}") # Team if data.current_team != sheetdata.loc[discord_id, 'Team']: old = data.current_team new = sheetdata.loc[discord_id, 'Team'] data.current_team = new self.move_teams(discord_id, old, new) self.remove_not_playing() logger.info("Done checking players.") def remove_not_playing(self): """Changes team of anyone listed on the sheet as "Not Playing" or "Departed" Returns: None """ sheetdata = self.p4sheet.to_df("Players!A1:AG") sheetdata = sheetdata.drop_duplicates(subset='Username') sheetdata = sheetdata.loc[sheetdata['Username'] != ""] sheetdata.set_index("Username", inplace=True) not_playing = sheetdata.loc[sheetdata['Not Playing'] == "TRUE"] departed = sheetdata.loc[sheetdata['Departed'] == "TRUE"] logger.info("NOT PLAYING") for player in not_playing.index: discord_id: str = not_playing.loc[player, 'Discord ID'] doc = self.session.all_players.find_one({'_id': discord_id}) if doc == None: continue if doc['current_team'] != "Not Playing": self.move_teams(discord_id, doc['current_team'], "Not Playing", models.JoinMethod.OTHER, models.LeaveMethod.NONPLAYING) logger.info("DEPARTED") for player in departed.index: discord_id: str = departed.loc[player, 'Discord ID'] doc = self.session.all_players.find_one({'_id': discord_id}) if doc == None: continue if doc['current_team'] != "Departed": self.move_teams(discord_id, doc['current_team'], "Departed", models.JoinMethod.OTHER, models.LeaveMethod.NONPLAYING)
class StatsHandler: def __init__(self, session: Session = None, p4sheet: Sheet = None, indysheet: Sheet = None, powerrankings: Sheet = None, gdsheet: Sheet = None, teams: rlpc.players.TeamsHandler = None, identifier: rlpc.players.Identifier = None): if not session: self.session = Session() else: self.session = session if not p4sheet: self.p4sheet = Sheet(sheet_p4) else: self.p4sheet = p4sheet if not indysheet: self.indysheet = Sheet(sheet_indy) else: self.indysheet = indysheet if not powerrankings: self.powerrankings = Sheet(power_rankings_sheet) else: self.powerrankings = powerrankings if not gdsheet: self.gdsheet = Sheet(gdstats_sheet) else: self.gdsheet = gdsheet if not teams: self.teams = rlpc.players.TeamsHandler(session=self.session) else: self.teams = teams if not identifier: self.identifier = rlpc.players.Identifier(self.session, self.p4sheet) else: self.identifier = identifier def capitalize_username(self, username: str) -> List[str]: try: players = self.p4sheet.to_df('Players!A1:I') except SheetToDfError: raise StatSheetError("Players!A1:I") lower_players = players['Username'].str.lower() if username.lower() in lower_players.values: pindex = lower_players[lower_players == username.lower()].index[0] player = players.loc[pindex][0] players = players.set_index("Username") try: league = players.loc[player, "League"] except UnboundLocalError: raise FindPlayersError(None, None) if type(league) == pd.Series: league = league[0] return player, league def get_player_stats_db(self, player: str, category: str = "all", pergame: bool = False) -> pd.DataFrame: player, league = self.capitalize_username(player) db_stats = self.session.all_players.find_one({'username': player}) if db_stats == None: raise PlayerNotFoundError(player, 0) stats = pd.DataFrame() stats.loc[0, 'Player'] = player if category not in statsCategories.keys() and category != "all": raise InvalidStatError(category) games = 1 if pergame: games = max( db_stats['seasons'][-1]['season_stats']['games_played'], 1) if category == "all": for stat in statsCategories['general']: stats.loc[0, stat] = round( db_stats['seasons'][-1]['season_stats'][snakecase_stat( stat)] / games, 1) else: keys = statsCategories[category] for key in keys: stats.loc[0, key] = round( db_stats['seasons'][-1]['season_stats'][snakecase_stat( key)] / games, 1) return stats def get_player_stats_sheet(self, player: str, stat: str = "all") -> pd.DataFrame: """ Gets the stats from the RLPC Spreadsheet for a given player parameters --------- player: str The player's username, as spelled on the sheet (case insentive) stat: (optional) str What stat(s) to return. "all" returns all stats, but a single stat can be specified. returns ------ stats: pd.DataFrame Series containing player's stats """ # Make sure the program understands the specified stat if it's mis-capitalized or whatever # TODO: Replace with structural pattern matching in python 3.10 if stat.lower() in [ "sp", "series", "series_played", "series-played", "splayed", "seris", "sieries", "seiries" ]: stat = "Series Played" elif stat.lower() in [ "gp", "games", "games_played", "games-played", "gplayed" ]: stat = "Games Played" elif stat.lower() in ["goals", "goal", "scores"]: stat = "Goals" elif stat.lower() in ['assists', 'assist', 'passes']: stat = "Assists" elif stat.lower() in ['saves', 'save']: stat = "Saves" elif stat.lower() in ['shots', 'shot']: stat = "Shots" elif stat.lower() in [ 'points', 'point', 'goals+assists', 'goals + assists', 'goals and assists' ]: stat = "Points (Goals+Assists)" elif stat.lower() in [ 'gpg', 'goals per game', 'goals pg', 'goals per' ]: stat = "Goals per game" elif stat.lower() in [ 'apg', 'assists per game', 'assists pg', 'assists per' ]: stat = "Assists per game" elif stat.lower() in [ 'spg', 'sapg', 'saves per game', 'saves pg', 'saves per' ]: stat = "Saves per game" elif stat.lower() in [ 'shot rate', 'shooting percent', 'shooting percentage', 'shot accuracy', 'shooting accuracy', 'shooting %', 'shot %' ]: stat = "Shooting %" elif stat.lower() in [ 'win rate', 'winning rate', 'winning percent', 'winning percentage' ]: stat = "Winning %" elif stat.lower() in ['wins', 'win']: stat = "Wins" elif stat.lower() in [ 'ppg', 'points per game', 'points pg', 'points per' ]: stat = "Points per Game" elif stat.lower() in [ 'shpg', 'shots per game', ' shots pg', 'shots per' ]: stat = "Shots Per Game" try: players = self.p4sheet.to_df('Players!A1:I') except SheetToDfError: raise StatSheetError("Players!A1:I") player, league = self.capitalize_username(player) try: stats = self.p4sheet.to_df(f"{league} League Stat Database!C3:R") except: raise StatSheetError(f"{league} League Stat Database!C3:R") if stat not in list(stats) and stat.lower() != "all": raise InvalidStatError(stat) stats = stats.loc[stats['Player'] == player] if stats.empty: raise StatsError(player, stat) if stat != "all": stats = stats[['Player', stat]] return stats def power_rankings(self, league: str) -> pd.DataFrame: """ Gets the most recent power rankings for a given league from the power rankings sheet parameters --------- league: str What league to get power rankings for returns --------- rankings: pd.DataFrame Dataframe containing each team's rank and how many points they had """ try: league = leagues[league.lower()] except: return "Could not understand league" start_rows = { 'Major': 2, 'AAA': 21, 'AA': 40, 'A': 59, 'Independent': 78, 'Maverick': 97, 'Renegade': 116, 'Paladin': 135 } data_range = f'Rankings History!A{start_rows[league]}:M{start_rows[league]+16}' try: data = self.powerrankings.to_df(data_range).set_index('') except GetSheetError or SheetToDfError: raise PRSheetError(league) if data.empty: raise NoPRError(league) column = 1 for i in range(12): if data.iloc[:, i].values[0] == '': column = i - 1 break else: continue try: data.iloc[:, column] = data.iloc[:, column].apply(lambda x: int(x)) except ValueError: raise NoPRError(league) rankings = data.iloc[:, column] rankings = rankings.sort_values(ascending=False) return rankings @staticmethod def season_index(doc, season=current_season): if isinstance(doc['seasons'], dict): doc['seasons'] = [ doc['seasons'] ] # Fix strange behavior from pymongo that turns arrays with 1 value into objects # Get the index needed for the current season season_index = 0 for i, season in enumerate(doc['seasons']): if season['season_num'] == season: season_index = i return season_index def statlb(self, useSheet: bool = False, league: str = "all", stat: str = "Goals", limit: int = 10, pergame: bool = False, asc: bool = False) -> pd.Series: """Gets a series containing a leaderboard for a given stat Args: useSheet (bool, optional): Whether to use stats from the sheet instead of database (League can't be "all" if True). Defaults to False. league (str, optional): Which league to get stats from. Defaults to "all". stat (str, optional): Which stat to look at. Defaults to "Goals". limit (int, optional): How many players to include on the leaderboard. Defaults to 10. pergame (bool, optional): Whether to divide stats by # games played. Defaults to False. Returns: pd.Series: Sorted series containing players and their stats """ if useSheet == True and league == "all": raise StatsError(player=None, stat=stat) if league != "all": try: league = leagues[league.lower()] except: return f"Could not understand league {league}." compound_stats = { 'Winning %': ['Games Won', 'Games Played'], 'Shooting %': ['Goals', 'Shots'], 'Shooting % Against': ['Goals Against', 'Shots Against'], 'Points': ['Goals', 'Assists'], 'MVP Rate': ['MVPs', 'Games Won'], '% Time Slow': ['Time Slow', 'Time Boost', 'Time Supersonic'], '% Time Boost': ['Time Slow', 'Time Boost', 'Time Supersonic'], '% Time Supersonic': ['Time Slow', 'Time Boost', 'Time Supersonic'], '% Time Ground': ['Time Ground', 'Time Low Air', 'Time High Air'], '% Time Low Air': ['Time Ground', 'Time Low Air', 'Time High Air'], '% Time High Air': ['Time Ground', 'Time Low Air', 'Time High Air'], '% Most Back': ['Time Most Back', 'Time Defensive Half', 'Time Offensive Half'], '% Most Forward': [ 'Time Most Forward', 'Time Defensive Half', 'Time Offensive Half' ], '% Goals Responsible': ['Conceded When Last', 'Goals Against'], 'Position Ratio': ['Time Infront Ball', 'Time Behind Ball'], } stat = stat.title() # Make sure the program understands the specified stat if it's mis-capitalized or whatever # SHEETS STATS if useSheet: if stat.lower() in [ "sp", "series", "series_played", "series-played", "splayed", "seris", "sieries", "seiries" ]: stat = "Series Played" elif stat.lower() in [ "gp", "games", "games_played", "games-played", "gplayed" ]: stat = "Games Played" elif stat.lower() in ["goals", "goal", "scores"]: stat = "Goals" elif stat.lower() in ['assists', 'assist', 'passes']: stat = "Assists" elif stat.lower() in ['saves', 'save']: stat = "Saves" elif stat.lower() in ['shots', 'shot']: stat = "Shots" elif stat.lower() in [ 'points', 'point', 'goals+assists', 'goals + assists', 'goals and assists' ]: stat = "Points (Goals+Assists)" elif stat.lower() in [ 'gpg', 'goals per game', 'goals pg', 'goals per' ]: stat = "Goals per game" elif stat.lower() in [ 'apg', 'assists per game', 'assists pg', 'assists per' ]: stat = "Assists per game" elif stat.lower() in [ 'spg', 'sapg', 'saves per game', 'saves pg', 'saves per' ]: stat = "Saves per game" elif stat.lower() in [ 'shot rate', 'shooting percent', 'shooting percentage', 'shot accuracy', 'shooting accuracy', 'shooting %', 'shot %' ]: stat = "Shooting %" elif stat.lower() in [ 'win rate', 'winning rate', 'winning percent', 'winning percentage' ]: stat = "Winning %" elif stat.lower() in ['wins', 'win']: stat = "Wins" elif stat.lower() in [ 'ppg', 'points per game', 'points pg', 'points per' ]: stat = "Points per Game" elif stat.lower() in [ 'shpg', 'shots per game', ' shots pg', 'shots per' ]: stat = "Shots Per Game" else: raise InvalidStatError(stat) try: if league.lower() in ['major', 'aaa', 'aa', 'a']: data = self.p4sheet.to_df( f'{league} League Stat Database!C3:R') elif league.lower() in [ 'independent', 'maverick', 'renegade', 'paladin' ]: data = self.indysheet.to_df(f'{league} Stat Database!C3:R') except SheetToDfError: raise StatSheetError('{league} League Stat Database!C3:R') data.set_index("Player", inplace=True) data.replace(to_replace='', value='0', inplace=True) # Turn number strings into ints and floats for col in data.columns: try: data[col] = data[col].astype(int) except: try: data[col] = data[col].astype(float) except: data[col] = data[col].str.rstrip('%').astype(float) lb = data[stat.title()] lb: pd.Series = lb[lb > 0] games_played = data['Games Played'] if pergame: if stat in [ 'Goals Per Game', 'Assists per game', 'Saves per game', 'Points per Game', 'Shots per Game', 'Winning %', 'Shooting %' ]: pass # These stats are already per game else: lb = round(lb / games_played, 2) # DATABASE STATS else: if stat not in valid_stats and stat not in compound_stats.keys( ) and stat != 'Mvp Rate': raise InvalidStatError(stat) else: stat = stat.title() if stat == 'Mvp Rate': stat = 'MVP Rate' players = self.session.players data = pd.Series(name=stat, dtype=float) stat_snake = snakecase_stat(stat) if stat not in compound_stats.keys(): # category = findCategory(stat) # if not category: # raise InvalidStatError(stat) try: if league != "all": cursor = self.session.all_players.aggregate([{ "$unwind": "$seasons" }, { "$match": { "league": league, f"seasons.season_stats.{stat_snake}": { '$gt': 0 }, f"seasons.season_stats.games_played": { '$gt': 0 } } }]) else: cursor = self.session.all_players.aggregate([{ "$unwind": "$seasons" }, { "$match": { f"seasons.season_stats.{stat_snake}": { '$gt': 0 }, f"seasons.season_stats.games_played": { '$gt': 0 } } }]) except: raise FindPlayersError(league, stat) # Iterate through cursor and get all players' stats while cursor.alive: doc = cursor.next() if isinstance(doc['seasons'], dict): doc['seasons'] = [ doc['seasons'] ] # Fix strange behavior from pymongo that turns arrays with 1 value into objects # Get the index needed for the current season season_index = self.season_index(doc) if pergame: data[doc['username']] = round( (doc['seasons'][season_index]['season_stats'] [stat_snake] / doc['seasons'][season_index] ['season_stats']["games_played"]), 2) else: data[doc['username']] = round( doc['seasons'][season_index]['season_stats'] [stat_snake], 2) return data.sort_values(ascending=asc).head(limit) else: filter = {"seasons.season_num": current_season} if league != "all": filter['league'] = league necessary = compound_stats[stat] for stat_name in necessary: filter[ f'seasons.season_stats.{snakecase_stat(stat_name)}'] = { '$gt': 0 } try: cursor = self.session.all_players.aggregate([{ "$unwind": "$seasons" }, { "$match": filter }]) except: raise FindPlayersError(league, stat) # Iterate throuch cursor and get all players' stats while cursor.alive: info = cursor.next() if isinstance(info['seasons'], dict): info['seasons'] = [ info['seasons'] ] # Fix strange behavior from pymongo that turns arrays with 1 value into objects # Get the index needed for the current season player_stats = info['seasons'][self.season_index( info)]['season_stats'] if stat == 'Winning %': datapoint = round((player_stats['games_won'] / player_stats['games_played']), 2) elif stat == 'Shooting %': datapoint = round( (player_stats['goals'] / player_stats['shots']), 2) elif stat == 'Shooting % Against': datapoint = round((player_stats['goals_against'] / player_stats['shots_against']), 2) elif stat == 'Points': datapoint = round( (player_stats['goals'] + player_stats['assists']), 2) if pergame: datapoint = round( (datapoint / player_stats['games_played']), 2) elif stat == 'MVP Rate': datapoint = round( player_stats['mvps'] / player_stats['games_won'], 2) elif stat == '% Time Slow': datapoint = round( player_stats['time_slow'] / (player_stats['time_slow'] + player_stats['time_boost'] + player_stats['time_supersonic']), 2) elif stat == '% Time Boost': datapoint = round( player_stats['time_boost'] / (player_stats['time_slow'] + player_stats['time_boost'] + player_stats['time_supersonic']), 2) elif stat == '% Time Supersonic': datapoint = round( player_stats['time_supersonic'] / (player_stats['time_slow'] + player_stats['time_boost'] + player_stats['time_supersonic']), 2) elif stat == '% Time Ground': datapoint = round( player_stats['time_ground'] / (player_stats['time_ground'] + player_stats['time_low_air'] + player_stats['time_high_air']), 2) elif stat == '% Time Low Air': datapoint = round( player_stats['time_low_air'] / (player_stats['time_ground'] + player_stats['time_low_air'] + player_stats['time_high_air']), 2) elif stat == '% Time High Air': datapoint = round( player_stats['time_high_air'] / (player_stats['time_ground'] + player_stats['time_low_air'] + player_stats['time_high_air']), 2) elif stat == '% Most Back': datapoint = round( player_stats['time_most_back'] / (player_stats['time_defensive_half'] + player_stats['time_offensive_half']), 2) elif stat == '% Most Forward': datapoint = round( player_stats['time_most_forward'] / (player_stats['time_defensive_half'] + player_stats['time_offensive_half']), 2) elif stat == '% Goals Responsible': datapoint = round( player_stats['conceded_when_last'] / player_stats['goals_against'], 2) elif stat == 'Position Ratio': datapoint = round( player_stats['time_infront_ball'] / player_stats['time_behind_ball'], 2) data[info['username']] = datapoint return data.sort_values(ascending=asc).head(limit) return lb.sort_values(ascending=asc).head(limit) def get_me(self, discord_id: str) -> str: ids = self.p4sheet.to_df('Trackers!A1:B').set_index('Discord ID') try: player = ids.loc[discord_id, 'Username'] if type(player) == pd.core.series.Series: player = player[0] except: raise FindMeError(discord_id) return player def gdstats(self, player: str, day: int = None, stat: str = None, pergame: bool = False) -> pd.DataFrame: if day == None: day = get_latest_gameday() try: datarange = dates[day] except KeyError: raise InvalidDayError(day) try: data = self.gdsheet.to_df(datarange).set_index("Username") data.loc[:, 'Series Played': 'Fantasy Points'] = data.loc[:, 'Series Played': 'Fantasy Points'].astype( float) except SheetToDfError: raise GDStatsSheetError(day) lower_players: pd.Index = data.index.str.lower() if player.lower() in lower_players.values: pindex = np.where(lower_players.to_numpy() == player.lower()) player = data.index[pindex].values[0] else: raise PlayerNotFoundError(player, day) stats_series = data.loc[player] if isinstance(stats_series, pd.DataFrame): stats_series = stats_series.sum() if pergame: stats_series[3:-1] = stats_series[3:-1].apply( lambda x: float(x)) / int(stats_series['Games Played']) stats_series[3:-1] = stats_series[3:-1].apply( lambda x: round(x, 2)) return stats_series def teamstats(self, team: str = None, league: str = None) -> pd.DataFrame: if team != None: team = team.title() roster = self.teams.get_roster(team) league = self.identifier.find_league(team) elif league != None: league = leagues[league.lower()] sheet: Sheet if league in ['Major', 'AAA', 'AA', 'A']: sheet = self.p4sheet elif league in ['Independent', 'Maverick', 'Renegade', 'Paladin']: sheet = self.indysheet else: raise LeagueNotFoundError(team) if not team: stats = sheet.to_df(f"{league} Stats!D5:Q9").set_index("") stats = stats.append( sheet.to_df(f"{league} Stats!D12:Q16").set_index("")) stats = stats.append( sheet.to_df(f"{league} Stats!D20:Q24").set_index("")) stats = stats.append( sheet.to_df(f"{league} Stats!D27:Q31").set_index("")) stats.rename(columns={ 'Record (Div Record)': 'Record', 'Forfeits': "FFs", 'W/L Streak': 'Streak', 'Winning %': 'Win %', 'Goals | PG': 'Goals', 'Assists | PG': 'Assists', 'Saves | PG': 'Saves', 'Shots | PG': 'Shots', r'%Goals Assisted': 'Assist %', 'Shooting %': 'Shot %', 'GA | PG': 'GA', 'Shots Agnst | PG': 'SA', 'Opp Shooting %': 'Opp Shot %' }, inplace=True) return stats else: stats = pd.DataFrame() for player in roster: stats = stats.append(self.get_player_stats_sheet(player)) stats.rename(columns={ 'Series Played': 'Series', 'Games Played': 'Games', 'Points (Goals+Assists)': 'Points', 'Goals per game': 'GPG', 'Assists per game': 'APG', 'Saves per game': 'SPG', 'Shooting %': 'Shot %', 'Winning %': 'Win %', 'Points per Game': 'PPG', 'Shots Per Game': 'ShPG' }, inplace=True) return stats.set_index('Player') def difference(self, player: str, stat: str) -> List[float]: stat = stat.title() if stat not in valid_stats: raise InvalidStatError(stat) day = get_latest_gameday() player_info = self.session.players.find_one( {'$text': { '$search': f"\"{player}\"" }}) if player_info == None: raise PlayerNotFoundError(player, 0) else: total_stat = player_info['stats'][findCategory(stat)][stat] games_played = player_info['stats']['general']['Games Played'] total_stat /= games_played while day > 0: try: recent_stat = self.gdstats(player, day, stat, pergame=True).loc[stat.title()] break except PlayerNotFoundError: day -= 1 continue if day == 0: # Raise error if player has no stats on gdstats sheet raise PlayerNotFoundError(player, 0) if recent_stat == 0: diff = -1 elif total_stat == 0: raise ZeroError() else: diff = (recent_stat - total_stat) / total_stat return (diff, total_stat, recent_stat)