Files
Redflag/discord/discord_manager.py
Fimeg c95cc7d91f cleanup: remove 2,369 lines of dead code
Removed backup files and unused legacy scanner function.
All code verified as unreferenced.
2025-11-10 21:20:42 -05:00

1046 lines
45 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
RedFlag Discord Management Bot
Interactive Discord server management with secure configuration
"""
import discord
import asyncio
import logging
import sys
from typing import Optional, Dict, List
from discord.ext import commands
from discord import app_commands
from env_manager import discord_env
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class DiscordManager:
"""Interactive Discord server manager"""
def __init__(self):
self.bot_token = discord_env.get_required('DISCORD_BOT_TOKEN')
self.server_id = int(discord_env.get_required('DISCORD_SERVER_ID'))
self.application_id = discord_env.get_required('DISCORD_APPLICATION_ID')
self.public_key = discord_env.get_required('DISCORD_PUBLIC_KEY')
# Bot setup with required intents
intents = discord.Intents.default()
intents.guilds = True
intents.message_content = True
# Initialize bot
self.bot = commands.Bot(
command_prefix='!',
intents=intents,
help_command=None # We'll create custom help
)
self.setup_events()
self.setup_commands()
def setup_events(self):
"""Setup bot event handlers"""
@self.bot.event
async def on_ready():
logger.info(f'✅ Bot logged in as {self.bot.user}')
logger.info(f'Serving server: {self.bot.user.name} (ID: {self.bot.user.id})')
# Sync commands to guild specifically (more reliable)
guild = self.bot.get_guild(self.server_id)
# Sync commands globally
await self.bot.tree.sync()
logger.info('✅ Commands synced globally')
# Get server info
guild = self.bot.get_guild(self.server_id)
if guild:
logger.info(f'✅ Connected to server: {guild.name}')
await self.print_server_status(guild)
else:
logger.error('❌ Could not find server!')
@self.bot.event
async def on_command_error(ctx, error):
"""Handle command errors"""
logger.error(f'Command error in {ctx.command}: {error}')
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send(f'❌ Missing required argument: `{error.param}`')
elif isinstance(error, commands.BadArgument):
await ctx.send(f'❌ Invalid argument: {error}')
else:
await ctx.send(f'❌ An error occurred: {error}')
@self.bot.event
async def on_interaction_error(interaction, error):
"""Handle interaction errors"""
logger.error(f'Interaction error: {error}')
if interaction.response.is_done():
await interaction.followup.send(f'❌ An error occurred: {error}', ephemeral=True)
else:
await interaction.response.send_message(f'❌ An error occurred: {error}', ephemeral=True)
def setup_commands(self):
"""Setup slash commands"""
# Server management commands
@self.bot.tree.command(name="status", description="Show current Discord server status")
async def cmd_status(interaction: discord.Interaction):
await self.cmd_status(interaction)
@self.bot.tree.command(name="create-channels", description="Create standard RedFlag channels")
async def cmd_create_channels(interaction: discord.Interaction):
await self.cmd_create_channels(interaction)
@self.bot.tree.command(name="send-message", description="Send a message to a channel")
@app_commands.describe(channel="Channel to send message to", message="Message to send")
async def cmd_send_message(interaction: discord.Interaction, channel: str, message: str):
await self.cmd_send_message(interaction, channel, message)
@self.bot.tree.command(name="list-channels", description="List all channels and categories")
async def cmd_list_channels(interaction: discord.Interaction):
await self.cmd_list_channels(interaction)
@self.bot.tree.command(name="create-category", description="Create a channel category")
@app_commands.describe(name="Category name")
async def cmd_create_category(interaction: discord.Interaction, name: str):
await self.cmd_create_category(interaction, name)
@self.bot.tree.command(name="create-test-channel", description="Create one simple test channel")
async def cmd_create_test_channel(interaction: discord.Interaction):
await self.cmd_create_test_channel(interaction)
@self.bot.tree.command(name="create-roles", description="Create RedFlag community roles")
async def cmd_create_roles(interaction: discord.Interaction):
await self.cmd_create_roles(interaction)
@self.bot.tree.command(name="role-menu", description="Show interactive role assignment menu")
async def cmd_role_menu(interaction: discord.Interaction):
await self.cmd_role_menu(interaction)
@self.bot.tree.command(name="assign-lead-dev", description="Assign RedFlag Lead Dev role *(Admin only)*")
async def cmd_assign_lead_dev(interaction: discord.Interaction, user: discord.Member):
await self.cmd_assign_lead_dev(interaction, user)
@self.bot.tree.command(name="setup-welcome", description="Setup welcome channel with message and role selector *(Admin only)*")
async def cmd_setup_welcome(interaction: discord.Interaction):
await self.cmd_setup_welcome(interaction)
@self.bot.tree.command(name="create-version-channels", description="Create version-related channels *(Admin only)*")
async def cmd_create_version_channels(interaction: discord.Interaction):
await self.cmd_create_version_channels(interaction)
@self.bot.tree.command(name="sync-commands", description="Force sync commands *(Admin only)*")
async def cmd_sync_commands(interaction: discord.Interaction):
await self.cmd_sync_commands(interaction)
@self.bot.tree.command(name="create-redflag-channels", description="Create RedFlag homelab management channels")
async def cmd_create_redflag_channels(interaction: discord.Interaction):
await self.cmd_create_redflag_channels(interaction)
@self.bot.tree.command(name="test", description="Test command")
async def cmd_test(interaction: discord.Interaction):
await interaction.response.send_message("✅ Test command works!", ephemeral=True)
@self.bot.tree.command(name="create-welcome-banner", description="Create a welcome banner in a channel")
@app_commands.describe(channel="Channel to create banner in")
async def cmd_create_welcome_banner(interaction: discord.Interaction, channel: discord.TextChannel):
await self.cmd_create_welcome_banner(interaction, channel)
@self.bot.tree.command(name="list-commands", description="List all available bot commands")
async def cmd_list_commands_debug(interaction: discord.Interaction):
await self.cmd_list_commands_debug(interaction)
@self.bot.tree.command(name="help", description="Show available commands")
async def cmd_help(interaction: discord.Interaction):
await self.cmd_help(interaction)
async def cmd_status(self, interaction: discord.Interaction):
"""Show server status"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
embed = discord.Embed(
title="📊 RedFlag Discord Server Status",
color=discord.Color.blue(),
description=f"Server: **{guild.name}**"
)
embed.add_field(name="👥 Members", value=str(guild.member_count), inline=True)
embed.add_field(name="💬 Channels", value=str(len(guild.channels)), inline=True)
embed.add_field(name="🎭 Roles", value=str(len(guild.roles)), inline=True)
embed.add_field(name="📅 Created", value=guild.created_at.strftime("%Y-%m-%d"), inline=True)
embed.add_field(name="👑 Owner", value=f"<@{guild.owner_id}>", inline=True)
embed.add_field(name="🚀 Boost Level", value=str(guild.premium_tier), inline=True)
await interaction.response.send_message(embed=embed)
async def cmd_create_channels(self, interaction: discord.Interaction):
"""Create standard RedFlag channels"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
results = []
# Create categories first
try:
# Community category
community_cat = await guild.create_category_channel("🌍 Community")
discord_env.update_category_ids("community", str(community_cat.id))
results.append("✅ Community category")
# Development category
dev_cat = await guild.create_category_channel("💻 Development")
discord_env.update_category_ids("development", str(dev_cat.id))
results.append("✅ Development category")
# Security category
security_cat = await guild.create_category_channel("🔒 Security")
discord_env.update_category_ids("security", str(security_cat.id))
results.append("✅ Security category")
except Exception as e:
logger.error(f"Error creating categories: {e}")
results.append(f"❌ Categories: {e}")
# Create channels (with small delays to avoid rate limits)
await asyncio.sleep(1)
# Community channels
try:
general = await guild.create_text_channel(
"general",
category=discord.Object(id=int(discord_env.get('COMMUNITY_CATEGORY_ID', 0))),
reason="Community general discussion"
)
discord_env.update_channel_ids("general", str(general.id))
results.append("✅ #general")
announcements = await guild.create_text_channel(
"announcements",
category=discord.Object(id=int(discord_env.get('COMMUNITY_CATEGORY_ID', 0))),
reason="Project announcements"
)
discord_env.update_channel_ids("announcements", str(announcements.id))
results.append("✅ #announcements")
except Exception as e:
logger.error(f"Error creating community channels: {e}")
results.append(f"❌ Community channels: {e}")
await asyncio.sleep(1)
# Security channels
try:
security_alerts = await guild.create_text_channel(
"security-alerts",
category=discord.Object(id=int(discord_env.get('SECURITY_CATEGORY_ID', 0))),
reason="Security alerts and notifications"
)
discord_env.update_channel_ids("security-alerts", str(security_alerts.id))
results.append("✅ #security-alerts")
except Exception as e:
logger.error(f"Error creating security channels: {e}")
results.append(f"❌ Security channels: {e}")
await asyncio.sleep(1)
# Development channels
try:
dev_chat = await guild.create_text_channel(
"dev-chat",
category=discord.Object(id=int(discord_env.get('DEVELOPMENT_CATEGORY_ID', 0))),
reason="Development discussions"
)
discord_env.update_channel_ids("dev-chat", str(dev_chat.id))
results.append("✅ #dev-chat")
bug_reports = await guild.create_text_channel(
"bug-reports",
category=discord.Object(id=int(discord_env.get('DEVELOPMENT_CATEGORY_ID', 0))),
reason="Bug reports and issues"
)
discord_env.update_channel_ids("bug-reports", str(bug_reports.id))
results.append("✅ #bug-reports")
except Exception as e:
logger.error(f"Error creating development channels: {e}")
results.append(f"❌ Development channels: {e}")
# Send results
embed = discord.Embed(
title="🔧 Channel Creation Results",
color=discord.Color.green() if "" not in str(results) else discord.Color.red(),
description="\n".join(results)
)
await interaction.followup.send(embed=embed, ephemeral=True)
async def cmd_send_message(self, interaction: discord.Interaction, channel: str, message: str):
"""Send a message to a specific channel"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Find channel by name
target_channel = discord.utils.get(guild.text_channels, name=channel.lower())
if not target_channel:
await interaction.response.send_message(f"❌ Channel '{channel}' not found!", ephemeral=True)
return
try:
await target_channel.send(message)
await interaction.response.send_message(
f"✅ Message sent to #{channel}!", ephemeral=True
)
except Exception as e:
logger.error(f"Error sending message: {e}")
await interaction.response.send_message(
f"❌ Failed to send message: {e}", ephemeral=True
)
async def cmd_list_channels(self, interaction: discord.Interaction):
"""List all channels and categories"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
embed = discord.Embed(
title="📋 Server Channels",
color=discord.Color.blue()
)
# List categories
categories = [c for c in guild.categories if c.name]
if categories:
category_text = "\n".join([f"**{c.name}** (ID: {c.id})" for c in categories])
embed.add_field(name="📂 Categories", value=category_text or "None", inline=False)
# List text channels
text_channels = [c for c in guild.text_channels if c.category]
if text_channels:
channel_text = "\n".join([f"#{c.name} (ID: {c.id})" for c in text_channels])
embed.add_field(name="💬 Text Channels", value=channel_text or "None", inline=False)
# List voice channels
voice_channels = [c for c in guild.voice_channels if c.category]
if voice_channels:
voice_text = "\n".join([f"🎤 {c.name} (ID: {c.id})" for c in voice_channels])
embed.add_field(name="🎤 Voice Channels", value=voice_text or "None", inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def cmd_create_category(self, interaction: discord.Interaction, name: str):
"""Create a new category"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
try:
category = await guild.create_category_channel(name)
await interaction.response.send_message(
f"✅ Created category: **{name}** (ID: {category.id})",
ephemeral=True
)
except Exception as e:
logger.error(f"Error creating category: {e}")
await interaction.response.send_message(
f"❌ Failed to create category: {e}",
ephemeral=True
)
async def cmd_create_test_channel(self, interaction: discord.Interaction):
"""Create one simple test channel"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
try:
# Create a simple text channel
test_channel = await guild.create_text_channel(
"test-channel",
reason="Testing bot channel creation"
)
await interaction.response.send_message(
f"✅ Created test channel: **#{test_channel.name}**",
ephemeral=True
)
except Exception as e:
logger.error(f"Error creating test channel: {e}")
await interaction.response.send_message(
f"❌ Failed to create test channel: {e}",
ephemeral=True
)
async def cmd_create_redflag_channels(self, interaction: discord.Interaction):
"""Create RedFlag development/support Discord channels"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
results = []
try:
# Create categories for community Discord
welcome_cat = await guild.create_category_channel("👋 Welcome & Info")
results.append("✅ Welcome & Info category")
support_cat = await guild.create_category_channel("💬 Support & Help")
results.append("✅ Support & Help category")
dev_cat = await guild.create_category_channel("🔧 Development")
results.append("✅ Development category")
community_cat = await guild.create_category_channel("🌍 Community")
results.append("✅ Community category")
await asyncio.sleep(1)
# Welcome & Info channels
rules = await guild.create_text_channel(
"rules-and-info",
category=welcome_cat,
reason="Community rules and project information"
)
results.append("✅ #rules-and-info")
announcements = await guild.create_text_channel(
"announcements",
category=welcome_cat,
reason="Project announcements and releases"
)
results.append("✅ #announcements")
await asyncio.sleep(1)
# Support & Help channels
general_support = await guild.create_text_channel(
"general-support",
category=support_cat,
reason="General RedFlag support and questions"
)
results.append("✅ #general-support")
installation = await guild.create_text_channel(
"installation-help",
category=support_cat,
reason="Help with RedFlag installation and setup"
)
results.append("✅ #installation-help")
bug_reports = await guild.create_text_channel(
"bug-reports",
category=support_cat,
reason="Bug reports and troubleshooting"
)
results.append("✅ #bug-reports")
await asyncio.sleep(1)
# Development channels
general_dev = await guild.create_text_channel(
"general-development",
category=dev_cat,
reason="General development discussions"
)
results.append("✅ #general-development")
feature_requests = await guild.create_text_channel(
"feature-requests",
category=dev_cat,
reason="Feature requests and ideas"
)
results.append("✅ #feature-requests")
code_review = await guild.create_text_channel(
"code-review",
category=dev_cat,
reason="Code review and development collaboration"
)
results.append("✅ #code-review")
await asyncio.sleep(1)
# Community channels
general_chat = await guild.create_text_channel(
"general-chat",
category=community_cat,
reason="Off-topic community chat"
)
results.append("✅ #general-chat")
homelab = await guild.create_text_channel(
"homelab-showcase",
category=community_cat,
reason="Share your homelab setups and RedFlag deployments"
)
results.append("✅ #homelab-showcase")
# Update .env with important channel IDs
discord_env.update_channel_ids("announcements", str(announcements.id))
discord_env.update_channel_ids("general-support", str(general_support.id))
discord_env.update_channel_ids("bug-reports", str(bug_reports.id))
discord_env.update_channel_ids("general-development", str(general_dev.id))
except Exception as e:
logger.error(f"Error creating RedFlag community channels: {e}")
results.append(f"❌ Error: {e}")
embed = discord.Embed(
title="🏠 RedFlag Community Discord Setup",
color=discord.Color.green() if "" not in str(results) else discord.Color.red(),
description="Created RedFlag development/support community channels:\n\n" + "\n".join(results)
)
await interaction.followup.send(embed=embed, ephemeral=True)
async def cmd_create_roles(self, interaction: discord.Interaction):
"""Create RedFlag community roles"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Only allow administrators to create roles
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ Only administrators can create roles!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
results = []
# Define RedFlag roles
redflag_roles = {
"🚩 RedFlag Lead Dev": discord.Color.red(),
"🛠 Backend Dev": discord.Color.blue(),
"🎨 Frontend Dev": discord.Color.green(),
"🔍 QA Tester": discord.Color.orange(),
"💬 Community Helper": discord.Color.purple(),
"👤 User": discord.Color.greyple(),
"👀 Lurker": discord.Color.dark_grey(),
}
for role_name, role_color in redflag_roles.items():
try:
# Check if role already exists
existing_role = discord.utils.get(guild.roles, name=role_name)
if existing_role:
results.append(f"⚠️ {role_name} already exists")
continue
# Create the role
role = await guild.create_role(
name=role_name,
color=role_color,
reason="RedFlag community role creation",
mentionable=True
)
results.append(f"✅ Created {role_name}")
# Store role ID in .env for future reference
safe_name = role_name.replace("🚩 ", "").replace("🛠 ", "").replace("🎨 ", "").replace("🔍 ", "").replace("💬 ", "").replace("👤 ", "").replace("👀 ", "").lower().replace(" ", "_")
discord_env._config[f"ROLE_{safe_name.upper()}_ID"] = str(role.id)
except Exception as e:
logger.error(f"Error creating role {role_name}: {e}")
results.append(f"❌ Failed to create {role_name}: {e}")
embed = discord.Embed(
title="🎭 Role Creation Results",
color=discord.Color.green() if "" not in str(results) else discord.Color.red(),
description="\n".join(results)
)
await interaction.followup.send(embed=embed, ephemeral=True)
async def cmd_role_menu(self, interaction: discord.Interaction):
"""Show interactive role assignment menu"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Create the view with role buttons
view = discord.ui.View(timeout=180) # 3 minutes timeout
# Available roles for self-assignment (excluding Lead Dev)
available_roles = [
("🛠 Backend Dev", discord.Color.blue()),
("🎨 Frontend Dev", discord.Color.green()),
("🔍 QA Tester", discord.Color.orange()),
("💬 Community Helper", discord.Color.purple()),
("👤 User", discord.Color.greyple()),
("👀 Lurker", discord.Color.dark_grey()),
]
# Create buttons for each role
for role_name, role_color in available_roles:
button = discord.ui.Button(
label=role_name.replace("🛠 ", "").replace("🎨 ", "").replace("🔍 ", "").replace("💬 ", "").replace("👤 ", "").replace("👀 ", ""),
emoji=role_name.split()[0], # Get the emoji
style=discord.ButtonStyle.secondary
)
async def button_callback(interaction: discord.Interaction, current_role_name=role_name):
await self.handle_role_assignment(interaction, current_role_name)
button.callback = button_callback
view.add_item(button)
embed = discord.Embed(
title="🎭 Choose Your RedFlag Role",
description="Click a button below to assign yourself a role. You can change your role anytime!",
color=discord.Color.blue()
)
embed.add_field(
name="🚩 RedFlag Lead Dev",
value="This role is assigned by administrators only",
inline=False
)
embed.set_footer(text="You can only have one role at a time. Click again to change roles.")
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
async def cmd_assign_lead_dev(self, interaction: discord.Interaction, user: discord.Member):
"""Assign RedFlag Lead Dev role (admin only)"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Only allow administrators to assign Lead Dev role
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ Only administrators can assign the Lead Dev role!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
# Find the Lead Dev role
lead_role = discord.utils.get(guild.roles, name="🚩 RedFlag Lead Dev")
if not lead_role:
await interaction.followup.send("❌ Lead Dev role not found! Please create roles first.", ephemeral=True)
return
try:
# Remove existing RedFlag roles from the user
redflag_role_prefixes = ["🚩 ", "🛠 ", "🎨 ", "🔍 ", "💬 ", "👤 ", "👀 "]
current_roles = [role for role in user.roles if any(role.name.startswith(prefix) for prefix in redflag_role_prefixes)]
if current_roles:
await user.remove_roles(*current_roles, reason="Assigned Lead Dev role")
# Assign Lead Dev role
await user.add_roles(lead_role, reason="Assigned by admin")
await interaction.followup.send(f"✅ Assigned **🚩 RedFlag Lead Dev** to {user.mention}", ephemeral=True)
except Exception as e:
logger.error(f"Error assigning Lead Dev role: {e}")
await interaction.followup.send(f"❌ Failed to assign role: {e}", ephemeral=True)
async def cmd_setup_welcome(self, interaction: discord.Interaction):
"""Setup welcome channel with message and role selector"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Only allow administrators
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ Only administrators can setup the welcome channel!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
results = []
try:
# Find the general channel (try multiple names)
general_channel = None
possible_names = ["general", "🏠127.0.0.1", "🏠localhost", "welcome", "welcome-and-info"]
for name in possible_names:
general_channel = discord.utils.get(guild.text_channels, name=name)
if general_channel:
break
if not general_channel:
# If no specific channel found, just use the first text channel (any category)
logger.info(f"Using first available text channel: {guild.text_channels[0].name}")
general_channel = guild.text_channels[0]
logger.info(f"Selected channel: {general_channel.name} (Category: {general_channel.category.name if general_channel.category else 'No category'})")
if not general_channel:
await interaction.followup.send("❌ Could not find any text channel to use!", ephemeral=True)
return
# Rename the channel to localhost with house emoji
await general_channel.edit(name="🏠localhost", reason="Setup welcome channel")
results.append("✅ Renamed general to 🏠localhost")
# Create welcome message with role selector
welcome_embed = discord.Embed(
title="🏠 Welcome to RedFlag",
description="**Self-hosted update management for homelabs**",
color=discord.Color.blue()
)
welcome_embed.add_field(
name="⚠️ ALPHA SOFTWARE",
value="This is experimental software in active development. Features may be broken, bugs are expected, and breaking changes happen frequently. Use at your own risk, preferably on test systems only.",
inline=False
)
welcome_embed.add_field(
name="🤝 Community & Support",
value="""**Discord Maintenance:** Full disclosure - Discord community management isn't my strongest area. If we grow over 100 users, I'll be looking to vet a moderator to help keep things organized.
**Response Times:** I *should* get alerts and will try to respond timely, but this place is a community for us all to grow and share in.
**Community Guidelines:** Small requests that are slightly off-topic are totally fine. We're building a community around homelabs, update management, and practical solutions - not a corporate support channel.""",
inline=False
)
welcome_embed.add_field(
name="🚀 Get Started",
value="1. **Choose Your Role** below - This helps us know how you're using RedFlag\n2. **Introduce Yourself** in #general-chat\n3. **Share Your Setup** in #homelab-showcase\n4. **Ask Questions** in #general-support",
inline=False
)
welcome_embed.set_footer(text="RedFlag - Simple, Honest, Homelab-first")
welcome_embed.set_thumbnail(url=guild.icon.url if guild.icon else None)
# Create role selector view
view = discord.ui.View(timeout=None) # Persistent view
# Available roles for self-assignment
available_roles = [
("🛠 Backend Dev", discord.Color.blue()),
("🎨 Frontend Dev", discord.Color.green()),
("🔍 QA Tester", discord.Color.orange()),
("💬 Community Helper", discord.Color.purple()),
("👤 User", discord.Color.greyple()),
("👀 Lurker", discord.Color.dark_grey()),
]
# Create buttons for each role
for role_name, role_color in available_roles:
button = discord.ui.Button(
label=role_name.replace("🛠 ", "").replace("🎨 ", "").replace("🔍 ", "").replace("💬 ", "").replace("👤 ", "").replace("👀 ", ""),
emoji=role_name.split()[0],
style=discord.ButtonStyle.secondary,
custom_id=f"role_select_{role_name.replace(' ', '_').replace('🛠', '').replace('🎨', '').replace('🔍', '').replace('💬', '').replace('👤', '').replace('👀', '')}"
)
async def button_callback(interaction: discord.Interaction, current_role_name=role_name):
await self.handle_role_assignment(interaction, current_role_name)
button.callback = button_callback
view.add_item(button)
# Set channel topic with important info
topic = "🏠 Welcome! Use /role-menu to choose your role. RedFlag: Self-hosted update management for homelabs. ALPHA SOFTWARE - expect bugs!"
await general_channel.edit(topic=topic, reason="Set welcome channel topic")
# Send the welcome message
await general_channel.send(embed=welcome_embed, view=view)
results.append("✅ Posted welcome message with role selector and channel topic")
except Exception as e:
logger.error(f"Error setting up welcome channel: {e}")
results.append(f"❌ Error: {e}")
embed = discord.Embed(
title="🏠 Welcome Channel Setup Complete",
color=discord.Color.green() if "" not in str(results) else discord.Color.red(),
description="\n".join(results)
)
await interaction.followup.send(embed=embed, ephemeral=True)
async def cmd_create_version_channels(self, interaction: discord.Interaction):
"""Create version-related channels"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Only allow administrators
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ Only administrators can create version channels!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
results = []
try:
# Create version category
version_cat = await guild.create_category_channel("📦 Version Management")
results.append("✅ Version Management category")
await asyncio.sleep(1)
# Main version channel
main_version = await guild.create_text_channel(
"🎯main",
category=version_cat,
reason="Main stable version discussion"
)
results.append("✅ #main (stable version)")
# Tagged versions channel
tagged_versions = await guild.create_text_channel(
"🏷tagged",
category=version_cat,
reason="Tagged release versions discussion"
)
results.append("✅ #tagged (release versions)")
# Unstable dev channel
unstable_dev = await guild.create_text_channel(
"🔮unstable-developer",
category=version_cat,
reason="Unstable developer branch discussion"
)
results.append("✅ #unstable-developer (dev branch)")
# Update .env with channel IDs
discord_env.update_channel_ids("main_version", str(main_version.id))
discord_env.update_channel_ids("tagged_versions", str(tagged_versions.id))
discord_env.update_channel_ids("unstable_dev", str(unstable_dev.id))
except Exception as e:
logger.error(f"Error creating version channels: {e}")
results.append(f"❌ Error: {e}")
embed = discord.Embed(
title="📦 Version Channels Created",
color=discord.Color.green() if "" not in str(results) else discord.Color.red(),
description="Created version management channels:\n\n" + "\n".join(results)
)
await interaction.followup.send(embed=embed, ephemeral=True)
async def cmd_sync_commands(self, interaction: discord.Interaction):
"""Force sync commands"""
# Only allow administrators
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ Only administrators can sync commands!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
try:
# Sync commands globally
synced = await self.bot.tree.sync()
await interaction.followup.send(f"✅ Synced {len(synced)} commands globally!", ephemeral=True)
logger.info(f"Manually synced {len(synced)} commands")
except Exception as e:
logger.error(f"Error syncing commands: {e}")
await interaction.followup.send(f"❌ Failed to sync commands: {e}", ephemeral=True)
async def handle_role_assignment(self, interaction: discord.Interaction, role_name: str):
"""Handle role assignment from button click"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Find the role
target_role = discord.utils.get(guild.roles, name=role_name)
if not target_role:
await interaction.response.send_message("❌ Role not found! Please ask an admin to create roles first.", ephemeral=True)
return
# Get all RedFlag roles (for removal)
redflag_role_prefixes = ["🚩 ", "🛠 ", "🎨 ", "🔍 ", "💬 ", "👤 ", "👀 "]
current_roles = [role for role in interaction.user.roles if any(role.name.startswith(prefix) for prefix in redflag_role_prefixes)]
try:
# Remove existing RedFlag roles
if current_roles:
await interaction.user.remove_roles(*current_roles, reason="Role change via bot")
# Add new role
await interaction.user.add_roles(target_role, reason="Self-assigned via bot")
# Update the original message to show success
await interaction.response.edit_message(
content=f"✅ Successfully assigned role: **{role_name}**",
view=None # Remove buttons after selection
)
except Exception as e:
logger.error(f"Error assigning role {role_name}: {e}")
await interaction.response.send_message(f"❌ Failed to assign role: {e}", ephemeral=True)
async def cmd_create_welcome_banner(self, interaction: discord.Interaction, channel: discord.TextChannel):
"""Create a welcome banner in a channel"""
try:
# Check if user has admin permissions
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ This command requires Administrator permissions.", ephemeral=True)
return
await interaction.response.defer()
# Create simple welcome embed
embed = discord.Embed(
title="🏠 RedFlag",
description="Self-hosted update management for homelabs",
color=discord.Color.red()
)
embed.add_field(
name="Links",
value="[GitHub](https://github.com/Fimeg/RedFlag) • [Issues](https://github.com/Fimeg/RedFlag/issues)",
inline=False
)
embed.set_thumbnail(url="https://raw.githubusercontent.com/Fimeg/RedFlag/main/website/public/favicon.svg")
# Send and pin the welcome message
message = await channel.send(embed=embed)
await message.pin()
await interaction.followup.send(f"✅ Created welcome banner in #{channel.name}!", ephemeral=True)
logger.info(f"Created welcome banner in #{channel.name}")
except Exception as e:
logger.error(f"Error converting announcement channel: {e}")
await interaction.followup.send(f"❌ Error converting channel: {e}", ephemeral=True)
async def cmd_list_commands_debug(self, interaction: discord.Interaction):
"""List all registered commands for debugging"""
try:
commands = self.bot.tree.get_commands(guild=discord.Object(id=self.server_id))
command_list = []
for cmd in commands:
if hasattr(cmd, 'name') and hasattr(cmd, 'description'):
command_list.append(f"**/{cmd.name}** - {cmd.description}")
embed = discord.Embed(
title="🔍 Registered Commands Debug",
description=f"Found {len(command_list)} commands:",
color=discord.Color.gold()
)
if command_list:
embed.add_field(name="Available Commands", value="\n".join(command_list), inline=False)
else:
embed.description = "No commands found!"
await interaction.response.send_message(embed=embed, ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Error listing commands: {e}", ephemeral=True)
async def cmd_help(self, interaction: discord.Interaction):
"""Show help information"""
embed = discord.Embed(
title="🤖 RedFlag Discord Bot Help",
description="Interactive Discord server management commands",
color=discord.Color.blue()
)
commands_info = [
("`/status`", "📊 Show server status"),
("`/create-channels`", "🔧 Create standard channels"),
("`/create-redflag-channels`", "🏠 Create RedFlag community channels"),
("`/create-roles`", "🎭 Create RedFlag community roles *(Admin only)*"),
("`/setup-welcome`", "🏠 Setup welcome channel with role selector *(Admin only)*"),
("`/create-version-channels`", "📦 Create version management channels *(Admin only)*"),
("`/role-menu`", "🎮 Show interactive role assignment menu"),
("`/assign-lead-dev`", "🚩 Assign Lead Dev role *(Admin only)*"),
("`/list-channels`", "📋 List all channels"),
("`/send-message`", "💬 Send message to channel"),
("`/create-category`", "📂 Create new category"),
("`/create-test-channel`", "🧪 Create one test channel"),
("`/help`", "❓ Show this help"),
]
for cmd, desc in commands_info:
embed.add_field(name=cmd, value=desc, inline=False)
embed.add_field(
name="🛡️ Security Features",
value="✅ Secure configuration management\n✅ Token protection\n✅ Rate limiting",
inline=False
)
embed.add_field(
name="🔄 Live Updates",
value="Configuration changes are saved instantly to .env file",
inline=False
)
embed.set_footer(text="RedFlag Security Discord Management v1.0")
await interaction.response.send_message(embed=embed, ephemeral=True)
async def print_server_status(self, guild):
"""Print detailed server status to console"""
print(f"\n{'='*60}")
print(f"🚀 REDFLAG DISCORD SERVER STATUS")
print(f"{'='*60}")
print(f"Server Name: {guild.name}")
print(f"Server ID: {guild.id}")
print(f"Members: {guild.member_count}")
print(f"Channels: {len(guild.channels)}")
print(f"Roles: {len(guild.roles)}")
print(f"Owner: <@{guild.owner_id}>")
print(f"Created: {guild.created_at}")
print(f"Boost Level: {guild.premium_tier}")
print(f"{'='*60}\n")
async def run(self):
"""Start the bot"""
try:
# Connect to Discord
await self.bot.start(self.bot_token)
except discord.errors.LoginFailure:
logger.error("❌ Invalid Discord bot token!")
logger.error("Please check your DISCORD_BOT_TOKEN in .env file")
sys.exit(1)
except Exception as e:
logger.error(f"❌ Failed to start bot: {e}")
sys.exit(1)
def main():
"""Main entry point"""
print("🚀 Starting RedFlag Discord Management Bot...")
# Check configuration
if not discord_env.is_configured():
print("❌ Discord not configured!")
print("Please:")
print("1. Copy .env.example to .env")
print("2. Fill in your Discord bot token and server ID")
print("3. Run this script again")
sys.exit(1)
# Create and run bot
bot = DiscordManager()
asyncio.run(bot.run())
if __name__ == "__main__":
main()