# cmd_discord.py import discord from discord.ext import commands from discord import app_commands from typing import Optional from cmd_common import common_commands as cc from modules.permissions import has_permission from modules.utility import handle_help_command import globals def setup(bot): """ Attach commands to the Discord bot, store references to db/log. """ config_data = globals.load_config_file() @bot.command() @commands.is_owner() async def sync_commands(ctx): """ Trigger manual command sync. OWNER ONLY """ if commands.is_owner(): primary_guild_int = int(config_data["discord_guilds"][0]) primary_guild = discord.Object(id=primary_guild_int) await ctx.send(f"Initiating slash sync to Discord Guild '{primary_guild_int}' ...") try: await bot.tree.sync(guild=primary_guild) reply = "... Commands synced!" except Exception as e: reply = f"... Commands failed to sync! Error message:\n{e}" globals.log(f"'sync_commands' failed to sync command tree\n{e}", "ERROR") else: reply = "You're not the registered owner of me!" await ctx.send(reply) @bot.hybrid_command(name="available", description="List commands available to you") async def available(ctx): available_cmds = [] for command in bot.commands: try: # This will return True if the command's checks pass for the given context. if await command.can_run(ctx): available_cmds.append(command.name) except commands.CheckFailure: # The command's checks did not pass. pass except Exception as e: # In case some commands fail unexpectedly during checks. globals.log(f"Error checking command {command.name}: {e}", "ERROR") if available_cmds: await ctx.send("Available commands: " + ", ".join(sorted(available_cmds))) else: await ctx.send("No commands are available to you at this time.") @bot.hybrid_command(name="help", description="Get information about commands") @app_commands.describe( command="The command to get help info about. Defaults to 'help'" ) async def cmd_help(ctx: commands.Context, *, command: str = ""): """ e.g. !help !help quote """ result = await handle_help_command(ctx, command, bot, is_discord=True) await ctx.send(result) @bot.hybrid_command(name="greet", description="Make me greet you") async def cmd_greet(ctx): result = cc.greet(ctx.author.display_name, "Discord") await ctx.send(result) @bot.hybrid_command(name="ping", description="Check my uptime") async def cmd_ping(ctx): result = cc.ping() # Get heartbeat latency. Discord only latency = round(float(bot.latency) * 1000) result += f" (*latency: {latency}ms*)" await ctx.send(result) @bot.hybrid_command(name="howl", description="Attempt a howl") async def cmd_howl(ctx): response = cc.handle_howl_command(ctx) await ctx.send(response) # @monitor_cmds(bot.log) # @bot.hybrid_command(name="reload", description="Dynamically reload commands (INOP)") # async def cmd_reload(ctx): # """ Dynamically reloads Discord commands. """ # try: # import cmd_discord # import importlib # importlib.reload(cmd_discord) # cmd_discord.setup(bot) # await ctx.send("Commands reloaded on first try!") # except Exception as e: # try: # await bot.reload_extension("cmd_discord") # await ctx.send("Commands reloaded on second try!") # except Exception as e: # try: # await bot.unload_extension("cmd_discord") # await bot.load_extension("cmd_discord") # await ctx.send("Commands reloaded on third try!") # except Exception as e: # await ctx.send(f"Fallback reload failed: {e}") @bot.hybrid_command(name="hi", description="Dev command for testing permissions system") async def cmd_hi(ctx): user_id = str(ctx.author.id) user_roles = [role.name.lower() for role in ctx.author.roles] # Normalize to lowercase if not has_permission("hi", user_id, user_roles, "discord"): await ctx.send("You don't have permission to use this command.") return await ctx.send("Hello there!") # @monitor_cmds(bot.log) # @bot.hybrid_command(name="quote", description="Interact with the quotes system") # async def cmd_quote(ctx, query: str = None): # """ # !quote # !quote add # !quote remove # !quote # """ # if not bot.db_conn: # return await ctx.send("Database is unavailable, sorry.") # args = query.split() # # Send to our shared logic # await cc.handle_quote_command( # db_conn=bot.db_conn, # log_func=bot.log, # is_discord=True, # ctx=ctx, # args=list(args), # get_twitch_game_for_channel=None # None for Discord # ) # @monitor_cmds(bot.log) # @bot.hybrid_group(name="quote", description="Interact with the quotes system", with_app_command=True) # async def cmd_quote(ctx, query: str = None): # """ # Usage: # !quote -> get a random quote # !quote -> get a specific quote by number # As a slash command, leave the query blank for a random quote or type a number. # """ # if not bot.db_conn: # return await ctx.send("Database is unavailable, sorry.") # # Only process the base command if no subcommand was invoked. # # When query is provided, split it into arguments (for a specific quote lookup). # args = query.split() if query else [] # await cc.handle_quote_command( # db_conn=bot.db_conn, # log_func=bot.log, # is_discord=True, # ctx=ctx, # args=args, # get_twitch_game_for_channel=None # None for Discord # ) @bot.command(name="quote") async def cmd_quote(ctx, *, arg_str: str = ""): """ Handles the !quote command with multiple subcommands. Usage: - `!quote` -> Retrieves a random (non-removed) quote. - `!quote ` -> Retrieves the specific quote by ID. - `!quote add ` -> Adds a new quote and replies with its quote number. - `!quote remove ` -> Removes the specified quote. - `!quote restore ` -> Restores a previously removed quote. - `!quote info ` -> Displays stored information about the quote (as an embed on Discord). - `!quote search [keywords]` -> Searches for the best matching quote based on the provided keywords. - `!quote last/latest/newest` -> Retrieves the latest (most recent) non-removed quote. """ if not globals.init_db_conn: await ctx.send("Database is unavailable, sorry.") return # Parse the arguments from the message text args = arg_str.split() if arg_str else [] globals.log(f"'quote' command initiated with arguments: {args}", "DEBUG") result = await cc.handle_quote_command( db_conn=globals.init_db_conn, is_discord=True, ctx=ctx, args=args, get_twitch_game_for_channel=None ) globals.log(f"'quote' result: {result}", "DEBUG") # If the result is a discord.Embed, send it as an embed; otherwise, send plain text. if hasattr(result, "to_dict"): await ctx.send(embed=result) else: await ctx.send(result) ###################### # The following log entry must be last in the file to verify commands loading as they should ###################### # Debug: Print that commands are being registered try: command_names = [cmd.name for cmd in bot.commands] # Extract command names globals.log(f"Registering commands for Discord: {command_names}", "DEBUG") except Exception as e: globals.log(f"An error occured while printing registered commands for Discord: {e}", "WARNING")