import discord import datetime import random from discord.ext import commands from utils.sql_commands import DatabaseManager import asyncio def random_gradient(): """ Generate a random color in hexadecimal format. Returns: int: A random color in hexadecimal format (e.g., 0xFFFFFF). """ return random.randint(0, 0xFFFFFF) def ordinal(number): """ Convert a number to its ordinal representation (e.g., 1st, 2nd, 3rd). Args: number (int): The number to convert to an ordinal. Returns: str: The ordinal representation of the given number. """ if 10 <= number % 100 <= 20: suffix = "th" else: suffix = {1: "st", 2: "nd", 3: "rd"}.get(number % 10, "th") return f"{number}{suffix}" async def get_channel_id(bot, ctx, allow_blank=False): """ Prompt the user to input a channel ID, allowing for options to skip or remove. Args: bot (discord.Client): The bot instance. ctx (commands.Context): The context in which the command was invoked. allow_blank (bool, optional): If True, allows 'skip' or 'remove' as options. Returns: int or str: The ID of the mentioned channel, "pass" if skipped, or None if removed. """ try: msg = await bot.wait_for( "message", check=lambda message: message.author == ctx.author, timeout=60 ) # Handle blank input options if allow_blank: if msg.content.lower() == "skip": return "pass" if msg.content.lower() == "remove": return None # Check if a channel was mentioned if msg.channel_mentions: return msg.channel_mentions[0].id await ctx.send("Please mention a valid channel.") return await get_channel_id(bot, ctx, allow_blank) except asyncio.TimeoutError: await ctx.send("You took too long to respond. Please run the command again.") return None async def server_join_embed(db: DatabaseManager, member: discord.Member): """ Create a dynamic welcome embed for a new server member. Args: db (DatabaseManager): The database manager instance. member (discord.Member): The new member joining the server. Returns: discord.Embed: A dynamically generated welcome embed for the new member. """ guild_channels = db.fetch_one( "SELECT * FROM guilds WHERE GUILD = %s", (member.guild.id,) ) # Prepare welcome messages welcome_messages = [ f"🌟 Welcome to {member.guild.name}, {member.name}! 🌟", f"🎉 Greetings, {member.name}! Welcome to {member.guild.name}!", f"🚀 Ahoy, {member.name}! Set sail for adventure in {member.guild.name}!", ] selected_message = random.choice(welcome_messages) # Create embed embed = discord.Embed( title=selected_message, description="We're delighted to have you join our community. Enjoy your stay!", color=random_gradient(), ) embed.set_thumbnail(url=member.display_avatar) # Dynamic fields based on available channels fields = [ ( "➡️ Get Started", f"Check out the {get_channel_mention(guild_channels, 'RULES', member)} channel for server rules.", ), ( "📚 Server Guide", f"Explore the server guide in {get_channel_mention(guild_channels, 'GUIDE', member)}.", ), ( "👋 Introduce Yourself", f"Head over to {get_channel_mention(guild_channels, 'INTRODUCTIONS', member)} and let us know a bit about yourself.", ), ( "💼 Join our Events", f"Participate in {get_channel_mention(guild_channels, 'EVENTS', member)} for exciting events!", ), ] for name, value in fields: if value: embed.add_field(name=name, value=value, inline=False) # Member count update if guild_channels.get("MEMBERCOUNT"): member_count_channel = member.guild.get_channel( int(guild_channels["MEMBERCOUNT"]) ) if member_count_channel: await member_count_channel.edit( name=f"Members: {len(member.guild.members)}" ) # Joining position join_position = sorted(member.guild.members, key=lambda m: m.joined_at).index(member) + 1 # type: ignore embed.add_field( name="🏆 Joining Position", value=f"You are the {ordinal(join_position)} member to join!", inline=False, ) # Greeting based on time of day current_time = datetime.datetime.now().time() greeting = ( "Good morning!" if 6 <= current_time.hour < 12 else "Good afternoon!" if 12 <= current_time.hour < 18 else "Good evening!" ) embed.insert_field_at( 0, name="🌞 Time of Day Greeting", value=greeting, inline=False ) return embed def get_channel_mention(guild_channels, key, member): """ Retrieve a mention for a channel based on a key in the guild channels data. Args: guild_channels (dict): The dictionary containing channel IDs. key (str): The key for the channel to mention. member (discord.Member): The member for whom the mention is being created. Returns: str or None: The mention for the channel if found, otherwise None. """ channel_id = guild_channels.get(key) if channel_id: channel = member.guild.get_channel(int(channel_id)) return channel.mention if channel else None return None class Channels(commands.Cog): """Cog that handles channel-related commands and events.""" def __init__(self, client): self.client = client self.db = DatabaseManager() @commands.Cog.listener() async def on_member_join(self, member: discord.Member): """ Event listener for new member joins. Sends a welcome embed if a welcome channel is set. Args: member (discord.Member): The new member joining the server. """ embed = await server_join_embed(self.db, member) # Fetch the welcome channel ID for the given guild result = self.db.fetch_one( "SELECT WELCOME FROM guilds WHERE GUILD = %s", (member.guild.id,) ) # Safely get the WELCOME value, defaulting to None if not found channel_id = result.get("WELCOME") if result else None if channel_id is None: return # No welcome channel set welcome_channel = self.client.get_channel(int(channel_id)) if welcome_channel: await welcome_channel.send(embed=embed) @commands.command( name="setchannels", brief="Setup channel IDs in the database", description="Adds or removes channel IDs to/from the database for use by the bot.", ) @commands.has_permissions(manage_channels=True) async def _channel_setup(self, ctx, specific_channel: str | None = None): """ Command to set up channel IDs in the database by prompting the user. Args: ctx (commands.Context): The context in which the command was invoked. specific_channel (str, optional): The specific channel to set up, or None to set up all channels. """ channels = {} channel_types = [ "Welcome", "Rules", "Guide", "Introductions", "Events", "MemberCount", "Logging", "Ticketing", ] if specific_channel and specific_channel.capitalize() in channel_types: channel_types = [specific_channel.capitalize()] await ctx.reply( "Let's set up the channels.\nPlease provide the channel ID,\nskip to keep it unchanged or\nremove to unset the channel:\n\n" ) # Collect channel IDs for channel_type in channel_types: await ctx.send(f"{len(channels)}. {channel_type} Channel ID:") channels[channel_type] = await get_channel_id( self.client, ctx, allow_blank=True ) # Check if existing entries need updating overwrite = (self.db.fetch_one("SELECT GUILD FROM guilds WHERE GUILD = %s", (ctx.guild.id,))is not None) # Prepare data for the insert or update operation data = tuple( { "GUILD": ctx.guild.id, "WELCOME": channels.get("Welcome"), "RULES": channels.get("Rules"), "GUIDE": channels.get("Guide"), "INTRODUCTIONS": channels.get("Introductions"), "EVENTS": channels.get("Events"), "MEMBERCOUNT": channels.get("MemberCount"), "LOGGING": channels.get("Logging"), "TICKETING": channels.get("Ticketing"), }.values() ) # Insert or update the channels in the database self.db.insert( "INSERT INTO guilds (GUILD, WELCOME, RULES, GUIDE, INTRODUCTIONS, EVENTS, MEMBERCOUNT, LOGGING, TICKETING) " "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) ", data, overwrite=overwrite, ) # Create a response message for the user response = "\n".join( [ f"{channel_type} Channel ID: {get_channel_mention(channels, channel_type, ctx.author)}" for channel_type in channels if channel_type != "pass" ] ) await ctx.send(f"Channels set!\n{response}") @commands.command( name="welcomeTest", brief="Test welcome message", description="Sends a welcome message to the welcome channel.", ) @commands.has_permissions(manage_channels=True) async def _welcome_test(self, ctx): """ Command to test the welcome message in the designated welcome channel. Args: ctx (commands.Context): The context in which the command was invoked. """ member = ctx.author embed = await server_join_embed(self.db, member) channel_id = self.db.fetch_one( "SELECT WELCOME FROM guilds WHERE GUILD=%s", (ctx.guild.id,) ).get("WELCOME") if channel_id is None: await ctx.reply("No Welcome Channel Set") return welcome_channel = self.client.get_channel(int(channel_id)) if welcome_channel: await welcome_channel.send(embed=embed) else: await ctx.reply( "The Welcome Channel is invalid. Please set a valid channel." ) @commands.command( name="autocreatechannels", brief="Auto-create all recommended channels and categories if missing.", description="Creates all recommended channels and categories if they do not exist.", ) @commands.has_permissions(manage_channels=True) async def autocreatechannels(self, ctx): """ Command to auto-create all recommended channels and categories if missing. """ # Recommended categories and their channels categories_and_channels = { "Welcome & Info": [ "welcome", "rules", "server-guide", "introductions", "announcements", ], "Community": [ "general", "off-topic", "media", "bot-commands", "suggestions", "polls", ], "Support": ["help", "faq"], "Events": ["events", "event-signup", "giveaways"], "Moderation": ["mod-log", "reports"], "Voice": ["General Voice", "Music", "AFK"], "Management": ["member-count", "ticketing"], "Staff": ["staff", "staff-announcements", "staff-logs"], } created = [] for category_name, channel_names in categories_and_channels.items(): category = await self.ensure_category(ctx.guild, category_name) for ch_name in channel_names: # For voice channels if ( "voice" in ch_name.lower() or ch_name.lower() == "music" or ch_name.lower() == "afk" ): ch_type = discord.ChannelType.voice else: ch_type = discord.ChannelType.text channel = discord.utils.get(ctx.guild.channels, name=ch_name) if not channel: channel = await self.ensure_channel( ctx.guild, ch_name, type=ch_type, category=category ) created.append(channel.mention) # Set permissions template for some channels if ch_name.lower() == "rules" and isinstance(channel, discord.TextChannel): await self.set_channel_permissions(channel, "rules") if created: await ctx.send(f"Created channels: {', '.join(created)}") else: await ctx.send("All recommended channels already exist.") async def ensure_channel( self, guild: discord.Guild, name, type=discord.ChannelType.text, category=None ): existing = discord.utils.get(guild.channels, name=name) if existing: return existing if type == discord.ChannelType.voice: return await guild.create_voice_channel(name, category=category) else: return await guild.create_text_channel(name, category=category) async def ensure_category(self, guild: discord.Guild, name): existing = discord.utils.get(guild.categories, name=name) if existing: return existing return await guild.create_category(name) async def set_channel_permissions(self, channel: discord.TextChannel, template): if template == "rules": await channel.set_permissions( channel.guild.default_role, send_messages=False ) # Add more permission logic as needed @commands.command() @commands.has_permissions(manage_channels=True) async def cleanup_channels(self, ctx): unused = [ch for ch in ctx.guild.text_channels if ch.last_message_id is None] for ch in unused: await ch.delete(reason="Channel cleanup") await ctx.send(f"Deleted {len(unused)} unused channels.") @commands.command() async def channelinfo(self, ctx, channel: discord.TextChannel | None = None): channel = channel or ctx.channel embed = discord.Embed(title=f"Info for #{channel.name}") embed.add_field(name="ID", value=channel.id) embed.add_field(name="Created", value=channel.created_at) embed.add_field(name="Topic", value=channel.topic or "None") embed.add_field(name="Slowmode", value=channel.slowmode_delay) await ctx.send(embed=embed) async def setup(client): """ Setup function to load the Channels cog. Args: client (commands.Bot): The bot instance. """ await client.add_cog(Channels(client))