import discord from redbot.core import commands, Config, app_commands from typing import Literal, Optional, TYPE_CHECKING, Type import datetime if TYPE_CHECKING: from redbot.core.bot import Red # --- Modals for the Application Forms --- class StaffApplicationModal(discord.ui.Modal, title="Staff Application"): chosen_plan = discord.ui.TextInput(label="What plan are you interested in?") tox_level = discord.ui.TextInput(label="What is your toxicity level tolerance?") describe_server = discord.ui.TextInput(label="Please describe your server.", style=discord.TextStyle.paragraph) server_link = discord.ui.TextInput(label="Server Invite Link") def __init__(self, ticket_channel: discord.TextChannel): super().__init__() self.ticket_channel = ticket_channel async def on_submit(self, interaction: discord.Interaction): cog: Optional["Hiring"] = interaction.client.get_cog("Hiring") # type: ignore if not cog: await interaction.response.send_message("Hiring cog not loaded.", ephemeral=True) return embed = discord.Embed(title="New Staff Application", color=discord.Color.blue()) embed.set_author(name=interaction.user.name, icon_url=interaction.user.display_avatar.url) embed.add_field(name="Chosen Plan", value=self.chosen_plan.value, inline=False) embed.add_field(name="Toxicity Level", value=self.tox_level.value, inline=False) embed.add_field(name="Server Description", value=self.describe_server.value, inline=False) embed.add_field(name="Server Link", value=self.server_link.value, inline=False) await self.ticket_channel.send(embed=embed) await interaction.response.send_message("Your staff application has been submitted.", ephemeral=True) class PMApplicationModal(discord.ui.Modal, title="PM Application"): ad = discord.ui.TextInput(label="Your Ad", style=discord.TextStyle.paragraph) reqs = discord.ui.TextInput(label="Your Requirements", style=discord.TextStyle.paragraph) tox_level = discord.ui.TextInput(label="What is your toxicity level tolerance?") chosen_plan = discord.ui.TextInput(label="What plan are you interested in?") def __init__(self, ticket_channel: discord.TextChannel): super().__init__() self.ticket_channel = ticket_channel async def on_submit(self, interaction: discord.Interaction): cog: Optional["Hiring"] = interaction.client.get_cog("Hiring") # type: ignore if not cog: await interaction.response.send_message("Hiring cog not loaded.", ephemeral=True) return embed = discord.Embed(title="New PM Application", color=discord.Color.green()) embed.set_author(name=interaction.user.name, icon_url=interaction.user.display_avatar.url) embed.add_field(name="Ad", value=f"```\n{self.ad.value}\n```", inline=False) embed.add_field(name="Requirements", value=f"```\n{self.reqs.value}\n```", inline=False) embed.add_field(name="Toxicity Level", value=self.tox_level.value, inline=False) embed.add_field(name="Chosen Plan", value=self.chosen_plan.value, inline=False) await self.ticket_channel.send(embed=embed) await interaction.response.send_message("Your PM application has been submitted.", ephemeral=True) class HPMApplicationModal(discord.ui.Modal, title="HPM Application"): ad = discord.ui.TextInput(label="Your Ad", style=discord.TextStyle.paragraph) reqs = discord.ui.TextInput(label="Your Requirements", style=discord.TextStyle.paragraph) def __init__(self, ticket_channel: discord.TextChannel): super().__init__() self.ticket_channel = ticket_channel async def on_submit(self, interaction: discord.Interaction): cog: Optional["Hiring"] = interaction.client.get_cog("Hiring") # type: ignore if not cog: await interaction.response.send_message("Hiring cog not loaded.", ephemeral=True) return embed = discord.Embed(title="New HPM Application", color=discord.Color.purple()) embed.set_author(name=interaction.user.name, icon_url=interaction.user.display_avatar.url) embed.add_field(name="Ad", value=f"```\n{self.ad.value}\n```", inline=False) embed.add_field(name="Requirements", value=f"```\n{self.reqs.value}\n```", inline=False) await self.ticket_channel.send(embed=embed) await interaction.response.send_message("Your HPM application has been submitted.", ephemeral=True) # --- Views with Buttons --- async def create_ticket(interaction: discord.Interaction, ticket_type: str, modal_class: Type[discord.ui.Modal]): """Helper function to create a ticket, used by multiple views.""" cog: Optional["Hiring"] = interaction.client.get_cog("Hiring") # type: ignore if not cog: await interaction.response.send_message("The Hiring cog is not loaded.", ephemeral=True) return guild = interaction.guild if not guild: await interaction.response.send_message("This action can only be performed in a server.", ephemeral=True) return category_id_raw = await cog.config.guild(guild).get_raw(f"{ticket_type}_category") if not isinstance(category_id_raw, int): await interaction.response.send_message(f"The category for '{ticket_type}' applications has not been set correctly.", ephemeral=True) return category = guild.get_channel(category_id_raw) if not isinstance(category, discord.CategoryChannel): await interaction.response.send_message(f"The configured category for '{ticket_type}' is invalid.", ephemeral=True) return assert isinstance(interaction.user, discord.Member) try: thread_name = f"{ticket_type}-application-{interaction.user.name}" overwrites = { guild.default_role: discord.PermissionOverwrite(read_messages=False), interaction.user: discord.PermissionOverwrite(read_messages=True, send_messages=True) } ticket_channel = await category.create_text_channel(name=thread_name, overwrites=overwrites) modal_instance = modal_class(ticket_channel) # type: ignore await interaction.response.send_modal(modal_instance) async with cog.config.guild(guild).closed_applications() as closed_apps: closed_apps[str(ticket_channel.id)] = datetime.datetime.now(datetime.timezone.utc).isoformat() except discord.Forbidden: if not interaction.response.is_done(): await interaction.response.send_message("I don't have permission to create channels in that category.", ephemeral=True) except Exception as e: if not interaction.response.is_done(): await interaction.response.send_message(f"An unexpected error occurred: {e}", ephemeral=True) class HireView(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.button(label="Staff", style=discord.ButtonStyle.primary, custom_id="staff_apply_button_persistent") async def staff_button(self, interaction: discord.Interaction, button: discord.ui.Button): await create_ticket(interaction, "staff", StaffApplicationModal) @discord.ui.button(label="PM", style=discord.ButtonStyle.secondary, custom_id="pm_apply_button_persistent") async def pm_button(self, interaction: discord.Interaction, button: discord.ui.Button): await create_ticket(interaction, "pm", PMApplicationModal) @discord.ui.button(label="HPM", style=discord.ButtonStyle.success, custom_id="hpm_apply_button_persistent") async def hpm_button(self, interaction: discord.Interaction, button: discord.ui.Button): await create_ticket(interaction, "hpm", HPMApplicationModal) class WorkView(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.button(label="Apply for PM Position", style=discord.ButtonStyle.green, custom_id="work_apply_pm_persistent") async def work_apply_button(self, interaction: discord.Interaction, button: discord.ui.Button): await create_ticket(interaction, "pm", PMApplicationModal) # --- Main Cog Class --- class Hiring(commands.Cog): """ A cog for handling staff hiring applications. """ def __init__(self, bot: "Red"): self.bot = bot self.config = Config.get_conf(self, identifier=1234567891, force_registration=True) default_guild = { "staff_category": None, "pm_category": None, "hpm_category": None, "work_channel": None, "closed_applications": {} } self.config.register_guild(**default_guild) async def cog_load(self): self.bot.add_view(HireView()) self.bot.add_view(WorkView()) @commands.hybrid_command() # type: ignore @app_commands.guild_only() @commands.admin_or_permissions(manage_guild=True) async def hire(self, ctx: commands.Context): """Sends the hiring application view.""" view = HireView() embed = discord.Embed( title="Hiring Applications", description="Please select the position you are applying for below.", color=await ctx.embed_color() ) await ctx.send(embed=embed, view=view) @commands.hybrid_command() # type: ignore @app_commands.guild_only() @commands.admin_or_permissions(manage_guild=True) async def work(self, ctx: commands.Context): """Posts the persistent PM application button.""" guild = ctx.guild if not guild: return work_channel_id = await self.config.guild(guild).work_channel() if not work_channel_id: await ctx.send("The work channel has not been set. Please use `/hiringset workchannel`.") return channel = guild.get_channel(work_channel_id) if not isinstance(channel, discord.TextChannel): await ctx.send("The configured work channel is invalid.") return view = WorkView() embed = discord.Embed( title="Partnership Manager Applications", description="Click the button below to apply for a Partnership Manager (PM) position.", color=discord.Color.green() ) try: await channel.send(embed=embed, view=view) await ctx.send(f"Application message posted in {channel.mention}.", ephemeral=True) except discord.Forbidden: await ctx.send("I don't have permission to send messages in that channel.", ephemeral=True) @commands.group(aliases=["hset"]) # type: ignore @commands.guild_only() @commands.admin_or_permissions(manage_guild=True) async def hiringset(self, ctx: commands.Context): """Configure Hiring settings.""" pass @hiringset.command(name="staffcategory") async def set_staff_category(self, ctx: commands.Context, category: discord.CategoryChannel): """Set the category for Staff applications.""" if not ctx.guild: return await self.config.guild(ctx.guild).staff_category.set(category.id) await ctx.send(f"Staff application category set to **{category.name}**.") @hiringset.command(name="pmcategory") async def set_pm_category(self, ctx: commands.Context, category: discord.CategoryChannel): """Set the category for PM applications.""" if not ctx.guild: return await self.config.guild(ctx.guild).pm_category.set(category.id) await ctx.send(f"PM application category set to **{category.name}**.") @hiringset.command(name="hpmcategory") async def set_hpm_category(self, ctx: commands.Context, category: discord.CategoryChannel): """Set the category for HPM applications.""" if not ctx.guild: return await self.config.guild(ctx.guild).hpm_category.set(category.id) await ctx.send(f"HPM application category set to **{category.name}**.") @hiringset.command(name="workchannel") async def set_work_channel(self, ctx: commands.Context, channel: discord.TextChannel): """Set the channel for the /work command announcements.""" if not ctx.guild: return await self.config.guild(ctx.guild).work_channel.set(channel.id) await ctx.send(f"Work announcement channel set to {channel.mention}.") async def setup(bot): await bot.add_cog(Hiring(bot))