Refactor: remove unused cogs and services, simplify architecture
- Remove admin.py and search.py cogs - Remove searxng.py service and rate_limiter.py utility - Update bot.py, ai_chat.py, config.py, and ai_service.py - Update documentation and docker-compose.yml
This commit is contained in:
@@ -1,137 +0,0 @@
|
||||
"""Admin cog - administrative commands for the bot."""
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
|
||||
from daemon_boyfriend.config import settings
|
||||
from daemon_boyfriend.services import SearXNGService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdminCog(commands.Cog):
|
||||
"""Administrative commands for bot management."""
|
||||
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot = bot
|
||||
self.start_time = datetime.now()
|
||||
|
||||
@app_commands.command(name="ping", description="Check bot latency")
|
||||
async def ping(self, interaction: discord.Interaction) -> None:
|
||||
"""Check the bot's latency."""
|
||||
latency = round(self.bot.latency * 1000)
|
||||
await interaction.response.send_message(f"Pong! Latency: {latency}ms")
|
||||
|
||||
@app_commands.command(name="status", description="Show bot status and info")
|
||||
async def status(self, interaction: discord.Interaction) -> None:
|
||||
"""Show bot status and information."""
|
||||
# Calculate uptime
|
||||
uptime = datetime.now() - self.start_time
|
||||
hours, remainder = divmod(int(uptime.total_seconds()), 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
uptime_str = f"{hours}h {minutes}m {seconds}s"
|
||||
|
||||
# Check SearXNG status
|
||||
searxng = SearXNGService()
|
||||
searxng_status = "Online" if await searxng.health_check() else "Offline"
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"{settings.bot_name} Status",
|
||||
color=discord.Color.green(),
|
||||
)
|
||||
|
||||
embed.add_field(name="Uptime", value=uptime_str, inline=True)
|
||||
embed.add_field(name="Latency", value=f"{round(self.bot.latency * 1000)}ms", inline=True)
|
||||
embed.add_field(name="Guilds", value=str(len(self.bot.guilds)), inline=True)
|
||||
|
||||
embed.add_field(name="AI Provider", value=settings.ai_provider, inline=True)
|
||||
embed.add_field(name="AI Model", value=settings.ai_model, inline=True)
|
||||
embed.add_field(name="SearXNG", value=searxng_status, inline=True)
|
||||
|
||||
embed.add_field(
|
||||
name="Python", value=f"{sys.version_info.major}.{sys.version_info.minor}", inline=True
|
||||
)
|
||||
embed.add_field(name="Discord.py", value=discord.__version__, inline=True)
|
||||
embed.add_field(name="Platform", value=platform.system(), inline=True)
|
||||
|
||||
await interaction.response.send_message(embed=embed)
|
||||
|
||||
@app_commands.command(name="provider", description="Show or change AI provider")
|
||||
@app_commands.describe(provider="The AI provider to switch to (admin only)")
|
||||
@app_commands.choices(
|
||||
provider=[
|
||||
app_commands.Choice(name="OpenAI", value="openai"),
|
||||
app_commands.Choice(name="OpenRouter", value="openrouter"),
|
||||
app_commands.Choice(name="Anthropic (Claude)", value="anthropic"),
|
||||
]
|
||||
)
|
||||
async def provider(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
provider: str | None = None,
|
||||
) -> None:
|
||||
"""Show current AI provider or change it (info only, actual change requires restart)."""
|
||||
if provider is None:
|
||||
# Just show current provider
|
||||
await interaction.response.send_message(
|
||||
f"Current AI provider: **{settings.ai_provider}**\nModel: **{settings.ai_model}**",
|
||||
ephemeral=True,
|
||||
)
|
||||
else:
|
||||
# Inform that changing requires restart
|
||||
await interaction.response.send_message(
|
||||
f"To change the AI provider to **{provider}**, update the `AI_PROVIDER` "
|
||||
f"and corresponding API key in your `.env` file, then restart the bot.",
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
@app_commands.command(name="help", description="Show available commands")
|
||||
async def help_command(self, interaction: discord.Interaction) -> None:
|
||||
"""Show help information."""
|
||||
embed = discord.Embed(
|
||||
title=f"{settings.bot_name} - Help",
|
||||
description="Here are the available commands:",
|
||||
color=discord.Color.blue(),
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Chat",
|
||||
value=(
|
||||
f"**@{settings.bot_name} <message>** - Chat with me by mentioning me\n"
|
||||
"**/chat <message>** - Chat using slash command\n"
|
||||
"**/clear** - Clear your conversation history"
|
||||
),
|
||||
inline=False,
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Search",
|
||||
value=("**/search <query>** - Search the web\n**/image <query>** - Search for images"),
|
||||
inline=False,
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Info",
|
||||
value=(
|
||||
"**/ping** - Check bot latency\n"
|
||||
"**/status** - Show bot status\n"
|
||||
"**/provider** - Show current AI provider\n"
|
||||
"**/help** - Show this help message"
|
||||
),
|
||||
inline=False,
|
||||
)
|
||||
|
||||
embed.set_footer(text=f"Made with love for the MSC group")
|
||||
|
||||
await interaction.response.send_message(embed=embed)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
"""Load the Admin cog."""
|
||||
await bot.add_cog(AdminCog(bot))
|
||||
@@ -1,10 +1,9 @@
|
||||
"""AI Chat cog - handles chat commands and mention responses."""
|
||||
"""AI Chat cog - handles mention responses."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
|
||||
from daemon_boyfriend.config import settings
|
||||
@@ -66,42 +65,13 @@ def split_message(content: str, max_length: int = MAX_MESSAGE_LENGTH) -> list[st
|
||||
|
||||
|
||||
class AIChatCog(commands.Cog):
|
||||
"""AI conversation commands and mention handling."""
|
||||
"""AI conversation via mentions."""
|
||||
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot = bot
|
||||
self.ai_service = AIService()
|
||||
self.conversations = ConversationManager()
|
||||
|
||||
@app_commands.command(name="chat", description="Chat with Daemon Boyfriend")
|
||||
@app_commands.describe(message="Your message to the bot")
|
||||
async def chat(self, interaction: discord.Interaction, message: str) -> None:
|
||||
"""Slash command to chat with the AI."""
|
||||
await interaction.response.defer(thinking=True)
|
||||
|
||||
try:
|
||||
response_text = await self._generate_response(interaction.user.id, message)
|
||||
|
||||
# Split long responses
|
||||
chunks = split_message(response_text)
|
||||
await interaction.followup.send(chunks[0])
|
||||
|
||||
# Send additional chunks as follow-up messages
|
||||
for chunk in chunks[1:]:
|
||||
await interaction.followup.send(chunk)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Chat error: {e}", exc_info=True)
|
||||
await interaction.followup.send("Sorry, I encountered an error. Please try again.")
|
||||
|
||||
@app_commands.command(name="clear", description="Clear your conversation history")
|
||||
async def clear_history(self, interaction: discord.Interaction) -> None:
|
||||
"""Clear the user's conversation history."""
|
||||
self.conversations.clear_history(interaction.user.id)
|
||||
await interaction.response.send_message(
|
||||
"Your conversation history has been cleared!", ephemeral=True
|
||||
)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self, message: discord.Message) -> None:
|
||||
"""Respond when the bot is mentioned."""
|
||||
@@ -117,8 +87,8 @@ class AIChatCog(commands.Cog):
|
||||
content = self._extract_message_content(message)
|
||||
|
||||
if not content:
|
||||
# Just a mention with no message
|
||||
await message.reply(f"Hey {message.author.display_name}! How can I help you?")
|
||||
# Just a mention with no message - use configured description
|
||||
await message.reply(f"Hey {message.author.display_name}! {settings.bot_description}")
|
||||
return
|
||||
|
||||
# Show typing indicator while generating response
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
"""Search cog - web search using SearXNG."""
|
||||
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
|
||||
from daemon_boyfriend.services import SearchResponse, SearXNGService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_search_embed(response: SearchResponse) -> discord.Embed:
|
||||
"""Create a Discord embed for search results.
|
||||
|
||||
Args:
|
||||
response: The search response
|
||||
|
||||
Returns:
|
||||
A formatted Discord embed
|
||||
"""
|
||||
embed = discord.Embed(
|
||||
title=f"Search: {response.query}",
|
||||
color=discord.Color.blue(),
|
||||
)
|
||||
|
||||
if not response.results:
|
||||
embed.description = "No results found."
|
||||
return embed
|
||||
|
||||
# Add results as fields
|
||||
for i, result in enumerate(response.results, 1):
|
||||
# Truncate content if too long
|
||||
content = result.content
|
||||
if len(content) > 200:
|
||||
content = content[:197] + "..."
|
||||
|
||||
embed.add_field(
|
||||
name=f"{i}. {result.title[:100]}",
|
||||
value=f"{content}\n[Link]({result.url})",
|
||||
inline=False,
|
||||
)
|
||||
|
||||
# Add footer with result count
|
||||
embed.set_footer(text=f"Found {response.number_of_results} results")
|
||||
|
||||
return embed
|
||||
|
||||
|
||||
class SearchCog(commands.Cog):
|
||||
"""Web search commands using SearXNG."""
|
||||
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot = bot
|
||||
self.search_service = SearXNGService()
|
||||
|
||||
@app_commands.command(name="search", description="Search the web")
|
||||
@app_commands.describe(
|
||||
query="What to search for",
|
||||
category="Search category",
|
||||
)
|
||||
@app_commands.choices(
|
||||
category=[
|
||||
app_commands.Choice(name="General", value="general"),
|
||||
app_commands.Choice(name="Images", value="images"),
|
||||
app_commands.Choice(name="News", value="news"),
|
||||
app_commands.Choice(name="Science", value="science"),
|
||||
app_commands.Choice(name="IT", value="it"),
|
||||
app_commands.Choice(name="Videos", value="videos"),
|
||||
]
|
||||
)
|
||||
async def search(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
query: str,
|
||||
category: str = "general",
|
||||
) -> None:
|
||||
"""Search the web using SearXNG."""
|
||||
await interaction.response.defer(thinking=True)
|
||||
|
||||
try:
|
||||
response = await self.search_service.search(
|
||||
query=query,
|
||||
categories=[category],
|
||||
num_results=5,
|
||||
)
|
||||
|
||||
embed = create_search_embed(response)
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Search error: {e}", exc_info=True)
|
||||
await interaction.followup.send(
|
||||
f"Search failed. Make sure SearXNG is running and accessible."
|
||||
)
|
||||
|
||||
@app_commands.command(name="image", description="Search for images")
|
||||
@app_commands.describe(query="What to search for")
|
||||
async def image_search(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
query: str,
|
||||
) -> None:
|
||||
"""Search for images using SearXNG."""
|
||||
await interaction.response.defer(thinking=True)
|
||||
|
||||
try:
|
||||
response = await self.search_service.search(
|
||||
query=query,
|
||||
categories=["images"],
|
||||
num_results=5,
|
||||
)
|
||||
|
||||
if not response.results:
|
||||
await interaction.followup.send(f"No images found for: {query}")
|
||||
return
|
||||
|
||||
# Create embed with first image
|
||||
embed = discord.Embed(
|
||||
title=f"Image search: {query}",
|
||||
color=discord.Color.purple(),
|
||||
)
|
||||
|
||||
# Add image results
|
||||
for i, result in enumerate(response.results[:5], 1):
|
||||
embed.add_field(
|
||||
name=f"{i}. {result.title[:50]}",
|
||||
value=f"[View]({result.url})",
|
||||
inline=True,
|
||||
)
|
||||
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Image search error: {e}", exc_info=True)
|
||||
await interaction.followup.send(
|
||||
"Image search failed. Make sure SearXNG is running and accessible."
|
||||
)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
"""Load the Search cog."""
|
||||
await bot.add_cog(SearchCog(bot))
|
||||
Reference in New Issue
Block a user