paper-dynasty-discord/cogs/players/team_management.py
2025-08-17 08:46:55 -05:00

351 lines
15 KiB
Python

# Team Management Module
# Contains team information and management functionality from the original players.py
from discord.ext import commands
from discord import app_commands
import discord
from typing import Optional
# Import specific utilities needed by this module
import logging
import pygsheets
import requests
from api_calls import db_get, db_post, db_patch, get_team_by_abbrev
from helpers import (
PD_PLAYERS_ROLE_NAME, IMAGES, get_channel, get_team_embed,
get_blank_team_card, get_team_by_owner, get_rosters,
legal_channel, team_summary_embed, SelectView,
is_ephemeral_channel, is_restricted_channel, can_send_message
)
import helpers
from helpers.utils import get_roster_sheet
from helpers.constants import ALL_MLB_TEAMS
logger = logging.getLogger('discord_app')
class TeamManagement(commands.Cog):
"""Team information and management functionality for Paper Dynasty."""
def __init__(self, bot):
self.bot = bot
@app_commands.command(name='team', description='Show team overview and rosters')
@app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME)
async def team_slash_command(self, interaction: discord.Interaction, team_abbrev: Optional[str] = None):
"""Display team overview and rosters."""
await interaction.response.defer()
if team_abbrev:
team = await get_team_by_abbrev(team_abbrev)
if not team:
await interaction.followup.send(f'Could not find team with abbreviation: {team_abbrev}')
return
else:
team = await get_team_by_owner(interaction.user.id)
if not team:
await interaction.followup.send('You don\'t have a team yet! Use `/newteam` to create one.')
return
# Create team summary embed
embed = await team_summary_embed(team, interaction, include_roster=True)
# Add roster information
try:
rosters = get_rosters(team, self.bot)
if rosters:
roster_text = ""
for i, roster in enumerate(rosters):
if roster and roster.get('name'):
card_count = len(roster.get('cards', []))
roster_text += f"**{roster.get('name', 'Unknown')}**: {card_count} players\n"
if roster_text:
embed.add_field(name="Saved Rosters", value=roster_text, inline=False)
except Exception as e:
logger.warning(f"Could not retrieve rosters for team {team.get('abbrev', 'Unknown')}: {e}")
# Add Google Sheet link if available
if team.get('gsheet') and team.get('gsheet') != 'None':
sheet_link = get_roster_sheet(team, allow_embed=True)
if sheet_link:
embed.add_field(name="Team Sheet", value=sheet_link, inline=False)
await interaction.followup.send(embed=embed)
@app_commands.command(name='update-player', description='Update a player\'s card to a specific MLB team')
@app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME)
async def update_player_team(self, interaction: discord.Interaction, player_id: int):
"""Update player team functionality."""
owner_team = await get_team_by_owner(interaction.user.id)
if not owner_team:
await interaction.response.send_message(
'Thank you for offering to help - if you sign up for a team with /newteam I can let you post updates.',
ephemeral=True
)
return
if is_restricted_channel(interaction.channel):
await interaction.response.send_message(
f'Slide on down to #pd-bot-hole to run updates - thanks!',
ephemeral=True
)
return
await interaction.response.defer()
this_player = await db_get('players', object_id=player_id)
if not this_player:
await interaction.edit_original_response(content=f'No clue who that is.')
return
from helpers import get_card_embeds
embed = await get_card_embeds(get_blank_team_card(this_player))
await interaction.edit_original_response(content=None, embed=embed[0])
view = helpers.Confirm(responders=[interaction.user])
if can_send_message(interaction.channel):
question = await interaction.channel.send(
content='Is this the player you want to update?',
view=view
)
else:
question = await interaction.followup.send(
content='Is this the player you want to update?',
view=view
)
await view.wait()
if not view.value:
await question.edit(content='Okay, we\'ll leave it be.', view=None)
return
else:
await question.delete()
view = SelectView([
helpers.SelectUpdatePlayerTeam('AL', this_player, owner_team, self.bot),
helpers.SelectUpdatePlayerTeam('NL', this_player, owner_team, self.bot)
])
if can_send_message(interaction.channel):
await interaction.channel.send(content=None, view=view)
else:
await interaction.followup.send(content=None, view=view)
@commands.hybrid_command(name='branding-pd', help='Update your team branding')
@commands.has_any_role(PD_PLAYERS_ROLE_NAME)
@commands.check(legal_channel)
async def branding_command(self, ctx, team_logo_url: str = None, color: str = None,
short_name: str = None, full_name: str = None, *, branding_updates: Optional[str] = None):
"""Update team branding (logo, color, etc.)."""
team = await get_team_by_owner(ctx.author.id)
if not team:
await ctx.send('You don\'t have a team yet! Use `/newteam` to create one.')
return
# Handle both old interface (individual parameters) and new interface (parsed string)
params = []
updates = {}
# Check if using old interface (individual parameters)
if any([team_logo_url, color, short_name, full_name]):
if team_logo_url:
if team_logo_url.startswith('http') and (team_logo_url.endswith('.png') or
team_logo_url.endswith('.jpg') or
team_logo_url.endswith('.jpeg')):
params.append(('logo', team_logo_url))
updates['logo'] = team_logo_url
else:
await ctx.send('Logo must be a valid URL ending in .png, .jpg, or .jpeg')
return
if color:
clean_color = color.replace('#', '')
if len(clean_color) == 6 and all(c in '0123456789abcdefABCDEF' for c in clean_color):
params.append(('color', clean_color))
updates['color'] = clean_color
else:
await ctx.send('Invalid color format. Use hex format like #FF0000 or FF0000')
return
if short_name:
params.append(('sname', short_name))
updates['short_name'] = short_name
if full_name:
params.append(('lname', full_name))
updates['full_name'] = full_name
elif not branding_updates:
# Show current branding
embed = get_team_embed("Current Team Branding", team)
embed.add_field(name="Team Name", value=f"{team.get('lname', 'Unknown')} ({team.get('abbrev', 'UNK')})", inline=False)
embed.add_field(name="Short Name", value=team.get('sname', 'Unknown'), inline=True)
embed.add_field(name="GM Name", value=team.get('gmname', 'Unknown'), inline=True)
embed.add_field(name="Color", value=f"#{team.get('color', 'a6ce39')}", inline=True)
if team.get('logo'):
embed.add_field(name="Logo URL", value=team.get('logo', 'None'), inline=False)
embed.add_field(
name="How to Update",
value="Individual params: `/branding-pd team_logo_url color short_name full_name`\n"
"Or parsed: `/branding-pd branding_updates:color:#FF0000 logo:url`",
inline=False
)
await ctx.send(embed=embed)
return
else:
# Parse branding updates (new interface)
for update in branding_updates.split():
if ':' in update:
key, value = update.split(':', 1)
if key.lower() == 'color':
# Validate hex color
if value.startswith('#'):
value = value[1:]
if len(value) == 6 and all(c in '0123456789abcdefABCDEF' for c in value):
updates['color'] = value
else:
await ctx.send('Invalid color format. Use hex format like #FF0000 or FF0000')
return
elif key.lower() == 'logo':
if value.startswith('http') and (value.endswith('.png') or value.endswith('.jpg') or value.endswith('.jpeg')):
updates['logo'] = value
else:
await ctx.send('Logo must be a valid URL ending in .png, .jpg, or .jpeg')
return
if not updates:
await ctx.send('No valid updates found. Use `color:#hexcode` or `logo:url` format.')
return
# Apply updates
update_params = [(key, value) for key, value in updates.items()]
updated_team = await db_patch('teams', object_id=team.get('id'), params=update_params)
if updated_team:
embed = get_team_embed("Updated Team Branding", updated_team)
embed.add_field(name="Changes Applied", value="\n".join([f"**{k.title()}**: {v}" for k, v in updates.items()]), inline=False)
await ctx.send(embed=embed)
else:
await ctx.send('Failed to update team branding. Please try again.')
@commands.hybrid_command(name='pullroster', help='Pull saved rosters from your team Sheet',
aliases=['roster', 'rosters', 'pullrosters'])
@app_commands.describe(
specific_roster_num='Enter 1, 2, or 3 to only pull one roster; leave blank to pull all 3',
roster_name='The name of the roster to pull (alternative to roster number)'
)
@commands.has_any_role(PD_PLAYERS_ROLE_NAME)
@commands.check(legal_channel)
async def pull_roster_command(self, ctx, specific_roster_num: Optional[int] = None,
roster_name: Optional[str] = None):
"""Pull and display saved rosters from Google Sheets."""
team = await get_team_by_owner(ctx.author.id)
if not team:
await ctx.send('You don\'t have a team yet! Use `/newteam` to create one.')
return
if not team.get('gsheet') or team.get('gsheet') == 'None':
await ctx.send('You don\'t have a Google Sheet linked yet! Use `/newsheet` to link one.')
return
# Get rosters from sheet
try:
rosters = get_rosters(team, self.bot)
except Exception as e:
logger.error(f"Error getting rosters for {team.get('abbrev', 'Unknown')}: {e}")
await ctx.send('Could not retrieve rosters from your sheet.')
return
if not rosters or not any(rosters):
await ctx.send('Could not retrieve rosters from your sheet.')
return
# Original roster sync functionality (matches players_old.py business logic)
logger.debug(f'roster_data: {rosters}')
# Post roster team/card ids and throw error if db says no
synced_count = 0
for index, roster in enumerate(rosters):
logger.debug(f'index: {index} / roster: {roster}')
if (not specific_roster_num or specific_roster_num == index + 1) and roster:
if roster_name and roster.get('name', '').lower() != roster_name.lower():
continue
try:
this_roster = await db_post(
'rosters',
payload={
'team_id': team.get('id'), 'name': roster.get('name', 'Unknown'),
'roster_num': roster.get('roster_num'), 'card_ids': roster.get('cards', [])
}
)
synced_count += 1
logger.info(f"Synced roster '{roster.get('name', 'Unknown')}' for team {team.get('abbrev', 'Unknown')}")
except Exception as e:
logger.error(f"Failed to sync roster '{roster.get('name', 'Unknown')}': {e}")
if synced_count > 0:
# Import missing function
from helpers.random_content import random_conf_gif
await ctx.send(f"Successfully synced {synced_count} roster(s) to database!\n{random_conf_gif()}")
else:
await ctx.send("No rosters were synced. Check your sheet and try again.")
@commands.hybrid_command(name='ai-teams', help='Get list of AI teams and abbreviations')
@commands.has_any_role(PD_PLAYERS_ROLE_NAME)
@commands.check(legal_channel)
async def ai_teams_command(self, ctx):
"""Display list of AI teams and their abbreviations."""
# Get AI teams from database
ai_teams_query = await db_get('teams', params=[('is_ai', True)])
if not ai_teams_query or ai_teams_query.get('count', 0) == 0:
await ctx.send('No AI teams found.')
return
ai_teams = ai_teams_query.get('teams', [])
if not ai_teams:
await ctx.send('No AI teams found.')
return
# Sort teams alphabetically
ai_teams.sort(key=lambda x: x.get('abbrev', 'ZZZ'))
embed = discord.Embed(
title="AI Teams",
description="Available AI teams for gameplay",
color=0x1f8b4c
)
# Split teams into chunks for multiple fields
chunk_size = 15
for i in range(0, len(ai_teams), chunk_size):
chunk = ai_teams[i:i + chunk_size]
field_name = f"Teams {i+1}-{min(i+chunk_size, len(ai_teams))}"
field_value = ""
for team in chunk:
field_value += f"**{team.get('abbrev', 'UNK')}** - {team.get('sname', 'Unknown')}\n"
embed.add_field(name=field_name, value=field_value, inline=True)
embed.add_field(
name="Usage",
value="Use these abbreviations when playing games against AI teams",
inline=False
)
await ctx.send(embed=embed)
async def setup(bot):
"""Setup function for the TeamManagement cog."""
await bot.add_cog(TeamManagement(bot))