dedicated slash command
All checks were successful
Build and Push Discord Bot Docker Image / build (push) Successful in 58s

This commit is contained in:
2025-12-26 16:36:51 +01:00
parent 0cd17238d0
commit 805095893c

View File

@@ -1,7 +1,7 @@
"""
Cavepedia Discord Bot - Entry point.
A Discord bot that provides access to the Cavepedia AI assistant.
A Discord bot that provides access to the Cavepedia AI assistant via /cavesearch command.
"""
import os
@@ -28,6 +28,7 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
import discord
from discord import app_commands
from src.config import Config
from src.agent_client import AgentClient
@@ -38,14 +39,11 @@ class CavepediaBot(discord.Client):
"""Discord bot for Cavepedia AI assistant."""
def __init__(self, config: Config):
# Set up intents
intents = discord.Intents.default()
intents.message_content = True # Required to read message content
intents.guilds = True
super().__init__(intents=intents)
self.config = config
self.tree = app_commands.CommandTree(self)
self.agent_client = AgentClient(
base_url=config.agent_url,
default_roles=config.default_roles,
@@ -59,6 +57,22 @@ class CavepediaBot(discord.Client):
async def setup_hook(self):
"""Called when the bot is starting up."""
await self.agent_client.start()
# Register the cavesearch command
@self.tree.command(name="cavesearch", description="Search the caving knowledge base")
@app_commands.describe(query="Your question about caving")
async def cavesearch(interaction: discord.Interaction, query: str):
await self.handle_search(interaction, query)
# Sync commands to specific guilds for instant availability
for guild_id in [1137321345718439959, 1454125232439955471]:
guild = discord.Object(id=guild_id)
self.tree.copy_global_to(guild=guild)
try:
await self.tree.sync(guild=guild)
logger.info(f"Commands synced to guild {guild_id}")
except discord.errors.Forbidden:
logger.warning(f"No access to guild {guild_id}, skipping sync")
logger.info("Bot setup complete")
async def close(self):
@@ -70,7 +84,6 @@ class CavepediaBot(discord.Client):
"""Called when the bot has connected to Discord."""
logger.info(f"Logged in as {self.user} (ID: {self.user.id})")
logger.info(f"Allowed channels: {self.config.allowed_channels}")
logger.info(f"Ambient channels: {self.config.ambient_channels}")
# Check agent health
if await self.agent_client.health_check():
@@ -78,78 +91,46 @@ class CavepediaBot(discord.Client):
else:
logger.warning("Agent server health check failed")
async def on_message(self, message: discord.Message):
"""Handle incoming messages."""
# Ignore messages from the bot itself
if message.author == self.user:
return
# Ignore DMs
if message.guild is None:
return
channel_id = message.channel.id
# Check if this is an allowed channel
if channel_id not in self.config.allowed_channels:
return
# Determine if we should respond
is_ambient = channel_id in self.config.ambient_channels
is_mentioned = self.user in message.mentions
# In ambient channels, respond to all messages
# In non-ambient allowed channels, only respond to mentions
if not is_ambient and not is_mentioned:
return
# Extract the actual query (remove bot mention if present)
query = message.content
if is_mentioned:
# Remove the mention from the message
query = query.replace(f"<@{self.user.id}>", "").strip()
query = query.replace(f"<@!{self.user.id}>", "").strip()
# Skip empty messages
if not query:
await message.reply(
"Please include a question after mentioning me. "
"For example: @Cavepedia What is the deepest cave in Virginia?"
async def handle_search(self, interaction: discord.Interaction, query: str):
"""Handle the /cavesearch command."""
# Check if channel is allowed
if interaction.channel_id not in self.config.allowed_channels:
await interaction.response.send_message(
"This command is not available in this channel.",
ephemeral=True,
)
return
# Check rate limits
allowed, error_msg = self.rate_limiter.check(message.author.id)
allowed, error_msg = self.rate_limiter.check(interaction.user.id)
if not allowed:
await message.reply(error_msg)
await interaction.response.send_message(error_msg, ephemeral=True)
return
# Show typing indicator while processing
async with message.channel.typing():
# Defer response since agent calls take time
await interaction.response.defer()
try:
logger.info(
f"Processing query from {message.author} in #{message.channel.name}: {query[:100]}..."
f"Processing query from {interaction.user} in #{interaction.channel}: {query[:100]}..."
)
response = await self.agent_client.query(query)
# Discord has a 2000 character limit
if len(response) > 2000:
# Split into chunks, trying to break at newlines
chunks = self._split_response(response, max_length=1900)
for i, chunk in enumerate(chunks):
if i == 0:
await message.reply(chunk)
await interaction.followup.send(chunks[0])
for chunk in chunks[1:]:
await interaction.channel.send(chunk)
else:
await message.channel.send(chunk)
else:
await message.reply(response)
await interaction.followup.send(response)
logger.info(f"Response sent to {message.author}")
logger.info(f"Response sent to {interaction.user}")
except Exception as e:
logger.error(f"Error processing query: {e}", exc_info=True)
await message.reply(
await interaction.followup.send(
"Sorry, I encountered an error processing your question. "
"Please try again later."
)
@@ -187,7 +168,7 @@ def main():
logger.info(f"Default roles: {config.default_roles}")
bot = CavepediaBot(config)
bot.run(config.discord_token, log_handler=None) # We handle logging ourselves
bot.run(config.discord_token, log_handler=None)
if __name__ == "__main__":