diff --git a/generate_website.py b/generate_website.py index b7d7855..5dc1c00 100755 --- a/generate_website.py +++ b/generate_website.py @@ -10,6 +10,7 @@ import os import html import logging import sys +import argparse from pathlib import Path from typing import Dict, List, Tuple, Optional, Any from collections import defaultdict @@ -52,6 +53,861 @@ class MinimalBot: self.command_manager = None # Will be set after plugin loading +# Style definitions for website themes +# Each style contains CSS custom property values that will be injected into the :root selector +STYLES = { + 'default': { + 'name': 'Modern Dark', + 'description': 'Dark theme with gradients and modern cards (current default)', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap', + 'css_vars': """--bg-primary: #0a0e14; + --bg-secondary: #111820; + --bg-card: #151c25; + --bg-card-hover: #1a232e; + --accent-blue: #00d4ff; + --accent-cyan: #00ffc8; + --accent-orange: #ff8a00; + --accent-purple: #a855f7; + --accent-red: #ff4757; + --accent-yellow: #ffd700; + --text-primary: #e8edf4; + --text-secondary: #8892a4; + --text-muted: #6b7280; + --border-subtle: rgba(255,255,255,0.06); + --glow-blue: rgba(0, 212, 255, 0.15); + --glow-cyan: rgba(0, 255, 200, 0.1);""" + }, + 'minimalist': { + 'name': 'Minimalist Clean', + 'description': 'Light theme with clean typography and whitespace', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap', + 'css_vars': """--bg-primary: #ffffff; + --bg-secondary: #ffffff; + --bg-card: #ffffff; + --bg-card-hover: #f8f9fa; + --accent-blue: #0052cc; + --accent-cyan: #006699; + --accent-orange: #d65d0e; + --accent-purple: #6b21a8; + --accent-red: #b91c1c; + --accent-yellow: #a16207; + --text-primary: #1a1a1a; + --text-secondary: #4a4a4a; + --text-muted: #6b6b6b; + --border-subtle: rgba(0,0,0,0.15); + --glow-blue: rgba(0,82,204,0.1); + --glow-cyan: rgba(0,102,153,0.08);""", + 'css_overrides': """ + /* Pure white background - remove atmospheric effects */ + .atmosphere, + .grid-overlay { + display: none !important; + } + + body { + background: #ffffff !important; + } + + /* Ultra-minimal: square everything */ + *, + *::before, + *::after, + .container, + .intro-section, + .sidebar-nav, + .sidebar-nav a, + .command-card, + .command-keyword, + .command-usage, + .channel-section, + .channel-card, + button, + input { + border-radius: 0 !important; + } + + /* Clean Inter font everywhere */ + body, + h1, h2, h3, h4, h5, h6, + p, a, span, div { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; + } + + /* Remove all shadows */ + .command-card, + .intro-section, + .sidebar-nav, + .command-keyword, + .command-usage { + box-shadow: none !important; + } + + /* Thin, clean borders */ + .command-card { + border: 1px solid var(--border-subtle) !important; + } + + .intro-section { + border: 1px solid var(--border-subtle) !important; + } + + .sidebar-nav { + border-right: 1px solid var(--border-subtle) !important; + } + + .command-keyword { + border: 1px solid currentColor !important; + font-weight: 500; + } + + .command-usage { + border-left: 2px solid var(--border-subtle) !important; + } + + /* Minimal hover effects - just background color */ + .command-card:hover { + box-shadow: none !important; + transform: none !important; + } + + .sidebar-nav a:hover { + background: var(--bg-card-hover) !important; + } + + /* Typography: lighter weight, more spacing */ + h1 { + font-weight: 600; + letter-spacing: -0.02em; + } + + h2, h3 { + font-weight: 500; + letter-spacing: -0.01em; + } + + .command-name { + font-weight: 600; + } + + p, .command-description { + font-weight: 400; + line-height: 1.6; + } + + /* Remove all animations and transitions for instant feedback */ + *, *::before, *::after { + animation: none !important; + transition: background-color 0.1s ease !important; + } + + /* Clean spacing */ + .command-card { + padding: 1.5rem !important; + } + + /* Subtle, clean keyword badges */ + .command-keyword { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + } + """ + }, + 'terminal': { + 'name': 'Terminal/Hacker', + 'description': 'Green/amber on black, monospace, retro terminal aesthetic', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap', + 'css_vars': """--bg-primary: #000000; + --bg-secondary: #0a0a0a; + --bg-card: #0f0f0f; + --bg-card-hover: #1a1a1a; + --accent-blue: #00ff00; + --accent-cyan: #00ff00; + --accent-orange: #ffb000; + --accent-purple: #00ff00; + --accent-red: #ff0000; + --accent-yellow: #ffff00; + --text-primary: #00ff00; + --text-secondary: #00aa00; + --text-muted: #008800; + --border-subtle: rgba(0,255,0,0.3); + --glow-blue: rgba(0,255,0,0.2); + --glow-cyan: rgba(0,255,0,0.15);""", + 'css_overrides': """ + /* Terminal monospace font everywhere */ + body, + h1, h2, h3, h4, h5, h6, + p, a, span, div, + .command-name, + .command-description, + .command-usage, + .command-keyword, + .sidebar-nav a { + font-family: 'JetBrains Mono', 'Courier New', monospace !important; + } + + /* CRT scanline effect */ + body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: repeating-linear-gradient( + 0deg, + rgba(0, 255, 0, 0.03), + rgba(0, 255, 0, 0.03) 1px, + transparent 1px, + transparent 2px + ); + pointer-events: none; + z-index: 9999; + } + + /* Terminal text glow */ + h1, h2, h3, + .command-name { + text-shadow: 0 0 10px rgba(0, 255, 0, 0.8); + } + + .command-keyword, + .command-usage { + text-shadow: 0 0 5px rgba(0, 255, 0, 0.6); + } + + /* Cursor blink effect after usage example */ + .command-card:hover .command-usage::after { + content: 'ā–®'; + display: inline-block; + margin-left: 0.5rem; + animation: blink 1s step-end infinite; + color: var(--accent-blue); + } + + @keyframes blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0; } + } + + /* Terminal prompt style for usage */ + .command-usage::before { + content: '$ '; + color: var(--accent-yellow); + font-weight: bold; + } + """ + }, + 'glass': { + 'name': 'Glass/Glassmorphism', + 'description': 'Frosted glass cards with blur effects and colorful gradients', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap', + 'css_vars': """--bg-primary: linear-gradient(135deg, #4c5fd7 0%, #764ba2 100%); + --bg-secondary: rgba(255,255,255,0.05); + --bg-card: rgba(255,255,255,0.1); + --bg-card-hover: rgba(255,255,255,0.15); + --accent-blue: #a8daff; + --accent-cyan: #a8fff4; + --accent-orange: #ffc085; + --accent-purple: #d4b3ff; + --accent-red: #ff9eb5; + --accent-yellow: #ffe8a8; + --text-primary: #ffffff; + --text-secondary: rgba(255,255,255,0.8); + --text-muted: rgba(255,255,255,0.6); + --border-subtle: rgba(255,255,255,0.2); + --glow-blue: rgba(168,218,255,0.2); + --glow-cyan: rgba(168,255,244,0.15);""" + }, + 'neon': { + 'name': 'Neon/Cyberpunk', + 'description': 'Bright neon colors, dark backgrounds, futuristic aesthetic', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&display=swap', + 'css_vars': """--bg-primary: #0a0014; + --bg-secondary: #150028; + --bg-card: #1a0033; + --bg-card-hover: #25004d; + --accent-blue: #00f5ff; + --accent-cyan: #00f5ff; + --accent-orange: #ff69b4; + --accent-purple: #bf40bf; + --accent-red: #ff1493; + --accent-yellow: #ffd700; + --text-primary: #ffffff; + --text-secondary: #e9d5ff; + --text-muted: #d8b4fe; + --border-subtle: rgba(157,78,221,0.3); + --glow-blue: rgba(0,245,255,0.4); + --glow-cyan: rgba(247,37,133,0.3);""", + 'css_overrides': """ + /* Neon cyberpunk font */ + body, + h1, h2, h3, h4, h5, h6, + .command-name, + .sidebar-nav a { + font-family: 'Orbitron', sans-serif !important; + font-weight: 600; + } + + /* Intense neon glow on headings */ + h1 { + text-shadow: + 0 0 10px var(--accent-cyan), + 0 0 20px var(--accent-cyan), + 0 0 30px var(--accent-cyan), + 0 0 40px var(--accent-blue); + letter-spacing: 0.1em; + text-transform: uppercase; + } + + h2, h3 { + text-shadow: + 0 0 10px var(--accent-purple), + 0 0 20px var(--accent-purple); + letter-spacing: 0.05em; + } + + .command-name { + text-shadow: + 0 0 10px var(--accent-cyan), + 0 0 20px var(--accent-cyan); + text-transform: uppercase; + letter-spacing: 0.05em; + } + + /* Neon border glow on cards - subtle */ + .command-card { + border: 2px solid var(--accent-cyan); + box-shadow: + 0 0 5px var(--glow-blue), + inset 0 0 5px rgba(0,245,255,0.05); + } + + .command-card:hover { + border-color: var(--accent-orange); + box-shadow: + 0 0 10px var(--accent-cyan), + 0 0 20px var(--accent-orange), + inset 0 0 10px rgba(255,105,180,0.1); + transform: translateY(-4px); + } + + .intro-section { + border: 2px solid var(--accent-purple); + box-shadow: + 0 0 8px var(--accent-purple), + inset 0 0 8px rgba(191,64,191,0.05); + } + + /* Neon keyword badges */ + .command-keyword { + text-shadow: 0 0 8px currentColor; + border: 1px solid currentColor; + box-shadow: + 0 0 5px currentColor, + inset 0 0 5px currentColor; + } + + /* Glowing usage examples */ + .command-usage { + border-left: 3px solid var(--accent-cyan); + box-shadow: -3px 0 10px var(--glow-blue); + text-shadow: 0 0 5px rgba(0,245,255,0.5); + } + + /* Animated neon flicker effect */ + @keyframes neon-flicker { + 0%, 100% { opacity: 1; } + 41%, 43% { opacity: 0.8; } + 45%, 47% { opacity: 0.9; } + 49% { opacity: 0.85; } + } + + h1, .command-name { + animation: neon-flicker 3s infinite; + } + + /* Cyberpunk grid overlay */ + body::after { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: + linear-gradient(rgba(0,245,255,0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(0,245,255,0.03) 1px, transparent 1px); + background-size: 50px 50px; + pointer-events: none; + z-index: 1; + } + + /* Ensure content is above grid */ + .container { + position: relative; + z-index: 2; + } + + /* Sidebar neon accent */ + .sidebar-nav { + border-right: 2px solid var(--accent-cyan); + box-shadow: 2px 0 15px var(--glow-blue); + } + + .sidebar-nav a:hover { + text-shadow: 0 0 10px var(--accent-cyan); + background: rgba(0,245,255,0.1); + } + """ + }, + 'brutalist': { + 'name': 'Brutalist/Bold', + 'description': 'High contrast, bold typography, thick borders, geometric', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700;900&display=swap', + 'css_vars': """--bg-primary: #ffffff; + --bg-secondary: #f0f0f0; + --bg-card: #ffffff; + --bg-card-hover: #f5f5f5; + --accent-blue: #0000cc; + --accent-cyan: #006666; + --accent-orange: #cc5200; + --accent-purple: #5c00b8; + --accent-red: #cc0000; + --accent-yellow: #997700; + --text-primary: #000000; + --text-secondary: #1a1a1a; + --text-muted: #4d4d4d; + --border-subtle: rgba(0,0,0,1); + --glow-blue: rgba(0,0,204,0.2); + --glow-cyan: rgba(0,102,102,0.2);""", + 'css_overrides': """ + /* BRUTALIST: Square everything aggressively */ + *, + *::before, + *::after { + border-radius: 0 !important; + } + + /* Space Grotesk everywhere, heavy weights */ + body, + h1, h2, h3, h4, h5, h6, + p, a, span, div { + font-family: 'Space Grotesk', sans-serif !important; + } + + /* MASSIVE, BOLD headings */ + h1 { + font-weight: 900 !important; + font-size: 4rem !important; + letter-spacing: -0.05em; + text-transform: uppercase; + line-height: 0.9; + } + + .category-title { + font-weight: 900 !important; + font-size: 2.5rem !important; + text-transform: uppercase; + letter-spacing: -0.03em; + border-bottom: 8px solid #000000 !important; + padding-bottom: 0.5rem !important; + } + + .category-title .anchor-link { + text-decoration: none; + color: #000000; + } + + .command-name { + font-weight: 900 !important; + font-size: 1.5rem !important; + text-transform: uppercase; + letter-spacing: -0.02em; + } + + /* THICK borders everywhere */ + .command-card { + border: 6px solid #000000 !important; + box-shadow: 12px 12px 0 #000000 !important; + } + + .command-card:hover { + box-shadow: 16px 16px 0 #000000 !important; + transform: translate(-4px, -4px) !important; + } + + /* Brutal intro box */ + .header-content { + border: 6px solid #000000 !important; + box-shadow: 12px 12px 0 #000000 !important; + border-radius: 0 !important; + background: #ffffff !important; + } + + .header-content::before { + display: none !important; + } + + header { + border-radius: 0 !important; + } + + /* Bold, chunky keyword badges */ + .command-keyword { + font-weight: 700 !important; + border: 3px solid #000000 !important; + padding: 0.5rem 1rem !important; + background: #ffffff; + text-transform: uppercase; + font-size: 0.75rem !important; + letter-spacing: 0.05em; + } + + .command-keyword:nth-child(odd) { + background: #000000; + color: #ffffff; + } + + /* Heavy usage box */ + .command-usage { + border: 4px solid #000000 !important; + background: #f0f0f0 !important; + padding: 1rem !important; + font-weight: 700; + } + + /* Aggressive sidebar - SQUARE */ + .sidebar-nav { + border-right: 8px solid #000000 !important; + border-radius: 0 !important; + } + + .sidebar-nav a { + font-weight: 700 !important; + text-transform: uppercase; + font-size: 0.85rem !important; + letter-spacing: 0.02em; + border-radius: 0 !important; + } + + .sidebar-nav a:hover { + background: #000000 !important; + color: #ffffff !important; + } + + /* Remove blue hover bar on command cards */ + .command-card::before { + display: none !important; + } + + /* CHANNEL CARDS - same brutal treatment */ + .channel-card { + border: 6px solid #000000 !important; + box-shadow: 12px 12px 0 #000000 !important; + border-radius: 0 !important; + } + + .channel-card:hover { + box-shadow: 16px 16px 0 #000000 !important; + transform: translate(-4px, -4px) !important; + } + + .channel-name { + font-weight: 900 !important; + font-size: 1.5rem !important; + text-transform: uppercase; + letter-spacing: -0.02em; + } + + .channel-description { + font-weight: 500 !important; + } + + .channel-section h2 { + font-weight: 900 !important; + font-size: 2.5rem !important; + text-transform: uppercase; + letter-spacing: -0.03em; + border-bottom: 8px solid #000000 !important; + padding-bottom: 0.5rem !important; + } + + /* Remove all smooth transitions - instant feedback */ + * { + transition: none !important; + } + + /* Heavy typography throughout */ + p, .command-description { + font-weight: 500 !important; + line-height: 1.5; + } + + /* Brutal intro section */ + .intro { + font-weight: 700 !important; + font-size: 1.2rem !important; + } + """ + }, + 'gradient': { + 'name': 'Gradient/Modern', + 'description': 'Colorful gradients, smooth transitions, vibrant colors', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap', + 'css_vars': """--bg-primary: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); + --bg-secondary: rgba(0,0,0,0.08); + --bg-card: rgba(255,255,255,0.95); + --bg-card-hover: rgba(255,255,255,0.98); + --accent-blue: #4338ca; + --accent-cyan: #0891b2; + --accent-orange: #ea580c; + --accent-purple: #7c3aed; + --accent-red: #dc2626; + --accent-yellow: #ca8a04; + --text-primary: #1a1a1a; + --text-secondary: #374151; + --text-muted: #6b7280; + --border-subtle: rgba(0,0,0,0.15); + --glow-blue: rgba(79,70,229,0.3); + --glow-cyan: rgba(8,145,178,0.25);""", + 'css_overrides': """ + /* Vibrant gradient borders */ + .command-card { + border: 2px solid transparent; + background: + linear-gradient(white, white) padding-box, + linear-gradient(135deg, #667eea, #764ba2, #f093fb) border-box; + box-shadow: 0 4px 20px rgba(102, 126, 234, 0.15); + background-origin: border-box; + background-clip: padding-box, border-box; + } + + .command-card:hover { + box-shadow: 0 8px 30px rgba(102, 126, 234, 0.25), 0 0 0 1px rgba(102, 126, 234, 0.1); + transform: translateY(-4px); + } + + .intro-section { + border: 2px solid transparent; + background: + linear-gradient(white, white) padding-box, + linear-gradient(135deg, #667eea, #00d4ff) border-box; + box-shadow: 0 4px 20px rgba(102, 126, 234, 0.15); + } + + /* Gradient text for headings */ + h1 { + background: linear-gradient(135deg, #667eea, #764ba2, #f093fb); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + /* Keep section headers (category titles) visible with high contrast */ + .category-title .anchor-link { + background: linear-gradient(135deg, #1a1a1a, #374151); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-decoration: none; + font-weight: 700; + } + + .command-name { + background: linear-gradient(135deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: 700; + } + + /* Colorful keyword badges with gradients */ + .command-keyword { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + font-weight: 600; + } + + .command-keyword:nth-child(2) { + background: linear-gradient(135deg, #f093fb, #667eea); + } + + .command-keyword:nth-child(3) { + background: linear-gradient(135deg, #764ba2, #f093fb); + } + + .command-keyword:nth-child(4) { + background: linear-gradient(135deg, #00d4ff, #667eea); + } + + .command-keyword:nth-child(5) { + background: linear-gradient(135deg, #f093fb, #00d4ff); + } + + /* Animated gradient on hover */ + @keyframes gradient-shift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } + } + + .command-card:hover .command-name { + background: linear-gradient(135deg, #667eea, #764ba2, #f093fb, #00d4ff); + background-size: 300% 300%; + animation: gradient-shift 3s ease infinite; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + /* Gradient usage box */ + .command-usage { + background: linear-gradient(135deg, rgba(102, 126, 234, 0.05), rgba(240, 147, 251, 0.05)); + border-left: 3px solid; + border-image: linear-gradient(135deg, #667eea, #f093fb) 1; + } + + /* Sidebar gradient accent */ + .sidebar-nav { + border-right: 3px solid transparent; + border-image: linear-gradient(180deg, #667eea, #764ba2, #f093fb) 1; + } + + .sidebar-nav a:hover { + background: linear-gradient(90deg, rgba(102, 126, 234, 0.1), transparent); + border-left: 3px solid #667eea; + padding-left: 1.5rem; + } + """ + }, + 'pixel': { + 'name': 'Pixel/Retro', + 'description': 'Pixel art aesthetic, squared boxes, bright retro gaming colors', + 'fonts_url': 'https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap', + 'css_vars': """--bg-primary: #2b2d42; + --bg-secondary: #3a3d5c; + --bg-card: #4a4e69; + --bg-card-hover: #5a5f7e; + --accent-blue: #00b4d8; + --accent-cyan: #00f5d4; + --accent-orange: #ff6b35; + --accent-purple: #b185db; + --accent-red: #ef476f; + --accent-yellow: #ffd60a; + --text-primary: #ffffff; + --text-secondary: #e0e0e0; + --text-muted: #a8a8a8; + --border-subtle: rgba(255,255,255,0.3); + --glow-blue: rgba(0,180,216,0.4); + --glow-cyan: rgba(0,245,212,0.3);""", + 'css_overrides': """ + /* Pixel art: remove ALL rounded corners */ + *, + *::before, + *::after, + .container, + .intro-section, + .sidebar-nav, + .sidebar-nav a, + .command-card, + .command-keyword, + .command-usage, + .channel-section, + .channel-card, + .mobile-menu-toggle, + button, + input { + border-radius: 0 !important; + } + + /* Chunky pixel borders */ + .command-card { + border: 3px solid var(--border-subtle) !important; + } + + .intro-section { + border: 3px solid var(--border-subtle) !important; + } + + .sidebar-nav { + border: 3px solid var(--border-subtle) !important; + } + + .command-keyword { + border: 2px solid currentColor !important; + } + + .command-usage { + border: 2px solid var(--accent-cyan) !important; + } + + .channel-section { + border: 3px solid var(--border-subtle) !important; + } + + /* Pixel font sizing adjustments */ + body { + font-family: 'Press Start 2P', cursive !important; + line-height: 1.8; + } + + h1 { + font-size: 1.5rem !important; + line-height: 1.6; + } + + h2 { + font-size: 1.2rem !important; + line-height: 1.6; + } + + h3 { + font-size: 1rem !important; + line-height: 1.6; + } + + .command-name { + font-size: 1.1rem !important; + } + + p, .command-description, .command-usage { + font-size: 0.75rem !important; + line-height: 1.8; + } + + .command-keyword { + font-size: 0.65rem !important; + padding: 4px 8px; + } + + .sidebar-nav a { + font-size: 0.7rem !important; + } + + /* Retro box shadow - hard edges */ + .command-card { + box-shadow: 4px 4px 0 var(--border-subtle) !important; + } + + .command-card:hover { + box-shadow: 8px 8px 0 var(--accent-cyan) !important; + transform: translate(-2px, -2px) !important; + } + + .intro-section { + box-shadow: 4px 4px 0 var(--border-subtle) !important; + } + + /* Remove smooth transitions for pixel feel */ + * { + transition: none !important; + } + """ + } +} + + def setup_logging(): """Setup basic logging""" logging.basicConfig( @@ -343,7 +1199,7 @@ def format_monitor_channels(monitor_channels: List[str], html: bool = False) -> return ", ".join(formatted[:-1]) + f", or {formatted[-1]}" -def generate_html(bot_name: str, title: str, introduction: str, commands: List[Tuple[str, Any]], monitor_channels: List[str] = None, channels_data: Dict[str, Dict[str, str]] = None) -> str: +def generate_html(bot_name: str, title: str, introduction: str, commands: List[Tuple[str, Any]], monitor_channels: List[str] = None, channels_data: Dict[str, Dict[str, str]] = None, style: str = 'default') -> str: """Generate the HTML content""" if monitor_channels is None: @@ -587,25 +1443,10 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T {escape_html(title)} - + @@ -1466,7 +2306,6 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T
-
šŸ¤–

{escape_html(bot_name)}

@@ -1577,14 +2416,249 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T return html_content +def list_styles(): + """Print available styles with descriptions.""" + print("Available styles:\n") + + # Calculate max name length for alignment + max_len = max(len(name) for name in STYLES.keys()) + + for style_name, style_info in STYLES.items(): + padding = ' ' * (max_len - len(style_name)) + print(f" {style_name}{padding} {style_info['name']}") + print(f" {' ' * max_len} {style_info['description']}\n") + + +def generate_samples(config_file): + """Generate sample HTML files for all styles with an index page.""" + logger = logging.getLogger(__name__) + + logger.info(f"Reading config from {config_file}") + config = read_config(config_file) + + # Get bot root directory + bot_root = os.path.dirname(os.path.abspath(config_file)) + if not bot_root: + bot_root = os.getcwd() + + # Get bot information + bot_name = get_bot_name(config) + admin_commands = get_admin_commands(config) + title = get_website_title(config) + introduction = get_website_intro(config) + + # Get monitor channels + monitor_channels_str = config.get('Channels', 'monitor_channels', fallback='') + monitor_channels = [ch.strip() for ch in monitor_channels_str.split(',') if ch.strip()] + + # Load channels from config + channels_data = load_channels_from_config(config) + + # Setup minimal bot for plugin loading + minimal_bot = MinimalBot(config, logger) + + # Initialize database manager if database exists + db_path = get_database_path(config, bot_root) + if db_path and os.path.exists(db_path): + try: + minimal_bot.db_manager = DBManager(minimal_bot, db_path) + except Exception as e: + logger.warning(f"Could not load database: {e}") + minimal_bot.db_manager = None + else: + minimal_bot.db_manager = None + + # Load plugins + plugin_loader = PluginLoader(minimal_bot) + commands = plugin_loader.load_all_plugins() + + # Filter out admin and hidden commands + public_commands = { + name: cmd for name, cmd in commands.items() + if name not in admin_commands and not getattr(cmd, 'hidden', False) + } + + # Sort commands + sorted_commands = sorted(public_commands.items(), key=lambda x: x[0]) + + # Create website directory + output_dir = os.path.join(bot_root, 'website') + os.makedirs(output_dir, exist_ok=True) + + # Generate a page for each style + generated_files = [] + for style_name in STYLES.keys(): + logger.info(f"Generating {style_name}.html...") + + html_content = generate_html( + bot_name=bot_name, + title=title, + introduction=introduction, + commands=sorted_commands, + monitor_channels=monitor_channels, + channels_data=channels_data, + style=style_name + ) + + output_path = os.path.join(output_dir, f'{style_name}.html') + with open(output_path, 'w', encoding='utf-8') as f: + f.write(html_content) + + generated_files.append(style_name) + logger.info(f" āœ“ {output_path}") + + # Generate index.html with links to all styles + logger.info("Generating index.html...") + + index_html = f""" + + + + + {escape_html(bot_name)} - Style Samples + + + +

{escape_html(bot_name)} Style Samples

+

Browse different visual styles for the command documentation.

+ +
+""" + + for style_name in STYLES.keys(): + style_info = STYLES[style_name] + index_html += f""" +
{escape_html(style_info['name'])}
+
{escape_html(style_info['description'])}
+
+""" + + index_html += """
+ + +""" + + index_path = os.path.join(output_dir, 'index.html') + with open(index_path, 'w', encoding='utf-8') as f: + f.write(index_html) + + logger.info(f" āœ“ {index_path}") + logger.info(f"\nGenerated {len(generated_files)} style samples + index.html in {output_dir}") + print(f"\nāœ“ Sample files generated in {output_dir}/") + print(f" - index.html (style browser)") + for style_name in generated_files: + print(f" - {style_name}.html") + + def main(): """Main function""" logger = setup_logging() - - # Get config file path - config_file = "config.ini" - if len(sys.argv) > 1: - config_file = sys.argv[1] + + # Parse command-line arguments + parser = argparse.ArgumentParser( + description='Generate a static website for the bot commands and channels.', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + 'config', + nargs='?', + default='config.ini', + help='Path to config.ini file (default: config.ini)' + ) + + parser.add_argument( + '--style', + choices=list(STYLES.keys()), + default='default', + help='CSS style theme for the website (default: default)' + ) + + parser.add_argument( + '--list-styles', + action='store_true', + help='List all available styles and exit' + ) + + parser.add_argument( + '--sample', + action='store_true', + help='Generate sample pages for all styles with an index.html' + ) + + args = parser.parse_args() + + # Handle --list-styles flag + if args.list_styles: + list_styles() + sys.exit(0) + + # Handle --sample flag + if args.sample: + generate_samples(args.config) + sys.exit(0) + + config_file = args.config + style = args.style try: # Read config @@ -1662,7 +2736,7 @@ def main(): # Generate HTML logger.info("Generating HTML...") - html_content = generate_html(bot_name, title, introduction, sorted_commands, monitor_channels, channels_data) + html_content = generate_html(bot_name, title, introduction, sorted_commands, monitor_channels, channels_data, style) # Create website directory website_dir = os.path.join(bot_root, "website")