123
This commit is contained in:
parent
4336e4174b
commit
af5023477c
@ -1,131 +0,0 @@
|
||||
"""
|
||||
Utility module for handling command contexts in Discord.py.
|
||||
|
||||
This module provides functions for setting up commands with different contexts,
|
||||
allowing them to work in DMs, private channels, guilds, or any combination.
|
||||
"""
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, List, Dict, Any, Union, Callable, Awaitable
|
||||
|
||||
class AppCommandContext(Enum):
|
||||
"""
|
||||
Enum representing the allowed contexts for application commands.
|
||||
|
||||
This enum defines where commands can be used:
|
||||
- GUILD: Commands can only be used in guilds (servers)
|
||||
- GUILD_INSTALL: Commands can only be used in guilds where the app is installed
|
||||
- PRIVATE: Commands can only be used in private contexts (DMs and private channels)
|
||||
- ALL: Commands can be used in both guilds and private contexts
|
||||
"""
|
||||
GUILD = auto()
|
||||
GUILD_INSTALL = auto()
|
||||
PRIVATE = auto()
|
||||
ALL = auto()
|
||||
|
||||
def set_command_context(
|
||||
command: app_commands.Command,
|
||||
context: AppCommandContext = AppCommandContext.ALL
|
||||
) -> app_commands.Command:
|
||||
"""
|
||||
Set the context for a command, determining where it can be used.
|
||||
|
||||
Args:
|
||||
command: The command to modify
|
||||
context: The context to set for the command
|
||||
|
||||
Returns:
|
||||
The modified command
|
||||
"""
|
||||
# Set guild_only and dm_permission based on the context
|
||||
if context == AppCommandContext.GUILD or context == AppCommandContext.GUILD_INSTALL:
|
||||
# Guild-only commands
|
||||
command.guild_only = True
|
||||
# dm_permission is automatically set to False when guild_only is True
|
||||
elif context == AppCommandContext.PRIVATE:
|
||||
# Private-only commands (DMs and private channels)
|
||||
command.guild_only = False
|
||||
# Set dm_permission to True
|
||||
if not hasattr(command, "extras"):
|
||||
command.extras = {}
|
||||
command.extras["dm_permission"] = True
|
||||
# Disable in guilds by setting default_member_permissions to "0"
|
||||
command.extras["default_member_permissions"] = "0"
|
||||
elif context == AppCommandContext.ALL:
|
||||
# Commands that work everywhere
|
||||
command.guild_only = False
|
||||
# Set dm_permission to True
|
||||
if not hasattr(command, "extras"):
|
||||
command.extras = {}
|
||||
command.extras["dm_permission"] = True
|
||||
# Allow default permissions in guilds
|
||||
|
||||
return command
|
||||
|
||||
def create_global_command(
|
||||
bot_tree: app_commands.CommandTree,
|
||||
name: str,
|
||||
description: str,
|
||||
callback: Callable[[discord.Interaction], Awaitable[None]],
|
||||
context: AppCommandContext = AppCommandContext.ALL,
|
||||
**kwargs
|
||||
) -> app_commands.Command:
|
||||
"""
|
||||
Create a command that can be used globally with the specified context.
|
||||
|
||||
Args:
|
||||
bot_tree: The command tree to add the command to
|
||||
name: The name of the command
|
||||
description: The description of the command
|
||||
callback: The function to call when the command is used
|
||||
context: The context to set for the command
|
||||
**kwargs: Additional arguments to pass to the command constructor
|
||||
|
||||
Returns:
|
||||
The created command
|
||||
"""
|
||||
# Create the command
|
||||
command = app_commands.Command(
|
||||
name=name,
|
||||
description=description,
|
||||
callback=callback,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
# Set the context
|
||||
set_command_context(command, context)
|
||||
|
||||
# Add the command to the tree
|
||||
bot_tree.add_command(command)
|
||||
|
||||
return command
|
||||
|
||||
def sync_commands(
|
||||
bot_tree: app_commands.CommandTree,
|
||||
guild_specific: bool = True,
|
||||
global_commands: bool = True
|
||||
) -> Awaitable[List[app_commands.Command]]:
|
||||
"""
|
||||
Sync commands with Discord.
|
||||
|
||||
Args:
|
||||
bot_tree: The command tree to sync
|
||||
guild_specific: Whether to sync guild-specific commands
|
||||
global_commands: Whether to sync global commands
|
||||
|
||||
Returns:
|
||||
A coroutine that resolves to the list of synced commands
|
||||
"""
|
||||
if guild_specific and not global_commands:
|
||||
# Only sync guild-specific commands
|
||||
# This is handled by command_customization.register_all_guild_commands
|
||||
return None
|
||||
elif not guild_specific and global_commands:
|
||||
# Only sync global commands
|
||||
return bot_tree.sync()
|
||||
else:
|
||||
# Sync both guild-specific and global commands
|
||||
# This is the default behavior of bot_tree.sync()
|
||||
return bot_tree.sync()
|
@ -203,26 +203,17 @@ async def register_guild_commands(bot, guild: discord.Guild) -> List[app_command
|
||||
return await syncer.sync_guild_commands(guild)
|
||||
|
||||
|
||||
async def register_all_guild_commands(bot, global_command_names=None) -> Dict[int, List[app_commands.Command]]:
|
||||
async def register_all_guild_commands(bot) -> Dict[int, List[app_commands.Command]]:
|
||||
"""
|
||||
Register commands for all guilds with customizations.
|
||||
Returns a dictionary mapping guild IDs to lists of registered commands.
|
||||
|
||||
Args:
|
||||
bot: The bot instance
|
||||
global_command_names: Optional list of command names that are already registered globally
|
||||
(not used in this implementation, kept for compatibility)
|
||||
"""
|
||||
syncer = GuildCommandSyncer(bot)
|
||||
results = {}
|
||||
|
||||
for guild in bot.guilds:
|
||||
try:
|
||||
# Sync all commands to this guild
|
||||
log.info(f"Syncing commands for guild {guild.id}")
|
||||
results[guild.id] = await syncer.sync_guild_commands(guild)
|
||||
log.info(f"Synced {len(results[guild.id])} commands for guild {guild.id}")
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Failed to sync commands for guild {guild.id}: {e}")
|
||||
results[guild.id] = []
|
||||
|
@ -1,156 +0,0 @@
|
||||
"""
|
||||
Utility module for syncing commands with Discord.
|
||||
|
||||
This module provides functions for syncing both global and guild-specific commands,
|
||||
allowing for more flexibility in command registration and supporting commands in
|
||||
both guild and private contexts (DMs and private channels).
|
||||
"""
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
import logging
|
||||
import command_customization
|
||||
from typing import Dict, List, Optional, Union, Any
|
||||
from command_context import AppCommandContext
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
async def sync_global_and_guild_commands(
|
||||
bot: commands.Bot,
|
||||
global_only: bool = False,
|
||||
guild_only: bool = False,
|
||||
private_command_names: List[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Sync commands with Discord, first to guilds and then globally.
|
||||
|
||||
This function implements the following strategy:
|
||||
1. First, sync guild-specific commands to every guild (as before)
|
||||
2. Then, sync global commands with dm_permission=True and guild_only=False
|
||||
|
||||
Args:
|
||||
bot: The bot instance
|
||||
global_only: If True, only sync global commands
|
||||
guild_only: If True, only sync guild-specific commands
|
||||
private_command_names: List of command names that should work in private contexts
|
||||
|
||||
Returns:
|
||||
A dictionary containing the results of the sync operations
|
||||
"""
|
||||
results = {
|
||||
"global": [],
|
||||
"guild": {}
|
||||
}
|
||||
|
||||
try:
|
||||
# Get all commands from the command tree
|
||||
all_commands = bot.tree.get_commands()
|
||||
command_names = [cmd.name for cmd in all_commands]
|
||||
log.info(f"Found {len(all_commands)} commands in tree: {', '.join(command_names)}")
|
||||
|
||||
# If private_command_names is not provided, use an empty list
|
||||
if private_command_names is None:
|
||||
private_command_names = []
|
||||
|
||||
# First, sync guild-specific commands if requested
|
||||
if not global_only:
|
||||
log.info("Syncing guild-specific commands to all guilds...")
|
||||
# Use the command_customization module to handle guild-specific commands
|
||||
guild_syncs = await command_customization.register_all_guild_commands(bot)
|
||||
results["guild"] = guild_syncs
|
||||
|
||||
total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values())
|
||||
log.info(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} customized commands")
|
||||
|
||||
# Now, prepare global commands with appropriate context settings
|
||||
if not guild_only and private_command_names:
|
||||
log.info("Preparing global commands for private contexts...")
|
||||
|
||||
# Clear the command tree for global commands
|
||||
bot.tree.clear_commands()
|
||||
|
||||
# Add back only the commands that should work in private contexts
|
||||
from command_context import AppCommandContext, set_command_context
|
||||
for cmd_name in private_command_names:
|
||||
# Find the command in the original list
|
||||
cmd = next((c for c in all_commands if c.name == cmd_name), None)
|
||||
if cmd:
|
||||
log.info(f"Setting command {cmd_name} to work in private contexts")
|
||||
# Set the command to work in private contexts
|
||||
set_command_context(cmd, AppCommandContext.ALL)
|
||||
# Add the command back to the tree
|
||||
bot.tree.add_command(cmd)
|
||||
else:
|
||||
log.warning(f"Command {cmd_name} not found in command tree")
|
||||
|
||||
# Sync the global commands
|
||||
log.info("Syncing global commands for private contexts...")
|
||||
global_synced = await bot.tree.sync()
|
||||
results["global"] = global_synced
|
||||
|
||||
# List the synced commands
|
||||
global_command_names = [cmd.name for cmd in global_synced]
|
||||
log.info(f"Synced {len(global_synced)} global command(s) for private contexts: {', '.join(global_command_names)}")
|
||||
|
||||
# Restore the original command tree
|
||||
bot.tree.clear_commands()
|
||||
for cmd in all_commands:
|
||||
bot.tree.add_command(cmd)
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Failed to sync commands: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
return results
|
||||
|
||||
def register_global_command(
|
||||
bot: commands.Bot,
|
||||
name: str,
|
||||
description: str,
|
||||
callback: Any,
|
||||
context: AppCommandContext = AppCommandContext.ALL,
|
||||
**kwargs
|
||||
) -> app_commands.Command:
|
||||
"""
|
||||
Register a global command with the specified context.
|
||||
|
||||
Args:
|
||||
bot: The bot instance
|
||||
name: The name of the command
|
||||
description: The description of the command
|
||||
callback: The function to call when the command is used
|
||||
context: The context to set for the command
|
||||
**kwargs: Additional arguments to pass to the command constructor
|
||||
|
||||
Returns:
|
||||
The created command
|
||||
"""
|
||||
from command_context import create_global_command
|
||||
|
||||
return create_global_command(
|
||||
bot.tree,
|
||||
name=name,
|
||||
description=description,
|
||||
callback=callback,
|
||||
context=context,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def set_command_contexts(bot: commands.Bot, contexts: Dict[str, AppCommandContext]) -> None:
|
||||
"""
|
||||
Set the contexts for multiple commands.
|
||||
|
||||
Args:
|
||||
bot: The bot instance
|
||||
contexts: A dictionary mapping command names to contexts
|
||||
"""
|
||||
from command_context import set_command_context
|
||||
|
||||
for cmd in bot.tree.get_commands():
|
||||
if cmd.name in contexts:
|
||||
set_command_context(cmd, contexts[cmd.name])
|
||||
log.info(f"Set context for command '{cmd.name}' to {contexts[cmd.name]}")
|
@ -1,97 +0,0 @@
|
||||
"""
|
||||
Example module demonstrating how to create global commands that work in DMs and private channels.
|
||||
|
||||
This module shows how to use the AppCommandContext system to create commands
|
||||
that can be used in both guild and private contexts.
|
||||
"""
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from command_context import AppCommandContext, create_global_command
|
||||
from typing import Optional
|
||||
|
||||
class GlobalCommandsCog(commands.Cog):
|
||||
"""A cog that demonstrates global commands that work in DMs and private channels."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
print("GlobalCommandsCog initialized!")
|
||||
|
||||
# Register global commands
|
||||
self.register_global_commands()
|
||||
|
||||
def register_global_commands(self):
|
||||
"""Register global commands that work in DMs and private channels."""
|
||||
|
||||
# Create a help command that works in DMs and private channels
|
||||
create_global_command(
|
||||
self.bot.tree,
|
||||
name="dmhelp",
|
||||
description="Get help with bot commands (works in DMs)",
|
||||
callback=self.dm_help_callback,
|
||||
context=AppCommandContext.ALL
|
||||
)
|
||||
|
||||
# Create a ping command that works in DMs and private channels
|
||||
create_global_command(
|
||||
self.bot.tree,
|
||||
name="dmping",
|
||||
description="Check if the bot is responsive (works in DMs)",
|
||||
callback=self.dm_ping_callback,
|
||||
context=AppCommandContext.ALL
|
||||
)
|
||||
|
||||
# Create a command that only works in private contexts
|
||||
create_global_command(
|
||||
self.bot.tree,
|
||||
name="privateonly",
|
||||
description="This command only works in DMs and private channels",
|
||||
callback=self.private_only_callback,
|
||||
context=AppCommandContext.PRIVATE
|
||||
)
|
||||
|
||||
print("GlobalCommandsCog: Registered global commands")
|
||||
|
||||
async def dm_help_callback(self, interaction: discord.Interaction):
|
||||
"""Callback for the /dmhelp command."""
|
||||
is_dm = isinstance(interaction.channel, discord.DMChannel)
|
||||
is_private = not interaction.guild
|
||||
|
||||
help_text = (
|
||||
"# Bot Help\n\n"
|
||||
"This help command works in both DMs and servers!\n\n"
|
||||
f"**Current context:** {'DM' if is_dm else 'Private Channel' if is_private else 'Server'}\n\n"
|
||||
"## Available Commands\n"
|
||||
"- `/dmhelp` - This help command\n"
|
||||
"- `/dmping` - Check if the bot is responsive\n"
|
||||
"- `/privateonly` - Only works in DMs and private channels\n"
|
||||
)
|
||||
|
||||
await interaction.response.send_message(help_text, ephemeral=True)
|
||||
|
||||
async def dm_ping_callback(self, interaction: discord.Interaction):
|
||||
"""Callback for the /dmping command."""
|
||||
is_dm = isinstance(interaction.channel, discord.DMChannel)
|
||||
is_private = not interaction.guild
|
||||
|
||||
await interaction.response.send_message(
|
||||
f"🏓 Pong! Bot is responsive.\n"
|
||||
f"**Current context:** {'DM' if is_dm else 'Private Channel' if is_private else 'Server'}",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
async def private_only_callback(self, interaction: discord.Interaction):
|
||||
"""Callback for the /privateonly command."""
|
||||
is_dm = isinstance(interaction.channel, discord.DMChannel)
|
||||
|
||||
await interaction.response.send_message(
|
||||
f"This command only works in private contexts.\n"
|
||||
f"**Current context:** {'DM' if is_dm else 'Private Channel'}",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
"""Add the GlobalCommandsCog to the bot."""
|
||||
await bot.add_cog(GlobalCommandsCog(bot))
|
||||
print("GlobalCommandsCog setup complete.")
|
34
gurt_bot.py
34
gurt_bot.py
@ -28,42 +28,8 @@ async def on_ready():
|
||||
# Sync commands
|
||||
try:
|
||||
print("Starting command sync process...")
|
||||
|
||||
# Import our command sync utilities
|
||||
try:
|
||||
import command_sync_utils
|
||||
|
||||
# Define commands that should work in private channels and DMs
|
||||
private_commands = [
|
||||
# Add command names that should work in private contexts here
|
||||
"gurtmood", # Allow mood command in DMs
|
||||
]
|
||||
|
||||
# First sync guild-specific commands to all guilds, then sync global commands for private contexts
|
||||
print("First syncing commands to all guilds, then syncing global commands for private contexts...")
|
||||
sync_results = await command_sync_utils.sync_global_and_guild_commands(
|
||||
bot,
|
||||
private_command_names=private_commands
|
||||
)
|
||||
|
||||
# Report results
|
||||
global_syncs = sync_results["global"]
|
||||
guild_syncs = sync_results["guild"]
|
||||
|
||||
# Report guild syncs
|
||||
if guild_syncs:
|
||||
total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values())
|
||||
print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} commands")
|
||||
|
||||
# Report global syncs
|
||||
print(f"Synced {len(global_syncs)} global command(s) for private contexts")
|
||||
|
||||
except ImportError:
|
||||
# Fall back to regular sync if the utility modules aren't available
|
||||
print("Command sync utilities not available, falling back to regular sync...")
|
||||
synced = await bot.tree.sync()
|
||||
print(f"Synced {len(synced)} command(s)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to sync commands: {e}")
|
||||
import traceback
|
||||
|
52
main.py
52
main.py
@ -230,57 +230,19 @@ async def on_ready():
|
||||
commands_before = [cmd.name for cmd in bot.tree.get_commands()]
|
||||
print(f"Commands before sync: {commands_before}")
|
||||
|
||||
# Import our new command sync utilities
|
||||
import command_sync_utils
|
||||
# Skip global command sync to avoid duplication
|
||||
print("Skipping global command sync to avoid command duplication...")
|
||||
|
||||
# First sync guild-specific commands to all guilds, then sync global commands for private contexts
|
||||
print("First syncing commands to all guilds, then syncing global commands for private contexts...")
|
||||
sync_results = await command_sync_utils.sync_global_and_guild_commands(
|
||||
bot,
|
||||
private_command_names=private_commands
|
||||
)
|
||||
# Only sync guild-specific commands with customizations
|
||||
print("Syncing guild-specific command customizations...")
|
||||
guild_syncs = await command_customization.register_all_guild_commands(bot)
|
||||
|
||||
# Report results
|
||||
global_syncs = sync_results["global"]
|
||||
guild_syncs = sync_results["guild"]
|
||||
|
||||
# Report guild syncs
|
||||
if guild_syncs:
|
||||
total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values())
|
||||
print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} commands")
|
||||
|
||||
# Report global syncs
|
||||
print(f"Synced {len(global_syncs)} global command(s) for private contexts")
|
||||
print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} customized commands")
|
||||
|
||||
# List commands after sync
|
||||
commands_after = [cmd.name for cmd in bot.tree.get_commands()]
|
||||
print(f"Commands registered in command tree: {', '.join(commands_after)}")
|
||||
|
||||
# Define commands that should work in private channels and DMs
|
||||
private_commands = [
|
||||
# Add command names that should work in private contexts here
|
||||
"dmhelp",
|
||||
"dmping",
|
||||
"privateonly",
|
||||
]
|
||||
|
||||
# Load the example global commands cog
|
||||
try:
|
||||
await bot.load_extension("example_global_commands")
|
||||
print("Loaded example_global_commands cog")
|
||||
except Exception as e:
|
||||
print(f"Failed to load example_global_commands cog: {e}")
|
||||
|
||||
# Sync both global and guild-specific commands, avoiding duplicates
|
||||
print("Re-syncing commands with private context settings...")
|
||||
sync_results = await command_sync_utils.sync_global_and_guild_commands(
|
||||
bot,
|
||||
private_command_names=private_commands
|
||||
)
|
||||
|
||||
# Report results
|
||||
global_syncs = sync_results["global"]
|
||||
print(f"Re-synced {len(global_syncs)} global command(s) with private context settings")
|
||||
print(f"Commands registered in command tree: {commands_after}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to sync commands: {e}")
|
||||
|
@ -28,42 +28,8 @@ async def on_ready():
|
||||
# Sync commands
|
||||
try:
|
||||
print("Starting command sync process...")
|
||||
|
||||
# Import our command sync utilities
|
||||
try:
|
||||
import command_sync_utils
|
||||
|
||||
# Define commands that should work in private channels and DMs
|
||||
private_commands = [
|
||||
# Add command names that should work in private contexts here
|
||||
"wheatleymemory", # Allow memory command in DMs
|
||||
]
|
||||
|
||||
# First sync guild-specific commands to all guilds, then sync global commands for private contexts
|
||||
print("First syncing commands to all guilds, then syncing global commands for private contexts...")
|
||||
sync_results = await command_sync_utils.sync_global_and_guild_commands(
|
||||
bot,
|
||||
private_command_names=private_commands
|
||||
)
|
||||
|
||||
# Report results
|
||||
global_syncs = sync_results["global"]
|
||||
guild_syncs = sync_results["guild"]
|
||||
|
||||
# Report guild syncs
|
||||
if guild_syncs:
|
||||
total_guild_syncs = sum(len(cmds) for cmds in guild_syncs.values())
|
||||
print(f"Synced commands for {len(guild_syncs)} guilds with a total of {total_guild_syncs} commands")
|
||||
|
||||
# Report global syncs
|
||||
print(f"Synced {len(global_syncs)} global command(s) for private contexts")
|
||||
|
||||
except ImportError:
|
||||
# Fall back to regular sync if the utility modules aren't available
|
||||
print("Command sync utilities not available, falling back to regular sync...")
|
||||
synced = await bot.tree.sync()
|
||||
print(f"Synced {len(synced)} command(s)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to sync commands: {e}")
|
||||
import traceback
|
||||
|
Loading…
x
Reference in New Issue
Block a user