""" 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 a datetime to an integer millisecond timestamp. If no argument is given, uses the current time. """ if datetime_obj is None: datetime_obj = datetime.datetime.now() return int(datetime.datetime.timestamp(datetime_obj) * 1000) def midnight_timestamp() -> int: """Return today's midnight (00:00:00) as an integer millisecond timestamp.""" now = datetime.datetime.now() midnight = datetime.datetime(now.year, now.month, now.day, 0, 0, 0) return int_timestamp(midnight) 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 = [258104532423147520] # 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()