diff --git a/.githooks/install-hooks.sh b/.githooks/install-hooks.sh new file mode 100755 index 0000000..39da684 --- /dev/null +++ b/.githooks/install-hooks.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# +# Install git hooks for this repository +# + +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) + +if [ -z "$REPO_ROOT" ]; then + echo "Error: Not in a git repository" + exit 1 +fi + +HOOKS_DIR="$REPO_ROOT/.githooks" +GIT_HOOKS_DIR="$REPO_ROOT/.git/hooks" + +echo "Installing git hooks..." + +if [ -f "$HOOKS_DIR/pre-commit" ]; then + cp "$HOOKS_DIR/pre-commit" "$GIT_HOOKS_DIR/pre-commit" + chmod +x "$GIT_HOOKS_DIR/pre-commit" + echo "Installed pre-commit hook" +else + echo "pre-commit hook not found in $HOOKS_DIR" +fi + +echo "" +echo "The pre-commit hook will:" +echo " - Run ruff lint checks" +echo " - Block commits on syntax errors or lint failures" +echo "" +echo "To bypass in emergency: git commit --no-verify" diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..51a7fa8 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Pre-commit hook: ruff lint check on staged Python files. +# Catches syntax errors, unused imports, and basic issues before commit. +# To bypass in emergency: git commit --no-verify +# + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT" + +STAGED_PY=$(git diff --cached --name-only --diff-filter=ACM -- '*.py') +if [ -z "$STAGED_PY" ]; then + exit 0 +fi + +echo "ruff check on staged files..." +echo "$STAGED_PY" | xargs ruff check +RUFF_EXIT=$? + +if [ $RUFF_EXIT -ne 0 ]; then + echo "" + echo -e "${RED}Pre-commit checks failed. Commit blocked.${NC}" + echo -e "${YELLOW}To bypass (not recommended): git commit --no-verify${NC}" + exit 1 +fi + +echo -e "${GREEN}All checks passed.${NC}" +exit 0 diff --git a/cogs/admins.py b/cogs/admins.py index 1d1b438..0ce2350 100644 --- a/cogs/admins.py +++ b/cogs/admins.py @@ -12,11 +12,20 @@ from api_calls import * from helpers import * import in_game from in_game import ai_manager -from in_game.gameplay_models import Play, Session, select, engine, Game, Cardset, Lineup, Team, Player +from in_game.gameplay_models import ( + Play, + Session, + select, + engine, + Game, + Cardset, + Lineup, + Team, + Player, +) from in_game.gameplay_queries import get_team_or_none, reset_cache - -logger = logging.getLogger('discord_app') +logger = logging.getLogger("discord_app") class Admins(commands.Cog): @@ -28,288 +37,337 @@ class Admins(commands.Cog): # await self.bot.change_presence(activity=discord.Game(name='strat | .help')) async def cog_command_error(self, ctx, error): - await ctx.send(f'{error}') + await ctx.send(f"{error}") async def dev_startup(self): # Check for Paper Sluggers event - e_query = await db_get('events', params=[('name', 'Paper Sluggers')]) + e_query = await db_get("events", params=[("name", "Paper Sluggers")]) if e_query is None: this_event = await db_post( - 'events', + "events", payload={ "name": "Paper Sluggers", - "short_desc": f'Draft a team to win you ten games as we celebrate the introduction of the ' - f'Mario Super Sluggers cardset to Paper Dynasty!', + "short_desc": f"Draft a team to win you ten games as we celebrate the introduction of the " + f"Mario Super Sluggers cardset to Paper Dynasty!", "long_desc": "", - "url": f'https://cdn.discordapp.com/attachments/603421569972305921/1087862987215347805/' - f'PD-Mario-Full.png', - "active": True - } + "url": f"https://cdn.discordapp.com/attachments/603421569972305921/1087862987215347805/" + f"PD-Mario-Full.png", + "active": True, + }, ) else: - this_event = e_query['events'][0] + this_event = e_query["events"][0] # Check for Game Rewards - gr_query = await db_get('gamerewards', params=[('name', 'MVP Pack')]) + gr_query = await db_get("gamerewards", params=[("name", "MVP Pack")]) if gr_query: - if gr_query['count'] == 0: + if gr_query["count"] == 0: mv_pack = db_post( - 'gamerewards', - payload={ - 'name': 'MVP', - 'pack_type_id': 5 - } + "gamerewards", payload={"name": "MVP", "pack_type_id": 5} ) else: - mv_pack = gr_query['gamerewards'][0] + mv_pack = gr_query["gamerewards"][0] - gr_query = await db_get('gamerewards', params=[('name', 'All-Star Pack')]) - if gr_query['count'] == 0: + gr_query = await db_get("gamerewards", params=[("name", "All-Star Pack")]) + if gr_query["count"] == 0: as_pack = db_post( - 'gamerewards', - payload={ - 'name': 'All-Star Pack', - 'pack_type_id': 6 - } + "gamerewards", payload={"name": "All-Star Pack", "pack_type_id": 6} ) else: - as_pack = gr_query['gamerewards'][0] + as_pack = gr_query["gamerewards"][0] - gr_query = await db_get('gamerewards', params=[('name', 'Mario Pack')]) - if gr_query['count'] == 0: + gr_query = await db_get("gamerewards", params=[("name", "Mario Pack")]) + if gr_query["count"] == 0: m_pack = await db_post( - 'gamerewards', - payload={ - 'name': 'Mario Pack', - 'pack_type_id': 7 - } + "gamerewards", payload={"name": "Mario Pack", "pack_type_id": 7} ) else: - m_pack = gr_query['gamerewards'][0] + m_pack = gr_query["gamerewards"][0] # Check for Gauntlet rewards - gr_query = await db_get('gauntletrewards', params=[('gauntlet_id', this_event['id'])]) - if gr_query['count'] == 0: + gr_query = await db_get( + "gauntletrewards", params=[("gauntlet_id", this_event["id"])] + ) + if gr_query["count"] == 0: await db_post( - 'gauntletrewards', + "gauntletrewards", payload={ - 'rewards': [ + "rewards": [ { - 'name': '3 Wins', - 'gauntlet_id': this_event['id'], - 'reward_id': m_pack['id'], - 'win_num': 3 + "name": "3 Wins", + "gauntlet_id": this_event["id"], + "reward_id": m_pack["id"], + "win_num": 3, }, { - 'name': '6 Wins', - 'gauntlet_id': this_event['id'], - 'reward_id': as_pack['id'], - 'win_num': 6 + "name": "6 Wins", + "gauntlet_id": this_event["id"], + "reward_id": as_pack["id"], + "win_num": 6, }, { - 'name': '8 Wins', - 'gauntlet_id': this_event['id'], - 'reward_id': m_pack['id'], - 'win_num': 8 + "name": "8 Wins", + "gauntlet_id": this_event["id"], + "reward_id": m_pack["id"], + "win_num": 8, }, { - 'name': '10 Wins', - 'gauntlet_id': this_event['id'], - 'reward_id': mv_pack['id'], - 'win_num': 10 + "name": "10 Wins", + "gauntlet_id": this_event["id"], + "reward_id": mv_pack["id"], + "win_num": 10, }, { - 'name': '10-0', - 'gauntlet_id': this_event['id'], - 'reward_id': m_pack['id'], - 'win_num': 10, - 'loss_max': 0 - } + "name": "10-0", + "gauntlet_id": this_event["id"], + "reward_id": m_pack["id"], + "win_num": 10, + "loss_max": 0, + }, ] - } + }, ) - @commands.command(name='dev_startup', help='Run startup function') + @commands.command(name="dev_startup", help="Run startup function") async def dev_startup_command(self, ctx): await self.dev_startup() await ctx.send(random_conf_gif()) - group_give = app_commands.Group(name='give', description='Mod: Distribute packs or tokens') + group_give = app_commands.Group( + name="give", description="Mod: Distribute packs or tokens" + ) - @group_give.command(name='packs') + @group_give.command(name="packs") async def give_packs_subcommand( - self, interaction: discord.Interaction, team_abbrevs: str, num_packs: int, pack_type: Literal['Standard', 'Premium', 'MVP'], cardset_id: int = None): + self, + interaction: discord.Interaction, + team_abbrevs: str, + num_packs: int, + pack_type: Literal["Standard", "Premium", "MVP"], + cardset_id: int = None, + ): if not owner_only(interaction): await interaction.response.send_message(random_no_gif()) return - current = await db_get('current') + current = await db_get("current") await interaction.response.defer() - p_query = await db_get('packtypes', params=[('name', pack_type)]) - if p_query['count'] == 0: - raise KeyError(f'Packtype not found') - this_packtype = p_query['packtypes'][0] + p_query = await db_get("packtypes", params=[("name", pack_type)]) + if p_query["count"] == 0: + raise KeyError(f"Packtype not found") + this_packtype = p_query["packtypes"][0] c_id = None if cardset_id is not None: - cardset = await db_get('cardsets', object_id=cardset_id, none_okay=False) - c_id = cardset['id'] - - response = '' - for x in team_abbrevs.split(' '): - if x.upper() == 'LEAGUE': - all_teams = await db_get('teams', params=[('season', current['season'])]) + cardset = await db_get("cardsets", object_id=cardset_id, none_okay=False) + c_id = cardset["id"] + + response = "" + for x in team_abbrevs.split(" "): + if x.upper() == "LEAGUE": + all_teams = await db_get( + "teams", params=[("season", current["season"])] + ) total_teams = 0 - for y in all_teams['teams']: - if not y['is_ai'] and 'gauntlet' not in y['abbrev'].lower(): - logger.warning(f'Giving {num_packs} pack(s) to team: {y["abbrev"]}') + for y in all_teams["teams"]: + if not y["is_ai"] and "gauntlet" not in y["abbrev"].lower(): + logger.warning( + f'Giving {num_packs} pack(s) to team: {y["abbrev"]}' + ) await db_post( - 'packs', - payload={'packs': [{ - 'team_id': y['id'], - 'pack_type_id': this_packtype['id'], - 'pack_cardset_id': c_id - } for x in range(num_packs)]} + "packs", + payload={ + "packs": [ + { + "team_id": y["id"], + "pack_type_id": this_packtype["id"], + "pack_cardset_id": c_id, + } + for x in range(num_packs) + ] + }, ) total_teams += 1 - response = f'Just gave all {total_teams} teams {num_packs} ' \ - f'{pack_type} pack{"s" if num_packs > 1 else ""}!' + response = ( + f"Just gave all {total_teams} teams {num_packs} " + f'{pack_type} pack{"s" if num_packs > 1 else ""}!' + ) else: - t_query = await db_get('teams', params=[('abbrev', x), ('season', current['season'])]) - if t_query['count'] > 0: - team = t_query['teams'][0] - + t_query = await db_get( + "teams", params=[("abbrev", x), ("season", current["season"])] + ) + if t_query["count"] > 0: + team = t_query["teams"][0] + await db_post( - 'packs', - payload={'packs': [{ - 'team_id': team['id'], - 'pack_type_id': this_packtype['id'], - 'pack_cardset_id': c_id - } for x in range(num_packs)]} + "packs", + payload={ + "packs": [ + { + "team_id": team["id"], + "pack_type_id": this_packtype["id"], + "pack_cardset_id": c_id, + } + for x in range(num_packs) + ] + }, ) response += f'Just gave {num_packs} {pack_type} pack{"s" if num_packs > 1 else ""} to the {team["sname"]}.' else: - await interaction.edit_original_response(content=f'Hmm...I\'m not sure who **{x.upper()}** is.') + await interaction.edit_original_response( + content=f"Hmm...I'm not sure who **{x.upper()}** is." + ) return - logger.info(f'give info: {response}') - await interaction.edit_original_response(content=f'{response if len(response) > 0 else "All done!"}') + logger.info(f"give info: {response}") + await interaction.edit_original_response( + content=f'{response if len(response) > 0 else "All done!"}' + ) - @commands.hybrid_command(name='post-guide', help='Mod: Post the ratings guide to team sheet') + @commands.hybrid_command( + name="post-guide", help="Mod: Post the ratings guide to team sheet" + ) @commands.is_owner() async def post_guide_command(self, ctx, gm: Member): team = await get_team_by_owner(gm.id) - t_query = await db_get('teams', params=[('gm_id', gm.id)]) + t_query = await db_get("teams", params=[("gm_id", gm.id)]) - if t_query['count'] == 0: - await ctx.send(f'Huh...I don\'t see any teams for {gm.name}') + if t_query["count"] == 0: + await ctx.send(f"Huh...I don't see any teams for {gm.name}") - for x in t_query['teams']: - await db_patch('teams', object_id=x['id'], params=[('has_guide', True)]) + for x in t_query["teams"]: + await db_patch("teams", object_id=x["id"], params=[("has_guide", True)]) await ctx.send(random_conf_gif()) - @app_commands.command(name='add-player-card', description='Mod: Manually upload a new PD card') - @app_commands.checks.has_any_role('Da Commish') + @app_commands.command( + name="add-player-card", description="Mod: Manually upload a new PD card" + ) + @app_commands.checks.has_any_role("Da Commish") async def new_manual_card_slash( - self, interaction: discord.Interaction, player_type: Literal['batter', 'pitcher'], player_json: str, - bc_or_pc_json: str, position_list: str, ratings_vl_json: str, ratings_vr_json: str): + self, + interaction: discord.Interaction, + player_type: Literal["batter", "pitcher"], + player_json: str, + bc_or_pc_json: str, + position_list: str, + ratings_vl_json: str, + ratings_vr_json: str, + ): await interaction.response.defer() try: d_player = json.loads(player_json) except json.decoder.JSONDecodeError as e: - await interaction.edit_original_response(content=f'RIP. Failed to process that player.') + await interaction.edit_original_response( + content=f"RIP. Failed to process that player." + ) return try: d_bcpc = json.loads(bc_or_pc_json) except json.decoder.JSONDecodeError as e: - await interaction.edit_original_response(content=f'RIP. Failed to process that {player_type} card.') + await interaction.edit_original_response( + content=f"RIP. Failed to process that {player_type} card." + ) return try: d_positions = json.loads(position_list) except json.decoder.JSONDecodeError as e: - await interaction.edit_original_response(content=f'RIP. Failed to process the position data.') + await interaction.edit_original_response( + content=f"RIP. Failed to process the position data." + ) return try: d_ratings_vl = json.loads(ratings_vl_json) except json.decoder.JSONDecodeError as e: - await interaction.edit_original_response(content=f'RIP. Failed to process the vL ratings.') + await interaction.edit_original_response( + content=f"RIP. Failed to process the vL ratings." + ) return try: d_ratings_vr = json.loads(ratings_vr_json) except json.decoder.JSONDecodeError as e: - await interaction.edit_original_response(content=f'RIP. Failed to process the vR ratings.') + await interaction.edit_original_response( + content=f"RIP. Failed to process the vR ratings." + ) return - logger.info(f'Data gathered:\n\n{d_player}\n\n{d_bcpc}\n\n{d_positions}\n\n{d_ratings_vl}\n\n{d_ratings_vr}') + logger.info( + f"Data gathered:\n\n{d_player}\n\n{d_bcpc}\n\n{d_positions}\n\n{d_ratings_vl}\n\n{d_ratings_vr}" + ) await interaction.edit_original_response( - content='Just spit out the debug info to the log, but processing was successful!') + content="Just spit out the debug info to the log, but processing was successful!" + ) - @app_commands.command(name='reset-image', description='Force a refresh of a player\'s card images') + @app_commands.command( + name="reset-image", description="Force a refresh of a player's card images" + ) async def reset_image(self, interaction: discord.Interaction, player_id: int): await interaction.response.defer() - new_player = await db_post(f'players/{player_id}/image-reset') + new_player = await db_post(f"players/{player_id}/image-reset") player_embed = await get_card_embeds(get_blank_team_card(new_player)) if not owner_only(interaction): await send_to_channel( self.bot, - 'pd-network-news', - content=f'{interaction.user.display_name} just refreshed {player_desc(new_player)}\'s card ' - f'{await get_emoji(interaction, "prayge")}' + "pd-network-news", + content=f"{interaction.user.display_name} just refreshed {player_desc(new_player)}'s card " + f'{await get_emoji(interaction, "prayge")}', ) await interaction.edit_original_response(content=None, embeds=player_embed) - @commands.hybrid_command(name='sync-sheets', help='Mod: Sync AI team sheets') + @commands.hybrid_command(name="sync-sheets", help="Mod: Sync AI team sheets") @commands.is_owner() async def sync_sheets_command(self, ctx): - t_query = await db_get('teams', params=[('is_ai', True)]) + t_query = await db_get("teams", params=[("is_ai", True)]) - response = await ctx.send(f'Alright, I\'m getting started...') + response = await ctx.send(f"Alright, I'm getting started...") sheets = get_sheets(self.bot) - for count, team in enumerate(t_query['teams']): - this_sheet = sheets.open_by_key(team['gsheet']) - team_data = this_sheet.worksheet_by_title('Team Data') + for count, team in enumerate(t_query["teams"]): + this_sheet = sheets.open_by_key(team["gsheet"]) + team_data = this_sheet.worksheet_by_title("Team Data") team_data.update_values( - crange='B1:B2', - values=[[f'{team["id"]}'], [f'\'{team_hash(team)}']] + crange="B1:B2", values=[[f'{team["id"]}'], [f"'{team_hash(team)}"]] + ) + await response.edit( + content=f'Just finished the {team["sname"]} ({count + 1}/{len(t_query["teams"])})...' ) - await response.edit(content=f'Just finished the {team["sname"]} ({count + 1}/{len(t_query["teams"])})...') - await response.edit(content=f'All done!') + await response.edit(content=f"All done!") - @commands.command(name='update-rarity', help='Mod: Pull current rarities and update players') + @commands.command( + name="update-rarity", help="Mod: Pull current rarities and update players" + ) @commands.is_owner() async def update_rarity_command(self, ctx): - await ctx.send(f'Oh boy, here I go sheetsing again!') + await ctx.send(f"Oh boy, here I go sheetsing again!") rarities = { - 'MVP': 1, - 'All-Star': 2, - 'Starter': 3, - 'Reserve': 4, - 'Replacement': 5, - 'Hall of Fame': 99, - 'HoF': 99 + "MVP": 1, + "All-Star": 2, + "Starter": 3, + "Reserve": 4, + "Replacement": 5, + "Hall of Fame": 99, + "HoF": 99, } def new_cost(player, new_rarity, old_rarity): - old_cost = player['cost'] + old_cost = player["cost"] old_rarity = old_rarity new_rarity = new_rarity - logger.info(f'old_rarity: {old_rarity} / new_rarity: {new_rarity}') + logger.info(f"old_rarity: {old_rarity} / new_rarity: {new_rarity}") if old_rarity == 1: if new_rarity == 2: @@ -378,121 +436,143 @@ class Admins(commands.Cog): elif new_rarity == 5: return max(old_cost - 2400, 5) - raise KeyError(f'Could not find a cost update for {player["p_name"]} from {player["rarity"]["name"]} to ' - f'{new_rarity}') + raise KeyError( + f'Could not find a cost update for {player["p_name"]} from {player["rarity"]["name"]} to ' + f"{new_rarity}" + ) - await ctx.send(f'Running player updates...') + await ctx.send(f"Running player updates...") errors = [] counter = 0 # Read player-json.csv - with open('storage/player_json.csv') as csv_file: + with open("storage/player_json.csv") as csv_file: csv_reader = csv.reader(csv_file) # Per line, search for player by cardset_id and bbref_id for row in csv_reader: if counter > 50: pass - p_query = await db_get('players', params=[('cardset_id', 9), ('bbref_id', row[4])]) - if p_query['count'] > 0: - this_player = p_query['players'][0] + p_query = await db_get( + "players", params=[("cardset_id", 9), ("bbref_id", row[4])] + ) + if p_query["count"] > 0: + this_player = p_query["players"][0] updates = [] - if '0706' in this_player['image']: - updates.append(('image', this_player['image'].replace('2023-0706', '2023-0802'))) + if "0706" in this_player["image"]: + updates.append( + ( + "image", + this_player["image"].replace("2023-0706", "2023-0802"), + ) + ) - if this_player['rarity']['name'] != row[9] and not \ - (this_player['rarity']['name'] == 'Hall of Fame' and row[9] == 'HoF'): + if this_player["rarity"]["name"] != row[9] and not ( + this_player["rarity"]["name"] == "Hall of Fame" + and row[9] == "HoF" + ): new_r = rarities[row[9]] updates.append( - ('cost', new_cost(this_player, new_r, rarities[this_player['rarity']['name']])) - ) - updates.append( - ('rarity_id', new_r) + ( + "cost", + new_cost( + this_player, + new_r, + rarities[this_player["rarity"]["name"]], + ), + ) ) + updates.append(("rarity_id", new_r)) - if this_player['pos_1'] != row[11]: - updates.append(('pos_1', row[11])) + if this_player["pos_1"] != row[11]: + updates.append(("pos_1", row[11])) try: if len(row[12]) > 0: - if this_player['pos_2'] != row[12]: - updates.append(('pos_2', row[12])) - elif this_player['pos_2'] is not None: - updates.append(('pos_2', False)) + if this_player["pos_2"] != row[12]: + updates.append(("pos_2", row[12])) + elif this_player["pos_2"] is not None: + updates.append(("pos_2", False)) except IndexError: pass try: if len(row[13]) > 0: - if this_player['pos_3'] != row[13]: - updates.append(('pos_3', row[13])) - elif this_player['pos_3'] is not None: - updates.append(('pos_3', False)) + if this_player["pos_3"] != row[13]: + updates.append(("pos_3", row[13])) + elif this_player["pos_3"] is not None: + updates.append(("pos_3", False)) except IndexError: pass try: if len(row[14]) > 0: - if this_player['pos_4'] != row[14]: - updates.append(('pos_4', row[14])) - elif this_player['pos_4'] is not None: - updates.append(('pos_4', False)) + if this_player["pos_4"] != row[14]: + updates.append(("pos_4", row[14])) + elif this_player["pos_4"] is not None: + updates.append(("pos_4", False)) except IndexError: pass try: if len(row[15]) > 0: - if this_player['pos_5'] != row[15]: - updates.append(('pos_5', row[15])) - elif this_player['pos_5'] is not None: - updates.append(('pos_5', False)) + if this_player["pos_5"] != row[15]: + updates.append(("pos_5", row[15])) + elif this_player["pos_5"] is not None: + updates.append(("pos_5", False)) except IndexError: pass try: if len(row[16]) > 0: - if this_player['pos_6'] != row[16]: - updates.append(('pos_6', row[16])) - elif this_player['pos_6'] is not None: - updates.append(('pos_6', False)) + if this_player["pos_6"] != row[16]: + updates.append(("pos_6", row[16])) + elif this_player["pos_6"] is not None: + updates.append(("pos_6", False)) except IndexError: pass try: if len(row[17]) > 0: - if this_player['pos_7'] != row[17]: - updates.append(('pos_7', row[17])) - elif this_player['pos_7'] is not None: - updates.append(('pos_7', False)) + if this_player["pos_7"] != row[17]: + updates.append(("pos_7", row[17])) + elif this_player["pos_7"] is not None: + updates.append(("pos_7", False)) except IndexError: pass try: if len(row[18]) > 0: - if this_player['pos_8'] != row[18]: - updates.append(('pos_5', row[18])) - elif this_player['pos_8'] is not None: - updates.append(('pos_8', False)) + if this_player["pos_8"] != row[18]: + updates.append(("pos_5", row[18])) + elif this_player["pos_8"] is not None: + updates.append(("pos_8", False)) except IndexError: pass # Patch player with new rarity_id, cost, and positions 1 - 6 if len(updates) > 0: - logger.info(f'Updating {row[1]} - params: {updates}') + logger.info(f"Updating {row[1]} - params: {updates}") await db_patch( - 'players', - object_id=this_player['player_id'], - params=updates + "players", + object_id=this_player["player_id"], + params=updates, ) else: - errors.append(f'{row[1]} - {row[4]}') + errors.append(f"{row[1]} - {row[4]}") counter += 1 - await ctx.send(f'All done!') + await ctx.send(f"All done!") if len(errors) > 0: e_string = "\n- ".join(errors) - logger.error(f'update errors:\n{e_string}') - await ctx.send(f'I encountered the following errors:\n\n{e_string}') + logger.error(f"update errors:\n{e_string}") + await ctx.send(f"I encountered the following errors:\n\n{e_string}") - @app_commands.command(name='reset-cache', description='Reset all cached player cards for gameplay') - @app_commands.checks.has_any_role('Da Commish') + @app_commands.command( + name="reset-cache", description="Reset all cached player cards for gameplay" + ) + @app_commands.checks.has_any_role("Da Commish") async def reset_cache_command( - self, interaction: discord.Interaction, player_cache: Optional[bool] = True, - scouting_cache: Optional[bool] = True, team_cache: bool = True): + self, + interaction: discord.Interaction, + player_cache: Optional[bool] = True, + scouting_cache: Optional[bool] = True, + team_cache: bool = True, + ): await interaction.response.defer() # if player_cache: # in_game.data_cache.PLAYER_CACHE = {} @@ -502,101 +582,117 @@ class Admins(commands.Cog): # in_game.data_cache.PITCHINGCARD_CACHE = {} # if team_cache: # in_game.data_cache.TEAM_CACHE = {} - + with Session(engine) as session: reset_cache( session=session, players=player_cache, scouting=scouting_cache, - team=team_cache + team=team_cache, ) - + await interaction.edit_original_response( - content=random_gif(random.choice(['all done', 'yes sir', 'complete', 'finished'])) + content=random_gif( + random.choice(["all done", "yes sir", "complete", "finished"]) + ) ) - @commands.command(name='tc', help='Mod: Test command') + @commands.command(name="tc", help="Mod: Test command") @commands.is_owner() async def test_choices_command(self, ctx): - await ctx.send(f'Wiping AI dexes...') - await db_post('paperdex/wipe-ai', timeout=15) - await ctx.send(f'All done!') + await ctx.send(f"Wiping AI dexes...") + await db_post("paperdex/wipe-ai", timeout=15) + await ctx.send(f"All done!") - @commands.command(name='get-bc', help='Mod: Test batting card cache') + @commands.command(name="get-bc", help="Mod: Test batting card cache") @commands.is_owner() async def get_battingcard_command(self, ctx, player_id: int): - await ctx.channel.send(f'Pulling the batting card for player ID: {player_id}') + await ctx.channel.send(f"Pulling the batting card for player ID: {player_id}") this_data = None async with ctx.channel.typing(): this_data = await in_game.data_cache.get_pd_battingcard(player_id) - await ctx.channel.send(f'Dumping data here:\n\n{this_data}') + await ctx.channel.send(f"Dumping data here:\n\n{this_data}") - @commands.command(name='get-pc', help='Mod: Test pitching card cache') + @commands.command(name="get-pc", help="Mod: Test pitching card cache") @commands.is_owner() async def get_pitchngcard_command(self, ctx, player_id: int): - await ctx.channel.send(f'Pulling the pitching card for player ID: {player_id}') + await ctx.channel.send(f"Pulling the pitching card for player ID: {player_id}") this_data = None async with ctx.channel.typing(): this_data = await in_game.data_cache.get_pd_pitchingcard(player_id) - await ctx.channel.send(f'Dumping data here:\n\n{this_data}') + await ctx.channel.send(f"Dumping data here:\n\n{this_data}") - @commands.command(name='test-fatigue', help='Mod: Test the fatigue AI') + @commands.command(name="test-fatigue", help="Mod: Test the fatigue AI") @commands.is_owner() async def test_fatigue_command(self, ctx, play_id: int): - this_play = db_calls_gameplay.convert_stratplay(db_calls_gameplay.Play.get_by_id(play_id)) + this_play = db_calls_gameplay.convert_stratplay( + db_calls_gameplay.Play.get_by_id(play_id) + ) is_fatigued = in_game.ai_manager.is_pitcher_fatigued(this_play) - await ctx.channel.send(f'Checking fatigue for Play #{play_id} / ' - f'Pitcher {"IS" if is_fatigued else "IS NOT"} fatigued') + await ctx.channel.send( + f"Checking fatigue for Play #{play_id} / " + f'Pitcher {"IS" if is_fatigued else "IS NOT"} fatigued' + ) - @commands.command(name='test-exhibition', help='Mod: Test the lineup gen for exhibition games') + @commands.command( + name="test-exhibition", help="Mod: Test the lineup gen for exhibition games" + ) @commands.is_owner() async def test_exhibition_command( - self, ctx, which: Literal['sp', 'rp', 'lineup'], team_id: int, cardset_ids: str, backup_cardset_ids: str): - if which == 'sp': - await ctx.send(f'Fetching a SP for Team ID {team_id}...') + self, + ctx, + which: Literal["sp", "rp", "lineup"], + team_id: int, + cardset_ids: str, + backup_cardset_ids: str, + ): + if which == "sp": + await ctx.send(f"Fetching a SP for Team ID {team_id}...") this_pitcher = await ai_manager.get_starting_pitcher( - {'id': team_id}, - game_id=69, - is_home=True, - league_name='exhibition' + {"id": team_id}, game_id=69, is_home=True, league_name="exhibition" ) - await ctx.send(f'Selected Pitcher:\n{this_pitcher}') + await ctx.send(f"Selected Pitcher:\n{this_pitcher}") # elif which == 'rp': # await ctx.send(f'Fetching an RP for Team ID {team_id}...') - @commands.command(name='test-dropdown', help='Mod: Test the custom dropdown objects') + @commands.command( + name="test-dropdown", help="Mod: Test the custom dropdown objects" + ) @commands.is_owner() async def test_dropdown_command(self, ctx): options = [ - discord.SelectOption(label='2024 Season', value='17'), - discord.SelectOption(label='2018 Season', value='13'), - discord.SelectOption(label='2016 Season', value='11'), - discord.SelectOption(label='2008 Season', value='12'), - discord.SelectOption(label='2007 Season', value='07'), - discord.SelectOption(label='2006 Season', value='06'), - discord.SelectOption(label='2005 Season', value='05'), - discord.SelectOption(label='2004 Season', value='04'), - discord.SelectOption(label='2003 Season', value='03'), - discord.SelectOption(label='2002 Season', value='02'), + discord.SelectOption(label="2024 Season", value="17"), + discord.SelectOption(label="2018 Season", value="13"), + discord.SelectOption(label="2016 Season", value="11"), + discord.SelectOption(label="2008 Season", value="12"), + discord.SelectOption(label="2007 Season", value="07"), + discord.SelectOption(label="2006 Season", value="06"), + discord.SelectOption(label="2005 Season", value="05"), + discord.SelectOption(label="2004 Season", value="04"), + discord.SelectOption(label="2003 Season", value="03"), + discord.SelectOption(label="2002 Season", value="02"), ] async def my_callback(interaction: discord.Interaction, values): await interaction.response.send_message( - f'Your selection{"s are" if len(values) > 1 else " is"}: {", ".join(values)}') + f'Your selection{"s are" if len(values) > 1 else " is"}: {", ".join(values)}' + ) my_dropdown = Dropdown( option_list=options, - placeholder='Select a cardset', + placeholder="Select a cardset", callback=my_callback, - max_values=8 + max_values=8, ) view = DropdownView([my_dropdown]) - await ctx.send(f'Here is your dropdown:', view=view) + await ctx.send(f"Here is your dropdown:", view=view) - @commands.command(name='db-check', help='Mod: Check cached db volume') + @commands.command(name="db-check", help="Mod: Check cached db volume") @commands.is_owner() async def db_check_command(self, ctx): - message = await ctx.send('I\'ll take a quick peek at the db and see what you\'ve got...') + message = await ctx.send( + "I'll take a quick peek at the db and see what you've got..." + ) with Session(engine) as session: games = session.exec(select(Game.id)).all() cardsets = session.exec(select(Cardset.id)).all() @@ -605,19 +701,114 @@ class Admins(commands.Cog): players = session.exec(select(Player.id)).all() plays = session.exec(select(Play.id)).all() - output = f'## Database Counts\nGames: {len(games)}\nCardsets: {len(cardsets)}\nLineups: {len(lineups)}\nTeams: {len(teams)}\nPlayers: {len(players)}\nPlays: {len(plays)}' + output = f"## Database Counts\nGames: {len(games)}\nCardsets: {len(cardsets)}\nLineups: {len(lineups)}\nTeams: {len(teams)}\nPlayers: {len(players)}\nPlays: {len(plays)}" await message.edit(content=output) - - - @commands.command(name='test_evo', help='Mod: Test pokemon evolution') + @commands.command(name="test_evo", help="Mod: Test pokemon evolution") @commands.is_owner() async def test_evolution(self, ctx, team_abbrev: str): with Session(engine) as session: this_team = await get_team_or_none(session, team_abbrev=team_abbrev) await evolve_pokemon(this_team, ctx.channel, responders=[ctx.author]) + @app_commands.command( + name="resend_scout", + description="Admin: Resend a scout opportunity to pack-openings", + ) + async def resend_scout( + self, interaction: discord.Interaction, scout_opp_id: int, minutes: int = 60 + ): + if not owner_only(interaction): + await interaction.response.send_message(random_no_gif()) + return + + await interaction.response.defer(ephemeral=True) + + import datetime + from helpers.scouting import build_scout_embed + from helpers.utils import int_timestamp + from discord_ui.scout_view import ScoutView + + # Fetch the scout opportunity + scout_opp = await db_get(f"scout_opportunities/{scout_opp_id}") + if not scout_opp: + await interaction.followup.send( + f"Scout opportunity {scout_opp_id} not found.", ephemeral=True + ) + return + + # Fetch cards + card_ids = scout_opp.get("card_ids", []) + if isinstance(card_ids, str): + card_ids = json.loads(card_ids) + + cards = [] + for card_id in card_ids: + card = await db_get(f"cards/{card_id}") + if card: + cards.append(card) + + if not cards: + await interaction.followup.send( + "Could not fetch any cards.", ephemeral=True + ) + return + + # Fetch opener team + opener_team = await db_get(f"teams/{scout_opp['opener_team']}") + if not opener_team: + await interaction.followup.send( + "Could not fetch opener team.", ephemeral=True + ) + return + + # Find pack-openings channel + channel = discord.utils.get( + interaction.guild.text_channels, name="pack-openings" + ) + if not channel: + await interaction.followup.send( + "pack-openings channel not found.", ephemeral=True + ) + return + + # Build with custom timeout + now = datetime.datetime.now() + expires_dt = now + datetime.timedelta(minutes=minutes) + expires_unix = int(expires_dt.timestamp()) + expires_at_ms = int_timestamp(expires_dt) + + # Update the scout opportunity's expires_at in the database + await db_patch( + "scout_opportunities", + scout_opp_id, + params=[("expires_at", expires_at_ms)], + ) + + embed, card_lines = build_scout_embed( + opener_team, cards, expires_unix=expires_unix + ) + + view = ScoutView( + scout_opp_id=scout_opp_id, + cards=cards, + opener_team=opener_team, + opener_user_id=0, + bot=self.bot, + expires_unix=expires_unix, + ) + view.timeout = float(minutes * 60) + view.card_lines = card_lines + + msg = await channel.send(embed=embed, view=view) + view.message = msg + + await interaction.followup.send( + f"Scout opportunity #{scout_opp_id} posted to {channel.mention} with {minutes} min window.", + ephemeral=True, + ) + async def setup(bot): await bot.add_cog(Admins(bot))