feat: machine binding and version enforcement
migration 017 adds machine_id to agents table middleware validates X-Machine-ID header on authed routes agent client sends machine ID with requests MIN_AGENT_VERSION config defaults 0.1.22 version utils added for comparison blocks config copying attacks via hardware fingerprint old agents get 426 upgrade required breaking: <0.1.22 agents rejected
This commit is contained in:
24
discord/.env.example
Normal file
24
discord/.env.example
Normal file
@@ -0,0 +1,24 @@
|
||||
# Discord Configuration Template
|
||||
# Copy this file to .env and fill in your actual values
|
||||
|
||||
# Discord Bot Configuration
|
||||
DISCORD_BOT_TOKEN=your_bot_token_here
|
||||
DISCORD_SERVER_ID=your_server_id_here
|
||||
DISCORD_APPLICATION_ID=your_app_id_here
|
||||
DISCORD_PUBLIC_KEY=your_public_key_here
|
||||
|
||||
# Server Management
|
||||
SERVER_NAME=RedFlag Security
|
||||
ADMIN_ROLE_ID=your_admin_role_id_here
|
||||
|
||||
# Channel IDs (to be filled after creation)
|
||||
GENERAL_CHANNEL_ID=
|
||||
ANNOUNCEMENTS_CHANNEL_ID=
|
||||
SECURITY_ALERTS_CHANNEL_ID=
|
||||
DEV_CHAT_CHANNEL_ID=
|
||||
BUG_REPORTS_CHANNEL_ID=
|
||||
|
||||
# Category IDs (to be filled after creation)
|
||||
COMMUNITY_CATEGORY_ID=
|
||||
DEVELOPMENT_CATEGORY_ID=
|
||||
SECURITY_CATEGORY_ID=
|
||||
31
discord/.gitignore
vendored
Normal file
31
discord/.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.env
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
venv.bak/
|
||||
venv/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
426
discord/discord_manager.py
Executable file
426
discord/discord_manager.py
Executable file
@@ -0,0 +1,426 @@
|
||||
#!/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
|
||||
await self.bot.tree.sync()
|
||||
logger.info('✅ Commands synced')
|
||||
|
||||
# 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}')
|
||||
|
||||
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="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_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"),
|
||||
("`/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()
|
||||
153
discord/env_manager.py
Executable file
153
discord/env_manager.py
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Secure Discord Environment Manager
|
||||
Handles loading Discord configuration from .env without exposing secrets
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DiscordEnvManager:
|
||||
"""Secure environment manager for Discord configuration"""
|
||||
|
||||
def __init__(self, env_file: str = ".env"):
|
||||
self.env_file = env_file
|
||||
self._config = {}
|
||||
self._load_config()
|
||||
|
||||
def _load_config(self):
|
||||
"""Load configuration from .env file"""
|
||||
try:
|
||||
load_dotenv(self.env_file)
|
||||
self._config = {
|
||||
'DISCORD_BOT_TOKEN': os.getenv('DISCORD_BOT_TOKEN'),
|
||||
'DISCORD_SERVER_ID': os.getenv('DISCORD_SERVER_ID'),
|
||||
'DISCORD_APPLICATION_ID': os.getenv('DISCORD_APPLICATION_ID'),
|
||||
'DISCORD_PUBLIC_KEY': os.getenv('DISCORD_PUBLIC_KEY'),
|
||||
'SERVER_NAME': os.getenv('SERVER_NAME', 'RedFlag Security'),
|
||||
'ADMIN_ROLE_ID': os.getenv('ADMIN_ROLE_ID'),
|
||||
'GENERAL_CHANNEL_ID': os.getenv('GENERAL_CHANNEL_ID'),
|
||||
'ANNOUNCEMENTS_CHANNEL_ID': os.getenv('ANNOUNCEMENTS_CHANNEL_ID'),
|
||||
'SECURITY_ALERTS_CHANNEL_ID': os.getenv('SECURITY_ALERTS_CHANNEL_ID'),
|
||||
'DEV_CHAT_CHANNEL_ID': os.getenv('DEV_CHAT_CHANNEL_ID'),
|
||||
'BUG_REPORTS_CHANNEL_ID': os.getenv('BUG_REPORTS_CHANNEL_ID'),
|
||||
'COMMUNITY_CATEGORY_ID': os.getenv('COMMUNITY_CATEGORY_ID'),
|
||||
'DEVELOPMENT_CATEGORY_ID': os.getenv('DEVELOPMENT_CATEGORY_ID'),
|
||||
'SECURITY_CATEGORY_ID': os.getenv('SECURITY_CATEGORY_ID'),
|
||||
}
|
||||
|
||||
# Validate required fields
|
||||
required_fields = ['DISCORD_BOT_TOKEN', 'DISCORD_SERVER_ID']
|
||||
missing = [field for field in required_fields if not self._config.get(field)]
|
||||
if missing:
|
||||
logger.error(f"Missing required environment variables: {missing}")
|
||||
raise ValueError(f"Missing required fields: {missing}")
|
||||
|
||||
logger.info("✅ Discord configuration loaded successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load Discord configuration: {e}")
|
||||
raise
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Optional[str]:
|
||||
"""Get configuration value"""
|
||||
return self._config.get(key, default)
|
||||
|
||||
def get_required(self, key: str) -> str:
|
||||
"""Get required configuration value"""
|
||||
value = self._config.get(key)
|
||||
if not value:
|
||||
raise ValueError(f"Required environment variable {key} is not set")
|
||||
return value
|
||||
|
||||
def update_channel_ids(self, channel_name: str, channel_id: str):
|
||||
"""Update channel ID in config"""
|
||||
channel_key = f"{channel_name.upper()}_CHANNEL_ID"
|
||||
self._config[channel_key] = channel_id
|
||||
|
||||
# Also update in file
|
||||
self._update_env_file(channel_key, channel_id)
|
||||
|
||||
def update_category_ids(self, category_name: str, category_id: str):
|
||||
"""Update category ID in config"""
|
||||
category_key = f"{category_name.upper()}_CATEGORY_ID"
|
||||
self._config[category_key] = category_id
|
||||
|
||||
# Also update in file
|
||||
self._update_env_file(category_key, category_id)
|
||||
|
||||
def _update_env_file(self, key: str, value: str):
|
||||
"""Update .env file with new value"""
|
||||
try:
|
||||
env_path = os.path.join(os.path.dirname(__file__), self.env_file)
|
||||
|
||||
# Read current file
|
||||
if os.path.exists(env_path):
|
||||
with open(env_path, 'r') as f:
|
||||
lines = f.readlines()
|
||||
else:
|
||||
lines = []
|
||||
|
||||
# Update or add the line
|
||||
updated = False
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith(f"{key}="):
|
||||
lines[i] = f"{key}={value}\n"
|
||||
updated = True
|
||||
break
|
||||
|
||||
if not updated:
|
||||
lines.append(f"{key}={value}\n")
|
||||
|
||||
# Write back to file
|
||||
with open(env_path, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
logger.info(f"✅ Updated {key} in {self.env_file}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update {key} in {self.env_file}: {e}")
|
||||
|
||||
def is_configured(self) -> bool:
|
||||
"""Check if the Discord bot is properly configured"""
|
||||
return (
|
||||
self.get('DISCORD_BOT_TOKEN') and
|
||||
self.get('DISCORD_SERVER_ID') and
|
||||
self.get('DISCORD_APPLICATION_ID')
|
||||
)
|
||||
|
||||
def mask_sensitive_info(self, text: str) -> str:
|
||||
"""Mask sensitive information in logs"""
|
||||
sensitive_words = ['TOKEN', 'KEY']
|
||||
masked_text = text
|
||||
|
||||
for word in sensitive_words:
|
||||
if f"{word}_ID" not in masked_text: # Don't mask channel IDs
|
||||
# Find and mask the value
|
||||
import re
|
||||
pattern = rf'{word}=\w+'
|
||||
replacement = f'{word}=***MASKED***'
|
||||
masked_text = re.sub(pattern, replacement, masked_text)
|
||||
|
||||
return masked_text
|
||||
|
||||
# Global instance for easy access
|
||||
discord_env = DiscordEnvManager()
|
||||
|
||||
# Convenience functions
|
||||
def get_discord_config():
|
||||
"""Get Discord configuration"""
|
||||
return discord_env
|
||||
|
||||
def is_discord_ready():
|
||||
"""Check if Discord is ready for use"""
|
||||
return discord_env.is_configured()
|
||||
4
discord/requirements.txt
Normal file
4
discord/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
discord.py>=2.4.0
|
||||
python-dotenv>=1.0.0
|
||||
aiohttp>=3.8.0
|
||||
asyncio-mqtt>=0.16.0
|
||||
124
discord/setup.py
Normal file
124
discord/setup.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
RedFlag Discord Setup Assistant
|
||||
Helps configure Discord bot for server management
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
||||
def setup_discord():
|
||||
"""Interactive Discord setup"""
|
||||
print("🚀 RedFlag Discord Bot Setup Assistant")
|
||||
print("=" * 50)
|
||||
|
||||
# Check if .env exists
|
||||
env_file = ".env"
|
||||
if not os.path.exists(env_file):
|
||||
print(f"📝 Creating {env_file} from template...")
|
||||
|
||||
if os.path.exists(".env.example"):
|
||||
import shutil
|
||||
shutil.copy(".env.example", env_file)
|
||||
print(f"✅ Created {env_file} from .env.example")
|
||||
else:
|
||||
# Create basic .env file
|
||||
with open(env_file, 'w') as f:
|
||||
f.write("# Discord Bot Configuration\n")
|
||||
f.write("DISCORD_BOT_TOKEN=your_bot_token_here\n")
|
||||
f.write("DISCORD_SERVER_ID=your_server_id_here\n")
|
||||
f.write("DISCORD_APPLICATION_ID=your_app_id_here\n")
|
||||
f.write("DISCORD_PUBLIC_KEY=your_public_key_here\n")
|
||||
f.write("\n# Server Settings\n")
|
||||
f.write("SERVER_NAME=RedFlag Security\n")
|
||||
f.write("ADMIN_ROLE_ID=\n")
|
||||
print(f"✅ Created basic {env_file}")
|
||||
|
||||
# Load environment
|
||||
load_dotenv(env_file)
|
||||
|
||||
print("\n📋 Discord Configuration Checklist:")
|
||||
print("1. ✅ Discord Developer Portal: https://discord.com/developers/applications")
|
||||
print("2. ✅ Create Application: Click 'New Application'")
|
||||
print("3. ✅ Create Bot: Go to 'Bot' → 'Add Bot'")
|
||||
print("4. ✅ Enable Privileged Intents:")
|
||||
print(" - ✅ Server Members Intent")
|
||||
print(" - ✅ Server Management Intent")
|
||||
print(" - ✅ Message Content Intent")
|
||||
print("5. ✅ OAuth2 URL Generator:")
|
||||
print(" - ✅ Scope: bot")
|
||||
print(" - ✅ Scope: applications.commands")
|
||||
print(" - ✅ Permissions: Administrator (or specific)")
|
||||
print("6. ✅ Invite Bot to Server")
|
||||
print("7. ✅ Copy Values Below:")
|
||||
|
||||
print("\n🔑 Required Discord Information:")
|
||||
print("From your Discord Developer Portal, copy these values:")
|
||||
print("-" * 50)
|
||||
|
||||
# Get user input (with masking)
|
||||
def get_sensitive_input(prompt, key):
|
||||
value = input(f"{prompt}: ").strip()
|
||||
if value:
|
||||
# Update .env file
|
||||
update_env_file(key, value)
|
||||
# Show masked version
|
||||
masked_value = value[:8] + "..." + value[-4:] if len(value) > 12 else value
|
||||
print(f"✅ {key}: {masked_value}")
|
||||
return value
|
||||
|
||||
def update_env_file(key, value):
|
||||
"""Update .env file with value"""
|
||||
env_path = os.path.join(os.path.dirname(__file__), env_file)
|
||||
|
||||
# Read current file
|
||||
with open(env_path, 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Update or add the line
|
||||
updated = False
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith(f"{key}="):
|
||||
lines[i] = f"{key}={value}\n"
|
||||
updated = True
|
||||
break
|
||||
|
||||
if not updated:
|
||||
lines.append(f"{key}={value}\n")
|
||||
|
||||
# Write back to file
|
||||
with open(env_path, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
# Get required values
|
||||
bot_token = get_sensitive_input("Discord Bot Token", "DISCORD_BOT_TOKEN")
|
||||
server_id = get_sensitive_input("Discord Server ID", "DISCORD_SERVER_ID")
|
||||
app_id = get_sensitive_input("Discord Application ID", "DISCORD_APPLICATION_ID")
|
||||
public_key = get_sensitive_input("Discord Public Key", "DISCORD_PUBLIC_KEY")
|
||||
|
||||
print("-" * 50)
|
||||
print("🎉 Configuration Complete!")
|
||||
print("\n📝 Next Steps:")
|
||||
print("1. Run the Discord bot:")
|
||||
print(" cd /home/memory/Desktop/Projects/RedFlag/discord")
|
||||
print(" python discord_manager.py")
|
||||
print("\n2. Available Commands (slash commands):")
|
||||
print(" • /status - Show server status")
|
||||
print(" • /create-channels - Create standard channels")
|
||||
print(" • /list-channels - List all channels")
|
||||
print(" • /send-message - Send message to channel")
|
||||
print(" • /create-category - Create new category")
|
||||
print(" • /help - Show all commands")
|
||||
print("\n🔒 Security Note:")
|
||||
print("• Your bot token is stored locally in .env")
|
||||
print("• Never share the .env file")
|
||||
print("• The bot only has Administrator permissions you grant it")
|
||||
print("• All actions are logged locally")
|
||||
|
||||
def main():
|
||||
"""Main setup function"""
|
||||
setup_discord()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user