"""Custom-bot management commands: create, list, and delete.""" from __future__ import annotations import asyncio import logging from typing import TYPE_CHECKING from discord.ext import commands from vibe_bot.app import invalidate_bot_cache from vibe_bot.commands._state import require_app, set_app if TYPE_CHECKING: from discord.ext.commands import Bot from discord.ext.commands import Context as CommandsContext from vibe_bot.app import App logger = logging.getLogger(__name__) MIN_BOT_NAME_LENGTH = 2 MAX_BOT_NAME_LENGTH = 50 MIN_PERSONALITY_LENGTH = 10 MAX_PERSONALITY_LENGTH = 1000 def register(bot: commands.Bot, app: App) -> None: """Register the custom-bot management commands.""" set_app(app) bot.command(name="custom-bot")(custom_bot) bot.command(name="list-custom-bots")(list_custom_bots) bot.command(name="delete-custom-bot")(delete_custom_bot) async def custom_bot( ctx: CommandsContext[Bot], bot_name: str, *, personality: str, ) -> None: """Create a custom bot with a name and personality. Usage: !custom-bot Example: !custom-bot alfred you are a proper british butler """ app = require_app() logger.info( "Custom bot command initiated by user %s: name=%r, personality_chars=%d", ctx.author.id, bot_name, len(personality), ) # Validate bot name name_length = 0 if not bot_name else len(bot_name) if ( not bot_name or name_length < MIN_BOT_NAME_LENGTH or name_length > MAX_BOT_NAME_LENGTH ): logger.warning( "Invalid bot name from user %s: %r (length: %d)", ctx.author.id, bot_name, name_length, ) await ctx.send("Invalid bot name. Name must be between 2 and 50 characters.") return logger.debug("Bot name validation passed for %r", bot_name) # Validate personality personality_length = 0 if not personality else len(personality) if not personality or personality_length < MIN_PERSONALITY_LENGTH: logger.warning( "Invalid personality from user %s: length=%d", ctx.author.id, personality_length, ) await ctx.send( "Invalid personality. Description must be at least 10 characters.", ) return if personality_length > MAX_PERSONALITY_LENGTH: logger.warning( "Personality too long from user %s: length=%d", ctx.author.id, personality_length, ) await ctx.send( f"Personality too long. " f"Max {MAX_PERSONALITY_LENGTH} characters.", ) return logger.debug("Personality validation passed for bot %r", bot_name) # Create the custom bot logger.debug( "Attempting to create custom bot %r for user %s", bot_name, ctx.author.id, ) result = await asyncio.to_thread( app.manager.create_custom_bot, bot_name=bot_name, system_prompt=personality, created_by=str(ctx.author.id), ) if result is False: logger.warning( "Failed to create custom bot %r for user %s", bot_name, ctx.author.id, ) await ctx.send("Failed to create custom bot.") return await asyncio.to_thread(invalidate_bot_cache, app) if result == "replaced": logger.info( "Replaced existing custom bot %r for user %s", bot_name, ctx.author.id, ) await ctx.send( f"Custom bot **'{bot_name}'** already existed and has been " f"**replaced** with personality: *{personality}*", ) else: logger.info( "Successfully created custom bot %r for user %s", bot_name, ctx.author.id, ) await ctx.send( f"Custom bot **'{bot_name}'** has been created " f"with personality: *{personality}*", ) await ctx.send( f"\nYou can now use this bot with: " f"`!{bot_name} `", ) async def list_custom_bots(ctx: CommandsContext[Bot]) -> None: """List all custom bots available in the server.""" app = require_app() logger.info("Listing custom bots requested by user %s", ctx.author.id) logger.debug("Fetching list of custom bots from database") bots = await asyncio.to_thread(app.manager.list_custom_bots) if not bots: logger.debug("No custom bots found for user %s", ctx.author.id) await ctx.send( "No custom bots have been created yet. " "Use `!custom-bot ` to create one.", ) return logger.debug( "Found %d custom bots, displaying top 10 for user %s", len(bots), ctx.author.id, ) bot_list = "Available Custom Bots:\n\n" for name, _prompt, _creator in bots: bot_list += f"* {name}\n" logger.debug("Sending bot list response to user %s", ctx.author.id) await ctx.send(bot_list) async def delete_custom_bot(ctx: CommandsContext[Bot], bot_name: str) -> None: """Delete a custom bot (only the creator can delete). Usage: !delete-custom-bot """ app = require_app() logger.info( "Delete custom bot command initiated by user %s: bot_name=%r", ctx.author.id, bot_name, ) # Get bot info logger.debug("Looking up custom bot %r in database", bot_name) bot_info = await asyncio.to_thread(app.manager.get_custom_bot, bot_name) if not bot_info: logger.warning( "Custom bot %r not found by user %s", bot_name, ctx.author.id, ) await ctx.send(f"Custom bot '{bot_name}' not found.") return logger.debug( "Custom bot %r found, owned by user %s", bot_name, bot_info[2], ) # Check ownership if bot_info[2] != str(ctx.author.id): logger.warning( "User %s attempted to delete bot %r they don't own", ctx.author.id, bot_name, ) await ctx.send("You can only delete your own custom bots.") return logger.debug( "User %s is authorized to delete bot %r", ctx.author.id, bot_name, ) # Delete the bot logger.debug("Deleting custom bot %r from database", bot_name) success = await asyncio.to_thread(app.manager.delete_custom_bot, bot_name) if success: logger.info( "Successfully deleted custom bot %r by user %s", bot_name, ctx.author.id, ) await asyncio.to_thread(invalidate_bot_cache, app) await ctx.send(f"Custom bot '{bot_name}' has been deleted.") else: logger.warning( "Failed to delete custom bot %r by user %s", bot_name, ctx.author.id, ) await ctx.send("Failed to delete custom bot.")