paper-dynasty-discord/helpers/utils.py
Cal Corum c2bbf94925 CLAUDE: Fix get_roster_sheet() to handle both dict and Team objects
Fixed TypeError: 'Team' object is not subscriptable error occurring at the
end of /gauntlet start command when sending completion messages.

The get_roster_sheet() function was using dict syntax (team["gsheet"]) but
was receiving Team objects from gauntlet commands. Updated the function to
handle both dict and Team object formats using isinstance() check.

This follows the same pattern as get_context_user() and owner_only() for
handling multiple input types gracefully.

Fixes: Command 'start' raised an exception: TypeError: 'Team' object is
not subscriptable (reported at end of gauntlet draft completion)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 09:11:37 -06:00

176 lines
5.7 KiB
Python

"""
General Utilities
This module contains standalone utility functions with minimal dependencies,
including timestamp conversion, position abbreviations, and simple helpers.
"""
import datetime
from typing import Optional
import discord
def int_timestamp(datetime_obj: Optional[datetime.datetime] = None):
"""Convert current datetime to integer timestamp."""
if datetime_obj:
return int(datetime.datetime.timestamp(datetime_obj) * 1000)
return int(datetime.datetime.now().timestamp())
def get_pos_abbrev(field_pos: str) -> str:
"""Convert position name to standard abbreviation."""
if field_pos.lower() == 'catcher':
return 'C'
elif field_pos.lower() == 'first baseman':
return '1B'
elif field_pos.lower() == 'second baseman':
return '2B'
elif field_pos.lower() == 'third baseman':
return '3B'
elif field_pos.lower() == 'shortstop':
return 'SS'
elif field_pos.lower() == 'left fielder':
return 'LF'
elif field_pos.lower() == 'center fielder':
return 'CF'
elif field_pos.lower() == 'right fielder':
return 'RF'
else:
return 'P'
def position_name_to_abbrev(position_name):
"""Convert position name to abbreviation (alternate format)."""
if position_name == 'Catcher':
return 'C'
elif position_name == 'First Base':
return '1B'
elif position_name == 'Second Base':
return '2B'
elif position_name == 'Third Base':
return '3B'
elif position_name == 'Shortstop':
return 'SS'
elif position_name == 'Left Field':
return 'LF'
elif position_name == 'Center Field':
return 'CF'
elif position_name == 'Right Field':
return 'RF'
elif position_name == 'Pitcher':
return 'P'
else:
return position_name
def user_has_role(user: discord.User | discord.Member, role_name: str) -> bool:
"""Check if a Discord user has a specific role."""
for x in user.roles:
if x.name == role_name:
return True
return False
def get_roster_sheet_legacy(team):
"""Get legacy roster sheet URL for a team."""
return f'https://docs.google.com/spreadsheets/d/{team.gsheet}/edit'
def get_roster_sheet(team):
"""
Get roster sheet URL for a team.
Handles both dict and Team object formats.
"""
# Handle both dict (team["gsheet"]) and object (team.gsheet) formats
gsheet = team.get("gsheet") if isinstance(team, dict) else getattr(team, "gsheet", None)
return f'https://docs.google.com/spreadsheets/d/{gsheet}/edit'
def get_player_url(team, player) -> str:
"""Generate player URL for SBA or Baseball Reference."""
if team.get('league') == 'SBA':
return f'https://statsplus.net/super-baseball-association/player/{player["player_id"]}'
else:
return f'https://www.baseball-reference.com/players/{player["bbref_id"][0]}/{player["bbref_id"]}.shtml'
def owner_only(ctx) -> bool:
"""Check if user is the bot owner."""
# ID for discord User Cal
owners = [287463767924137994, 1087936030899347516]
# Handle both Context (has .author) and Interaction (has .user) objects
user = getattr(ctx, 'user', None) or getattr(ctx, 'author', None)
if user and user.id in owners:
return True
return False
def get_context_user(ctx):
"""
Get the user from either a Context or Interaction object.
Hybrid commands can receive either commands.Context (from prefix commands)
or discord.Interaction (from slash commands). This helper safely extracts
the user from either type.
Returns:
discord.User or discord.Member: The user who invoked the command
"""
# Handle both Context (has .author) and Interaction (has .user) objects
return getattr(ctx, 'user', None) or getattr(ctx, 'author', None)
def get_cal_user(ctx):
"""Get the Cal user from context. Always returns an object with .mention attribute."""
import logging
logger = logging.getLogger('discord_app')
# Define placeholder user class first
class PlaceholderUser:
def __init__(self):
self.mention = "<@287463767924137994>"
self.id = 287463767924137994
# Handle both Context and Interaction objects
if hasattr(ctx, 'bot'): # Context object
bot = ctx.bot
logger.debug("get_cal_user: Using Context object")
elif hasattr(ctx, 'client'): # Interaction object
bot = ctx.client
logger.debug("get_cal_user: Using Interaction object")
else:
logger.error("get_cal_user: No bot or client found in context")
return PlaceholderUser()
if not bot:
logger.error("get_cal_user: bot is None")
return PlaceholderUser()
logger.debug(f"get_cal_user: Searching among members")
try:
for user in bot.get_all_members():
if user.id == 287463767924137994:
logger.debug("get_cal_user: Found user in get_all_members")
return user
except Exception as e:
logger.error(f"get_cal_user: Exception in get_all_members: {e}")
# Fallback: try to get user directly by ID
logger.debug("get_cal_user: User not found in get_all_members, trying get_user")
try:
user = bot.get_user(287463767924137994)
if user:
logger.debug("get_cal_user: Found user via get_user")
return user
else:
logger.debug("get_cal_user: get_user returned None")
except Exception as e:
logger.error(f"get_cal_user: Exception in get_user: {e}")
# Last resort: return a placeholder user object with mention
logger.debug("get_cal_user: Using placeholder user")
return PlaceholderUser()