feat: Add Hiring cog for ticket-based applications
Introduces a Discord cog to manage staff and PM hiring through interactive tickets, forms, and buttons. Includes modals for Staff, PM, and HPM applications, as well as commands for posting hiring messages and configuring categories and channels.
This commit is contained in:
253
hiring/hiring.py
Normal file
253
hiring/hiring.py
Normal file
@@ -0,0 +1,253 @@
|
||||
import discord
|
||||
from redbot.core import commands, Config
|
||||
from redbot.core.bot import Red
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from hiring.hiring import Hiring
|
||||
|
||||
# --- 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 Link")
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
embed = discord.Embed(
|
||||
title="New Staff Application",
|
||||
description=f"Submitted by {interaction.user.mention}",
|
||||
color=0xadd8e6
|
||||
)
|
||||
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 interaction.response.send_message("Your application has been submitted!", ephemeral=True)
|
||||
if isinstance(interaction.channel, discord.TextChannel):
|
||||
await interaction.channel.send(embed=embed)
|
||||
|
||||
|
||||
class PMApplicationModal(discord.ui.Modal, title="PM Application"):
|
||||
ad = discord.ui.TextInput(label="Please provide your server ad.", style=discord.TextStyle.paragraph)
|
||||
reqs = discord.ui.TextInput(label="What are your requirements?")
|
||||
tox_level = discord.ui.TextInput(label="What is your toxicity level tolerance?")
|
||||
chosen_plan = discord.ui.TextInput(label="What plan are you interested in?")
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
embed = discord.Embed(
|
||||
title="New PM Application",
|
||||
description=f"Submitted by {interaction.user.mention}",
|
||||
color=0xadd8e6
|
||||
)
|
||||
embed.add_field(name="Server Ad", value=f"```\n{self.ad.value}\n```", inline=False)
|
||||
embed.add_field(name="Requirements", value=self.reqs.value, 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 interaction.response.send_message("Your application has been submitted!", ephemeral=True)
|
||||
if isinstance(interaction.channel, discord.TextChannel):
|
||||
await interaction.channel.send(embed=embed)
|
||||
|
||||
class HPMApplicationModal(discord.ui.Modal, title="HPM Application"):
|
||||
ad = discord.ui.TextInput(label="Please provide your server ad.", style=discord.TextStyle.paragraph)
|
||||
reqs = discord.ui.TextInput(label="What are your requirements?")
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
embed = discord.Embed(
|
||||
title="New HPM Application",
|
||||
description=f"Submitted by {interaction.user.mention}",
|
||||
color=0xadd8e6
|
||||
)
|
||||
embed.add_field(name="Server Ad", value=f"```\n{self.ad.value}\n```", inline=False)
|
||||
embed.add_field(name="Requirements", value=self.reqs.value, inline=False)
|
||||
await interaction.response.send_message("Your application has been submitted!", ephemeral=True)
|
||||
if isinstance(interaction.channel, discord.TextChannel):
|
||||
await interaction.channel.send(embed=embed)
|
||||
|
||||
|
||||
# --- Reusable Ticket Creation Logic ---
|
||||
async def create_ticket(interaction: discord.Interaction, role_type: str, category_id: Optional[int], modal: discord.ui.Modal):
|
||||
if not interaction.guild:
|
||||
await interaction.response.send_message("This interaction must be used in a server.", ephemeral=True)
|
||||
return
|
||||
|
||||
if not category_id:
|
||||
await interaction.response.send_message(f"The category for '{role_type}' applications has not been set by an admin.", ephemeral=True)
|
||||
return
|
||||
|
||||
category = interaction.guild.get_channel(category_id)
|
||||
if not isinstance(category, discord.CategoryChannel):
|
||||
await interaction.response.send_message(f"The category for '{role_type}' applications is invalid or has been deleted.", ephemeral=True)
|
||||
return
|
||||
|
||||
ticket_name = f"{role_type}-app-{interaction.user.name}"
|
||||
|
||||
try:
|
||||
overwrites = {
|
||||
interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
||||
interaction.user: discord.PermissionOverwrite(read_messages=True, send_messages=True),
|
||||
interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True)
|
||||
}
|
||||
ticket_channel = await interaction.guild.create_text_channel(
|
||||
name=ticket_name,
|
||||
category=category,
|
||||
overwrites=overwrites
|
||||
)
|
||||
await ticket_channel.send(f"Welcome {interaction.user.mention}! Please fill out the form to complete your application.")
|
||||
await interaction.response.send_modal(modal)
|
||||
|
||||
except discord.Forbidden:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send("I don't have permission to create channels in that category.", ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message("I don't have permission to create channels in that category.", ephemeral=True)
|
||||
except Exception as e:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(f"An unexpected error occurred: {e}", ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message(f"An unexpected error occurred: {e}", ephemeral=True)
|
||||
|
||||
|
||||
# --- Button Views for Commands ---
|
||||
|
||||
class HireView(discord.ui.View):
|
||||
def __init__(self, cog: "Hiring"):
|
||||
super().__init__(timeout=None)
|
||||
self.cog = cog
|
||||
|
||||
@discord.ui.button(label="Staff", style=discord.ButtonStyle.primary, custom_id="staff_apply_button")
|
||||
async def staff_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if not interaction.guild: return
|
||||
category_id = await self.cog.config.guild(interaction.guild).staff_category()
|
||||
await create_ticket(interaction, "staff", category_id, StaffApplicationModal())
|
||||
|
||||
@discord.ui.button(label="PM", style=discord.ButtonStyle.secondary, custom_id="pm_apply_button")
|
||||
async def pm_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if not interaction.guild: return
|
||||
category_id = await self.cog.config.guild(interaction.guild).pm_category()
|
||||
await create_ticket(interaction, "pm", category_id, PMApplicationModal())
|
||||
|
||||
@discord.ui.button(label="HPM", style=discord.ButtonStyle.success, custom_id="hpm_apply_button")
|
||||
async def hpm_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if not interaction.guild: return
|
||||
category_id = await self.cog.config.guild(interaction.guild).hpm_category()
|
||||
await create_ticket(interaction, "hpm", category_id, HPMApplicationModal())
|
||||
|
||||
|
||||
class WorkView(discord.ui.View):
|
||||
def __init__(self, cog: "Hiring"):
|
||||
super().__init__(timeout=None)
|
||||
self.cog = cog
|
||||
|
||||
@discord.ui.button(label="Apply for PM Position", style=discord.ButtonStyle.blurple, custom_id="work_apply_button")
|
||||
async def work_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if not interaction.guild: return
|
||||
category_id = await self.cog.config.guild(interaction.guild).pm_category()
|
||||
await create_ticket(interaction, "pm", category_id, PMApplicationModal())
|
||||
|
||||
|
||||
class Hiring(commands.Cog):
|
||||
"""
|
||||
A cog for managing staff and PM applications.
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=1122334455, force_registration=True)
|
||||
|
||||
default_guild = {
|
||||
"hpm_category": None,
|
||||
"pm_category": None,
|
||||
"staff_category": None,
|
||||
"work_channel": None # New setting for the /work command channel
|
||||
}
|
||||
self.config.register_guild(**default_guild)
|
||||
# We need to make sure the views are persistent so buttons work after a restart
|
||||
self.bot.add_view(HireView(self))
|
||||
self.bot.add_view(WorkView(self))
|
||||
|
||||
|
||||
# --- Commands ---
|
||||
@commands.hybrid_command() # type: ignore
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def hire(self, ctx: commands.Context):
|
||||
"""Post the application message with buttons in the current channel."""
|
||||
embed = discord.Embed(
|
||||
title="Start an Application",
|
||||
description="Click a button below to open a ticket for the role you are interested in.",
|
||||
color=0xadd8e6
|
||||
)
|
||||
await ctx.send(embed=embed, view=HireView(self))
|
||||
|
||||
@commands.hybrid_command() # type: ignore
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def work(self, ctx: commands.Context):
|
||||
"""Post the PM hiring message in the configured work channel."""
|
||||
if not ctx.guild:
|
||||
return
|
||||
work_channel_id = await self.config.guild(ctx.guild).work_channel()
|
||||
if not work_channel_id:
|
||||
await ctx.send("The work channel has not been set. Please use `[p]hiringset workchannel` to set it.", ephemeral=True)
|
||||
return
|
||||
|
||||
work_channel = ctx.guild.get_channel(work_channel_id)
|
||||
if not isinstance(work_channel, discord.TextChannel):
|
||||
await ctx.send("The configured work channel is invalid or has been deleted.", ephemeral=True)
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Now Hiring: Partnership Managers",
|
||||
description="We are for talented Partnership Managers (PMs) to join our team. If you are interested in applying, please click the button below to begin the application process.",
|
||||
color=0xadd8e6
|
||||
)
|
||||
try:
|
||||
await work_channel.send(embed=embed, view=WorkView(self))
|
||||
await ctx.send(f"Hiring message posted in {work_channel.mention}.", ephemeral=True)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(f"I don't have permission to send messages in {work_channel.mention}.", ephemeral=True)
|
||||
|
||||
|
||||
# --- Settings Commands ---
|
||||
@commands.group(aliases=["hset"]) # type: ignore
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def hiringset(self, ctx: commands.Context):
|
||||
"""Configure the Hiring cog 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 where the /work hiring message will be posted."""
|
||||
if not ctx.guild: return
|
||||
await self.config.guild(ctx.guild).work_channel.set(channel.id)
|
||||
await ctx.send(f"The work channel has been set to {channel.mention}.")
|
||||
|
||||
|
||||
async def setup(bot: Red):
|
||||
await bot.add_cog(Hiring(bot))
|
||||
|
||||
Reference in New Issue
Block a user