major-domo-v2/views/help_commands.py
Cal Corum e689aadbd8 CLAUDE: Fix double emoji issue in Discord embeds
Fixed 13 instances where EmbedTemplate methods were receiving titles
with emojis, resulting in double emojis (e.g., "  Command Created").

Changes:
- tasks/custom_command_cleanup.py: Removed emojis from warning/error titles (2 fixes)
- views/help_commands.py: Removed emoji from success title (1 fix)
- views/custom_commands.py: Removed emojis from titles or switched to
  create_base_embed() for custom emojis (10 fixes)

The following template methods auto-add emoji prefixes:
- EmbedTemplate.success() → adds 
- EmbedTemplate.error() → adds 
- EmbedTemplate.warning() → adds ⚠️
- EmbedTemplate.info() → adds ℹ️
- EmbedTemplate.loading() → adds 

For custom emojis, use EmbedTemplate.create_base_embed() with explicit
color parameter.

All 69 related tests passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 23:09:21 -05:00

400 lines
14 KiB
Python

"""
Help Command Views for Discord Bot v2.0
Interactive views and modals for the custom help system.
"""
from typing import Optional, List
import discord
from views.base import BaseView, ConfirmationView, PaginationView
from views.embeds import EmbedTemplate, EmbedColors
from views.modals import BaseModal
from models.help_command import HelpCommand, HelpCommandSearchResult
from utils.logging import get_contextual_logger
from exceptions import BotException
class HelpCommandCreateModal(BaseModal):
"""Modal for creating a new help topic."""
def __init__(self, *, timeout: Optional[float] = 300.0):
super().__init__(title="Create Help Topic", timeout=timeout)
self.topic_name = discord.ui.TextInput(
label="Topic Name",
placeholder="e.g., trading-rules (2-32 chars, letters/numbers/dashes)",
required=True,
min_length=2,
max_length=32
)
self.topic_title = discord.ui.TextInput(
label="Display Title",
placeholder="e.g., Trading Rules & Guidelines",
required=True,
min_length=1,
max_length=200
)
self.topic_category = discord.ui.TextInput(
label="Category (Optional)",
placeholder="e.g., rules, guides, resources, info, faq",
required=False,
max_length=50
)
self.topic_content = discord.ui.TextInput(
label="Content",
placeholder="Help content (markdown supported, max 4000 chars)",
style=discord.TextStyle.paragraph,
required=True,
min_length=1,
max_length=4000
)
self.add_item(self.topic_name)
self.add_item(self.topic_title)
self.add_item(self.topic_category)
self.add_item(self.topic_content)
async def on_submit(self, interaction: discord.Interaction):
"""Handle form submission."""
# Store results
self.result = {
'name': self.topic_name.value.strip(),
'title': self.topic_title.value.strip(),
'content': self.topic_content.value.strip(),
'category': self.topic_category.value.strip() if self.topic_category.value else None
}
self.is_submitted = True
# Create preview embed
embed = EmbedTemplate.info(
title="Help Topic Preview",
description="Here's how your help topic will look:"
)
embed.add_field(
name="Name",
value=f"`/help {self.result['name']}`",
inline=True
)
embed.add_field(
name="Category",
value=self.result['category'] or "None",
inline=True
)
embed.add_field(
name="Title",
value=self.result['title'],
inline=False
)
# Show content preview (truncated if too long)
content_preview = self.result['content'][:500] + ('...' if len(self.result['content']) > 500 else '')
embed.add_field(
name="Content",
value=content_preview,
inline=False
)
embed.set_footer(text="Creating this help topic will make it available to all server members")
await interaction.response.send_message(embed=embed, ephemeral=True)
class HelpCommandEditModal(BaseModal):
"""Modal for editing an existing help topic."""
def __init__(self, help_command: HelpCommand, *, timeout: Optional[float] = 300.0):
super().__init__(title=f"Edit: {help_command.name}", timeout=timeout)
self.original_help = help_command
self.topic_title = discord.ui.TextInput(
label="Display Title",
placeholder="e.g., Trading Rules & Guidelines",
default=help_command.title,
required=True,
min_length=1,
max_length=200
)
self.topic_category = discord.ui.TextInput(
label="Category (Optional)",
placeholder="e.g., rules, guides, resources, info, faq",
default=help_command.category or '',
required=False,
max_length=50
)
self.topic_content = discord.ui.TextInput(
label="Content",
placeholder="Help content (markdown supported, max 4000 chars)",
style=discord.TextStyle.paragraph,
default=help_command.content,
required=True,
min_length=1,
max_length=4000
)
self.add_item(self.topic_title)
self.add_item(self.topic_category)
self.add_item(self.topic_content)
async def on_submit(self, interaction: discord.Interaction):
"""Handle form submission."""
# Store results
self.result = {
'name': self.original_help.name,
'title': self.topic_title.value.strip(),
'content': self.topic_content.value.strip(),
'category': self.topic_category.value.strip() if self.topic_category.value else None
}
self.is_submitted = True
# Create preview embed showing changes
embed = EmbedTemplate.info(
title="Help Topic Edit Preview",
description=f"Changes to `/help {self.original_help.name}`:"
)
# Show title changes if different
if self.original_help.title != self.result['title']:
embed.add_field(name="Old Title", value=self.original_help.title, inline=True)
embed.add_field(name="New Title", value=self.result['title'], inline=True)
embed.add_field(name="\u200b", value="\u200b", inline=True) # Spacer
# Show category changes
old_cat = self.original_help.category or "None"
new_cat = self.result['category'] or "None"
if old_cat != new_cat:
embed.add_field(name="Old Category", value=old_cat, inline=True)
embed.add_field(name="New Category", value=new_cat, inline=True)
embed.add_field(name="\u200b", value="\u200b", inline=True) # Spacer
# Show content preview (always show since it's the main field)
old_content = self.original_help.content[:300] + ('...' if len(self.original_help.content) > 300 else '')
new_content = self.result['content'][:300] + ('...' if len(self.result['content']) > 300 else '')
embed.add_field(
name="Old Content",
value=old_content,
inline=False
)
embed.add_field(
name="New Content",
value=new_content,
inline=False
)
embed.set_footer(text="Changes will be visible to all server members")
await interaction.response.send_message(embed=embed, ephemeral=True)
class HelpCommandDeleteConfirmView(BaseView):
"""Confirmation view for deleting a help topic."""
def __init__(self, help_command: HelpCommand, *, user_id: int, timeout: float = 180.0):
super().__init__(timeout=timeout, user_id=user_id)
self.help_command = help_command
self.result = None
@discord.ui.button(label="Delete Topic", emoji="🗑️", style=discord.ButtonStyle.danger, row=0)
async def confirm_delete(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Confirm the topic deletion."""
self.result = True
embed = EmbedTemplate.success(
title="Help Topic Deleted",
description=f"The help topic `/help {self.help_command.name}` has been deleted (soft delete)."
)
embed.add_field(
name="Note",
value="This topic can be restored later if needed using admin commands.",
inline=False
)
# Disable all buttons
for item in self.children:
if hasattr(item, 'disabled'):
item.disabled = True # type: ignore
await interaction.response.edit_message(embed=embed, view=self)
self.stop()
@discord.ui.button(label="Cancel", emoji="", style=discord.ButtonStyle.secondary, row=0)
async def cancel_delete(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Cancel the topic deletion."""
self.result = False
embed = EmbedTemplate.info(
title="Deletion Cancelled",
description=f"The help topic `/help {self.help_command.name}` was not deleted."
)
# Disable all buttons
for item in self.children:
if hasattr(item, 'disabled'):
item.disabled = True # type: ignore
await interaction.response.edit_message(embed=embed, view=self)
self.stop()
class HelpCommandListView(BaseView):
"""Paginated view for browsing help topics."""
def __init__(
self,
help_commands: List[HelpCommand],
user_id: Optional[int] = None,
category_filter: Optional[str] = None,
*,
timeout: float = 300.0
):
super().__init__(timeout=timeout, user_id=user_id)
self.help_commands = help_commands
self.category_filter = category_filter
self.current_page = 0
self.topics_per_page = 10
self._update_buttons()
def _update_buttons(self):
"""Update button states based on current page."""
total_pages = max(1, (len(self.help_commands) + self.topics_per_page - 1) // self.topics_per_page)
self.previous_page.disabled = self.current_page == 0
self.next_page.disabled = self.current_page >= total_pages - 1
# Update page info
self.page_info.label = f"Page {self.current_page + 1}/{total_pages}"
def _get_current_topics(self) -> List[HelpCommand]:
"""Get help topics for current page."""
start_idx = self.current_page * self.topics_per_page
end_idx = start_idx + self.topics_per_page
return self.help_commands[start_idx:end_idx]
def _create_embed(self) -> discord.Embed:
"""Create embed for current page."""
current_topics = self._get_current_topics()
title = "📚 Help Topics"
if self.category_filter:
title += f" - {self.category_filter.title()}"
description = f"Found {len(self.help_commands)} help topic{'s' if len(self.help_commands) != 1 else ''}"
embed = EmbedTemplate.create_base_embed(
title=title,
description=description,
color=EmbedColors.INFO
)
if not current_topics:
embed.add_field(
name="No Topics",
value="No help topics found. Admins can create topics using `/help-create`.",
inline=False
)
else:
# Group by category for better organization
by_category = {}
for topic in current_topics:
cat = topic.category or "Uncategorized"
if cat not in by_category:
by_category[cat] = []
by_category[cat].append(topic)
for category, topics in sorted(by_category.items()):
topic_list = []
for topic in topics:
views_text = f"{topic.view_count} views" if topic.view_count > 0 else ""
topic_list.append(f"• `/help {topic.name}` - {topic.title}{views_text}")
embed.add_field(
name=f"📂 {category}",
value='\n'.join(topic_list),
inline=False
)
embed.set_footer(text="Use /help <topic-name> to view a specific topic")
return embed
@discord.ui.button(emoji="◀️", style=discord.ButtonStyle.secondary, row=0)
async def previous_page(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Go to previous page."""
self.current_page = max(0, self.current_page - 1)
self._update_buttons()
embed = self._create_embed()
await interaction.response.edit_message(embed=embed, view=self)
@discord.ui.button(label="1/1", style=discord.ButtonStyle.secondary, disabled=True, row=0)
async def page_info(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Page info (disabled button)."""
pass
@discord.ui.button(emoji="▶️", style=discord.ButtonStyle.secondary, row=0)
async def next_page(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Go to next page."""
total_pages = max(1, (len(self.help_commands) + self.topics_per_page - 1) // self.topics_per_page)
self.current_page = min(total_pages - 1, self.current_page + 1)
self._update_buttons()
embed = self._create_embed()
await interaction.response.edit_message(embed=embed, view=self)
async def on_timeout(self):
"""Handle view timeout."""
for item in self.children:
if hasattr(item, 'disabled'):
item.disabled = True # type: ignore
def get_embed(self) -> discord.Embed:
"""Get the embed for this view."""
return self._create_embed()
def create_help_topic_embed(help_command: HelpCommand) -> discord.Embed:
"""
Create a formatted embed for displaying a help topic.
Args:
help_command: The help command to display
Returns:
Formatted discord.Embed
"""
embed = EmbedTemplate.create_base_embed(
title=help_command.title,
description=help_command.content,
color=EmbedColors.INFO
)
# Add metadata footer
footer_text = f"Topic: {help_command.name}"
if help_command.category:
footer_text += f" • Category: {help_command.category}"
if help_command.view_count > 0:
footer_text += f" • Viewed {help_command.view_count} times"
embed.set_footer(text=footer_text)
# Add timestamps if available
if help_command.updated_at:
embed.timestamp = help_command.updated_at
else:
embed.timestamp = help_command.created_at
return embed