The owner_only() function was accessing ctx.author.id, which caused an AttributeError when called with Interaction objects from slash commands (which use .user instead of .author). Updated both utils.py and helpers/utils.py to handle both Context and Interaction objects by checking for .user first, then falling back to .author for backward compatibility with traditional commands. Fixes: Command 'reset-image' raised an exception: AttributeError: 'Interaction' object has no attribute 'author' 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
152 lines
4.8 KiB
Python
152 lines
4.8 KiB
Python
"""
|
|
General Utilities
|
|
|
|
This module contains standalone utility functions with minimal dependencies,
|
|
including timestamp conversion, position abbreviations, and simple helpers.
|
|
"""
|
|
import datetime
|
|
import discord
|
|
|
|
|
|
def int_timestamp():
|
|
"""Convert current datetime to integer timestamp."""
|
|
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."""
|
|
return f'https://docs.google.com/spreadsheets/d/{team["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_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() |