""" Server Banner Management Cog - Allows servers to set custom banners """ from __future__ import annotations import sqlite3 from urllib.parse import urlparse import discord from discord.ext import commands from discord import app_commands from bot.theme import NEON_CYAN, NEON_GOLD, NEON_LIME, NEON_RED, add_banner_to_embed from bot.emojis import ui class SetBannerModal(discord.ui.Modal, title="🖼️ Set Server Banner"): banner_url = discord.ui.TextInput( label="Banner Image URL", placeholder="https://example.com/banner.png", required=True, min_length=5, max_length=500, ) def __init__(self, cog: "BannerManager"): super().__init__() self.cog = cog async def on_submit(self, interaction: discord.Interaction) -> None: url = self.banner_url.value.strip().strip("<>") # Validate URL if not self.cog._looks_like_image_url(url): await interaction.response.send_message( "Invalid image URL. Use a public http(s) link ending with png/jpg/jpeg/webp/gif.", ephemeral=True, ) return guild_id = interaction.guild.id if interaction.guild else 0 await self.cog._ensure_schema() # Save to database await self.cog.bot.db.execute( "INSERT INTO guild_config(guild_id, custom_banner_url) VALUES (?, ?) " "ON CONFLICT(guild_id) DO UPDATE SET custom_banner_url = excluded.custom_banner_url", guild_id, url, ) embed = discord.Embed( title="✅ Banner Set Successfully!", description=f"Your custom banner has been saved for **{interaction.guild.name}**.\n\nThe banner will now appear in all bot panels and commands.", color=NEON_LIME, ) embed.set_image(url=url) embed.set_footer(text="Use /remove_banner to remove the custom banner") await interaction.response.send_message(embed=embed, ephemeral=True) class BannerManager(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot self._schema_checked = False async def _ensure_schema(self) -> None: if self._schema_checked: return try: await self.bot.db.execute( "ALTER TABLE guild_config ADD COLUMN custom_banner_url TEXT" ) except sqlite3.OperationalError as exc: if "duplicate column name" not in str(exc).lower(): raise self._schema_checked = True @staticmethod def _looks_like_image_url(url: str) -> bool: try: parsed = urlparse(url) except Exception: return False if parsed.scheme not in {"http", "https"} or not parsed.netloc: return False path = (parsed.path or "").lower() return path.endswith((".png", ".jpg", ".jpeg", ".webp", ".gif")) @app_commands.command(name="set_banner", description="Set a custom banner for this server") @app_commands.checks.has_permissions(manage_guild=True) async def set_banner(self, interaction: discord.Interaction) -> None: """Set a custom banner URL for the server.""" if not interaction.guild: await interaction.response.send_message("❌ This command can only be used in a server.", ephemeral=True) return await self._ensure_schema() await interaction.response.send_modal(SetBannerModal(self)) @app_commands.command(name="remove_banner", description="Remove the custom banner from this server") @app_commands.checks.has_permissions(manage_guild=True) async def remove_banner(self, interaction: discord.Interaction) -> None: """Remove the custom banner from the server.""" if not interaction.guild: await interaction.response.send_message("❌ This command can only be used in a server.", ephemeral=True) return await self._ensure_schema() guild_id = interaction.guild.id await self.bot.db.execute( "UPDATE guild_config SET custom_banner_url = NULL WHERE guild_id = ?", guild_id, ) embed = discord.Embed( title="🗑️ Banner Removed", description=f"The custom banner has been removed from **{interaction.guild.name}**.\n\nThe bot will now use the default Discord banner (if available).", color=NEON_RED, ) await interaction.response.send_message(embed=embed, ephemeral=True) @app_commands.command(name="view_banner", description="View the current custom banner for this server") async def view_banner(self, interaction: discord.Interaction) -> None: """View the current custom banner.""" if not interaction.guild: await interaction.response.send_message("❌ This command can only be used in a server.", ephemeral=True) return await self._ensure_schema() guild_id = interaction.guild.id row = await self.bot.db.fetchone( "SELECT custom_banner_url FROM guild_config WHERE guild_id = ?", guild_id, ) custom_banner = row[0] if row else None discord_banner = interaction.guild.banner.url if interaction.guild.banner else None embed = discord.Embed( title="🖼️ Server Banner Status", color=NEON_CYAN, ) if custom_banner: embed.add_field( name="✅ Custom Banner", value=f"A custom banner is set for this server.\n[View Banner]({custom_banner})", inline=False, ) embed.set_image(url=custom_banner) else: embed.add_field( name="❌ No Custom Banner", value="No custom banner is set. Use `/set_banner` to add one.", inline=False, ) if discord_banner: embed.add_field( name="🎨 Discord Banner", value=f"Discord banner is available.\n[View Banner]({discord_banner})", inline=False, ) else: embed.add_field( name="⚪ No Discord Banner", value="No Discord banner is set. Upgrade your server to add one!", inline=False, ) embed.set_footer(text="Custom banner takes priority over Discord banner") await interaction.response.send_message(embed=embed, ephemeral=True) @app_commands.command(name="banner_help", description="Learn how to set a custom banner") async def banner_help(self, interaction: discord.Interaction) -> None: """Show help for banner commands.""" embed = discord.Embed( title="🖼️ Custom Banner Help", description=( "Set a custom banner for your server that will appear in all bot panels and commands.\n\n" "**Commands:**\n" "📌 `/set_banner` - Set a custom banner URL\n" "🗑️ `/remove_banner` - Remove the custom banner\n" "👁️ `/view_banner` - View current banner status\n\n" "**Requirements:**\n" "• Manage Server permission required\n" "• Image URL must be publicly accessible\n" "• Recommended size: 960x540 (16:9 ratio)\n" "• Max file size: 10MB\n" "• Supported formats: PNG, JPG, GIF" ), color=NEON_GOLD, ) embed.add_field( name="💡 Tips", value=( "• Use high-quality images for best results\n" "• Make sure the image URL is public (not private)\n" "• Test with Imgur, Discord CDN, or other public hosts\n" "• Custom banner overrides Discord's native banner" ), inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) async def setup(bot: commands.Bot) -> None: await bot.add_cog(BannerManager(bot))