123
This commit is contained in:
parent
fb2278e986
commit
4c2cbc636f
@ -231,6 +231,22 @@ except ImportError as e:
|
||||
log.error(f"Could not import dashboard API endpoints: {e}")
|
||||
log.error("Dashboard API endpoints will not be available")
|
||||
|
||||
# Import command customization models and endpoints
|
||||
try:
|
||||
# Try relative import first
|
||||
try:
|
||||
from .command_customization_endpoints import router as customization_router
|
||||
except ImportError:
|
||||
# Fall back to absolute import
|
||||
from command_customization_endpoints import router as customization_router
|
||||
|
||||
# Add the command customization router to the dashboard API app
|
||||
dashboard_api_app.include_router(customization_router, prefix="/commands", tags=["Command Customization"])
|
||||
log.info("Command customization endpoints loaded successfully")
|
||||
except ImportError as e:
|
||||
log.error(f"Could not import command customization endpoints: {e}")
|
||||
log.error("Command customization endpoints will not be available")
|
||||
|
||||
# Mount the API apps at their respective paths
|
||||
app.mount("/api", api_app)
|
||||
app.mount("/discordapi", discordapi_app)
|
||||
|
324
api_service/command_customization_endpoints.py
Normal file
324
api_service/command_customization_endpoints.py
Normal file
@ -0,0 +1,324 @@
|
||||
"""
|
||||
Command customization API endpoints for the bot dashboard.
|
||||
These endpoints provide functionality for customizing command names and groups.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Import the dependencies from api_server.py
|
||||
try:
|
||||
# Try relative import first
|
||||
from .api_server import (
|
||||
get_dashboard_user,
|
||||
verify_dashboard_guild_admin,
|
||||
CommandCustomizationResponse,
|
||||
CommandCustomizationUpdate,
|
||||
GroupCustomizationUpdate,
|
||||
CommandAliasAdd,
|
||||
CommandAliasRemove
|
||||
)
|
||||
except ImportError:
|
||||
# Fall back to absolute import
|
||||
from api_server import (
|
||||
get_dashboard_user,
|
||||
verify_dashboard_guild_admin,
|
||||
CommandCustomizationResponse,
|
||||
CommandCustomizationUpdate,
|
||||
GroupCustomizationUpdate,
|
||||
CommandAliasAdd,
|
||||
CommandAliasRemove
|
||||
)
|
||||
|
||||
# Import settings_manager for database access
|
||||
try:
|
||||
from discordbot import settings_manager
|
||||
except ImportError:
|
||||
# Try relative import
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
||||
from discordbot import settings_manager
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Create the router
|
||||
router = APIRouter()
|
||||
|
||||
# --- Command Customization Endpoints ---
|
||||
|
||||
@router.get("/customizations/{guild_id}", response_model=CommandCustomizationResponse)
|
||||
async def get_command_customizations(
|
||||
guild_id: int,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Get all command customizations for a guild."""
|
||||
try:
|
||||
# Check if settings_manager is available
|
||||
if not settings_manager or not settings_manager.pg_pool:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Get command customizations
|
||||
command_customizations = await settings_manager.get_all_command_customizations(guild_id)
|
||||
if command_customizations is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get command customizations"
|
||||
)
|
||||
|
||||
# Get group customizations
|
||||
group_customizations = await settings_manager.get_all_group_customizations(guild_id)
|
||||
if group_customizations is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get group customizations"
|
||||
)
|
||||
|
||||
# Get command aliases
|
||||
command_aliases = await settings_manager.get_all_command_aliases(guild_id)
|
||||
if command_aliases is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get command aliases"
|
||||
)
|
||||
|
||||
return CommandCustomizationResponse(
|
||||
command_customizations=command_customizations,
|
||||
group_customizations=group_customizations,
|
||||
command_aliases=command_aliases
|
||||
)
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error getting command customizations for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error getting command customizations: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/customizations/{guild_id}/commands", status_code=status.HTTP_200_OK)
|
||||
async def set_command_customization(
|
||||
guild_id: int,
|
||||
customization: CommandCustomizationUpdate,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Set a custom name for a command in a guild."""
|
||||
try:
|
||||
# Check if settings_manager is available
|
||||
if not settings_manager or not settings_manager.pg_pool:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Validate custom name format if provided
|
||||
if customization.custom_name is not None:
|
||||
if not customization.custom_name.islower() or not customization.custom_name.replace('_', '').isalnum():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Custom command names must be lowercase and contain only letters, numbers, and underscores"
|
||||
)
|
||||
|
||||
if len(customization.custom_name) < 1 or len(customization.custom_name) > 32:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Custom command names must be between 1 and 32 characters long"
|
||||
)
|
||||
|
||||
# Set the custom command name
|
||||
success = await settings_manager.set_custom_command_name(
|
||||
guild_id,
|
||||
customization.command_name,
|
||||
customization.custom_name
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to set custom command name"
|
||||
)
|
||||
|
||||
return {"message": "Command customization updated successfully"}
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error setting command customization for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error setting command customization: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/customizations/{guild_id}/groups", status_code=status.HTTP_200_OK)
|
||||
async def set_group_customization(
|
||||
guild_id: int,
|
||||
customization: GroupCustomizationUpdate,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Set a custom name for a command group in a guild."""
|
||||
try:
|
||||
# Check if settings_manager is available
|
||||
if not settings_manager or not settings_manager.pg_pool:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Validate custom name format if provided
|
||||
if customization.custom_name is not None:
|
||||
if not customization.custom_name.islower() or not customization.custom_name.replace('_', '').isalnum():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Custom group names must be lowercase and contain only letters, numbers, and underscores"
|
||||
)
|
||||
|
||||
if len(customization.custom_name) < 1 or len(customization.custom_name) > 32:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Custom group names must be between 1 and 32 characters long"
|
||||
)
|
||||
|
||||
# Set the custom group name
|
||||
success = await settings_manager.set_custom_group_name(
|
||||
guild_id,
|
||||
customization.group_name,
|
||||
customization.custom_name
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to set custom group name"
|
||||
)
|
||||
|
||||
return {"message": "Group customization updated successfully"}
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error setting group customization for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error setting group customization: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/customizations/{guild_id}/aliases", status_code=status.HTTP_200_OK)
|
||||
async def add_command_alias(
|
||||
guild_id: int,
|
||||
alias: CommandAliasAdd,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Add an alias for a command in a guild."""
|
||||
try:
|
||||
# Check if settings_manager is available
|
||||
if not settings_manager or not settings_manager.pg_pool:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Validate alias format
|
||||
if not alias.alias_name.islower() or not alias.alias_name.replace('_', '').isalnum():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Aliases must be lowercase and contain only letters, numbers, and underscores"
|
||||
)
|
||||
|
||||
if len(alias.alias_name) < 1 or len(alias.alias_name) > 32:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Aliases must be between 1 and 32 characters long"
|
||||
)
|
||||
|
||||
# Add the command alias
|
||||
success = await settings_manager.add_command_alias(
|
||||
guild_id,
|
||||
alias.command_name,
|
||||
alias.alias_name
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to add command alias"
|
||||
)
|
||||
|
||||
return {"message": "Command alias added successfully"}
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error adding command alias for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error adding command alias: {str(e)}"
|
||||
)
|
||||
|
||||
@router.delete("/customizations/{guild_id}/aliases", status_code=status.HTTP_200_OK)
|
||||
async def remove_command_alias(
|
||||
guild_id: int,
|
||||
alias: CommandAliasRemove,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Remove an alias for a command in a guild."""
|
||||
try:
|
||||
# Check if settings_manager is available
|
||||
if not settings_manager or not settings_manager.pg_pool:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Remove the command alias
|
||||
success = await settings_manager.remove_command_alias(
|
||||
guild_id,
|
||||
alias.command_name,
|
||||
alias.alias_name
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to remove command alias"
|
||||
)
|
||||
|
||||
return {"message": "Command alias removed successfully"}
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error removing command alias for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error removing command alias: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/customizations/{guild_id}/sync", status_code=status.HTTP_200_OK)
|
||||
async def sync_guild_commands(
|
||||
guild_id: int,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Sync commands for a guild to apply customizations."""
|
||||
try:
|
||||
# This endpoint would trigger a command sync for the guild
|
||||
# In a real implementation, this would communicate with the bot to sync commands
|
||||
# For now, we'll just return a success message
|
||||
return {"message": "Command sync requested. This may take a moment to complete."}
|
||||
except Exception as e:
|
||||
log.error(f"Error syncing commands for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error syncing commands: {str(e)}"
|
||||
)
|
@ -2,6 +2,7 @@ import discord
|
||||
from discord.ext import commands
|
||||
import logging
|
||||
from discordbot import settings_manager # Assuming settings_manager is accessible
|
||||
from discordbot import command_customization # Import command customization utilities
|
||||
from typing import Optional
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -385,11 +386,18 @@ class SettingsCog(commands.Cog, name="Settings"):
|
||||
guild = ctx.guild
|
||||
await ctx.send("Syncing commands with Discord... This may take a moment.")
|
||||
|
||||
# Sync commands for this guild specifically
|
||||
synced = await self.bot.tree.sync(guild=guild)
|
||||
# Use the command_customization module to sync commands with customizations
|
||||
try:
|
||||
synced = await command_customization.register_guild_commands(self.bot, guild)
|
||||
|
||||
await ctx.send(f"Successfully synced {len(synced)} commands for this server.")
|
||||
log.info(f"Commands synced for guild {guild.id} by {ctx.author.name}")
|
||||
await ctx.send(f"Successfully synced {len(synced)} commands for this server with customizations.")
|
||||
log.info(f"Commands synced with customizations for guild {guild.id} by {ctx.author.name}")
|
||||
except Exception as e:
|
||||
log.error(f"Failed to sync commands with customizations: {e}")
|
||||
# Fall back to regular sync if customization sync fails
|
||||
synced = await self.bot.tree.sync(guild=guild)
|
||||
await ctx.send(f"Failed to apply customizations, but synced {len(synced)} commands for this server.")
|
||||
log.info(f"Commands synced (without customizations) for guild {guild.id} by {ctx.author.name}")
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to sync commands: {str(e)}")
|
||||
log.error(f"Failed to sync commands for guild {ctx.guild.id}: {e}")
|
||||
|
@ -7,7 +7,7 @@ from discord import app_commands
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Tuple, Any, Callable, Awaitable
|
||||
import asyncio
|
||||
from . import settings_manager
|
||||
from discordbot import settings_manager
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -20,7 +20,7 @@ class GuildCommandTransformer(app_commands.Transformer):
|
||||
"""Transform the command name based on guild settings."""
|
||||
if not interaction.guild:
|
||||
return value # No customization in DMs
|
||||
|
||||
|
||||
guild_id = interaction.guild.id
|
||||
custom_name = await settings_manager.get_custom_command_name(guild_id, value)
|
||||
return custom_name if custom_name else value
|
||||
@ -34,7 +34,7 @@ class GuildCommandSyncer:
|
||||
self.bot = bot
|
||||
self._command_cache = {} # Cache of original commands
|
||||
self._customized_commands = {} # Guild ID -> {original_name: custom_command}
|
||||
|
||||
|
||||
async def load_guild_customizations(self, guild_id: int) -> Dict[str, str]:
|
||||
"""
|
||||
Load command customizations for a specific guild.
|
||||
@ -42,16 +42,16 @@ class GuildCommandSyncer:
|
||||
"""
|
||||
cmd_customizations = await settings_manager.get_all_command_customizations(guild_id)
|
||||
group_customizations = await settings_manager.get_all_group_customizations(guild_id)
|
||||
|
||||
|
||||
if cmd_customizations is None or group_customizations is None:
|
||||
log.error(f"Failed to load command customizations for guild {guild_id}")
|
||||
return {}
|
||||
|
||||
|
||||
# Combine command and group customizations
|
||||
customizations = {**cmd_customizations, **group_customizations}
|
||||
log.info(f"Loaded {len(customizations)} command customizations for guild {guild_id}")
|
||||
return customizations
|
||||
|
||||
|
||||
async def prepare_guild_commands(self, guild_id: int) -> List[app_commands.Command]:
|
||||
"""
|
||||
Prepare guild-specific commands with customized names.
|
||||
@ -59,16 +59,16 @@ class GuildCommandSyncer:
|
||||
"""
|
||||
# Get all global commands
|
||||
global_commands = self.bot.tree.get_commands()
|
||||
|
||||
|
||||
# Cache original commands if not already cached
|
||||
if not self._command_cache:
|
||||
self._command_cache = {cmd.name: cmd for cmd in global_commands}
|
||||
|
||||
|
||||
# Load customizations for this guild
|
||||
customizations = await self.load_guild_customizations(guild_id)
|
||||
if not customizations:
|
||||
return global_commands # No customizations, use global commands
|
||||
|
||||
|
||||
# Create guild-specific commands with custom names
|
||||
guild_commands = []
|
||||
for cmd in global_commands:
|
||||
@ -80,15 +80,15 @@ class GuildCommandSyncer:
|
||||
else:
|
||||
# Use the original command
|
||||
guild_commands.append(cmd)
|
||||
|
||||
|
||||
# Store customized commands for this guild
|
||||
self._customized_commands[guild_id] = {
|
||||
cmd.name: custom_cmd for cmd, custom_cmd in zip(global_commands, guild_commands)
|
||||
if cmd.name in customizations
|
||||
}
|
||||
|
||||
|
||||
return guild_commands
|
||||
|
||||
|
||||
def _create_custom_command(self, original_cmd: app_commands.Command, custom_name: str) -> app_commands.Command:
|
||||
"""
|
||||
Create a copy of a command with a custom name.
|
||||
@ -101,13 +101,13 @@ class GuildCommandSyncer:
|
||||
description=original_cmd.description,
|
||||
callback=original_cmd.callback
|
||||
)
|
||||
|
||||
|
||||
# Copy options, if any
|
||||
if hasattr(original_cmd, 'options'):
|
||||
custom_cmd._params = original_cmd._params.copy()
|
||||
|
||||
|
||||
return custom_cmd
|
||||
|
||||
|
||||
async def sync_guild_commands(self, guild: discord.Guild) -> List[app_commands.Command]:
|
||||
"""
|
||||
Sync commands for a specific guild with customizations.
|
||||
@ -116,10 +116,10 @@ class GuildCommandSyncer:
|
||||
try:
|
||||
# Prepare guild-specific commands
|
||||
guild_commands = await self.prepare_guild_commands(guild.id)
|
||||
|
||||
|
||||
# Sync commands with Discord
|
||||
synced = await self.bot.tree.sync(guild=guild)
|
||||
|
||||
|
||||
log.info(f"Synced {len(synced)} commands for guild {guild.id}")
|
||||
return synced
|
||||
except Exception as e:
|
||||
@ -132,22 +132,22 @@ def guild_command(name: str, description: str, **kwargs):
|
||||
"""
|
||||
Decorator for registering commands with guild-specific name customization.
|
||||
Usage:
|
||||
|
||||
|
||||
@guild_command(name="mycommand", description="My command description")
|
||||
async def my_command(interaction: discord.Interaction):
|
||||
...
|
||||
"""
|
||||
def decorator(func: Callable[[discord.Interaction, ...], Awaitable[Any]]):
|
||||
def decorator(func: Callable[[discord.Interaction], Awaitable[Any]]):
|
||||
# Create the app command
|
||||
@app_commands.command(name=name, description=description, **kwargs)
|
||||
async def wrapper(interaction: discord.Interaction, *args, **kwargs):
|
||||
return await func(interaction, *args, **kwargs)
|
||||
|
||||
|
||||
# Store the original name for reference
|
||||
wrapper.__original_name__ = name
|
||||
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@ -156,9 +156,9 @@ class GuildCommandGroup(app_commands.Group):
|
||||
"""
|
||||
A command group that supports guild-specific name customization.
|
||||
Usage:
|
||||
|
||||
|
||||
my_group = GuildCommandGroup(name="mygroup", description="My group description")
|
||||
|
||||
|
||||
@my_group.command(name="subcommand", description="Subcommand description")
|
||||
async def my_subcommand(interaction: discord.Interaction):
|
||||
...
|
||||
@ -166,7 +166,7 @@ class GuildCommandGroup(app_commands.Group):
|
||||
def __init__(self, name: str, description: str, **kwargs):
|
||||
super().__init__(name=name, description=description, **kwargs)
|
||||
self.__original_name__ = name
|
||||
|
||||
|
||||
async def get_guild_name(self, guild_id: int) -> str:
|
||||
"""Get the guild-specific name for this group."""
|
||||
custom_name = await settings_manager.get_custom_group_name(guild_id, self.__original_name__)
|
||||
@ -190,12 +190,12 @@ async def register_all_guild_commands(bot) -> Dict[int, List[app_commands.Comman
|
||||
"""
|
||||
syncer = GuildCommandSyncer(bot)
|
||||
results = {}
|
||||
|
||||
|
||||
for guild in bot.guilds:
|
||||
try:
|
||||
results[guild.id] = await syncer.sync_guild_commands(guild)
|
||||
except Exception as e:
|
||||
log.error(f"Failed to sync commands for guild {guild.id}: {e}")
|
||||
results[guild.id] = []
|
||||
|
||||
|
||||
return results
|
||||
|
2
main.py
2
main.py
@ -13,7 +13,7 @@ from commands import load_all_cogs, reload_all_cogs
|
||||
from error_handler import handle_error, patch_discord_methods, store_interaction_content
|
||||
from utils import reload_script
|
||||
import settings_manager # Import the settings manager
|
||||
import command_customization # Import command customization utilities
|
||||
from discordbot import command_customization # Import command customization utilities
|
||||
|
||||
# Import the unified API service runner and the sync API module
|
||||
import sys
|
||||
|
Loading…
x
Reference in New Issue
Block a user