Enhance ticketing and modal handling in cogs

- Improved exception handling in `create_ticket` for better user feedback.
- Added "Apply for PM Position" button in `WorkView` to facilitate PM applications.
- Updated `Hiring` class to manage guild settings and ensure persistent views.
- Restructured `OrderModal` and `ReviewModal` in `KofiShop` for improved user experience and error handling.
- Refactored `ModMail` class for better thread management and added ticket closure functionality with logging.
- Converted several commands in `KofiShop` from hybrid to app commands for better interaction.
- Enhanced overall code structure for readability and maintainability.
This commit is contained in:
2025-09-23 02:40:35 -04:00
parent 07b436ed8c
commit 500c7daaae
3 changed files with 153 additions and 288 deletions

View File

@@ -3,92 +3,81 @@ from redbot.core import commands, Config
from redbot.core.bot import Red
from typing import Optional
# --- Modals for the Commands ---
class OrderModal(discord.ui.Modal, title="Commission Order Form"):
commission_type = discord.ui.TextInput(label="What type of commission?")
payment_status = discord.ui.TextInput(label="Is this a Free or Paid commission?")
description = discord.ui.TextInput(label="Description", style=discord.TextStyle.paragraph)
questions = discord.ui.TextInput(label="Any questions?", style=discord.TextStyle.paragraph, required=False)
class OrderModal(discord.ui.Modal, title="Commission/Shop Order"):
def __init__(self, cog: "KofiShop"):
super().__init__()
self.cog = cog
comm_type = discord.ui.TextInput(label="What type of commission/item is this?")
payment_status = discord.ui.TextInput(label="Is this free or paid?")
description = discord.ui.TextInput(label="Please describe your request.", style=discord.TextStyle.paragraph)
questions = discord.ui.TextInput(label="Any questions for the artist?", style=discord.TextStyle.paragraph, required=False)
async def on_submit(self, interaction: discord.Interaction):
if not interaction.guild:
return await interaction.response.send_message("This must be used in a server.", ephemeral=True)
guild = interaction.guild
if not guild:
return
order_channel_id = await self.cog.config.guild(interaction.guild).order_channel()
if not order_channel_id:
return await interaction.response.send_message("The order channel has not been set by an admin.", ephemeral=True)
channel_id = await self.cog.config.guild(guild).order_channel()
if not channel_id:
await interaction.response.send_message("Order channel not set.", ephemeral=True)
return
order_channel = interaction.guild.get_channel(order_channel_id)
if not isinstance(order_channel, discord.TextChannel):
return await interaction.response.send_message("The configured order channel is invalid.", ephemeral=True)
channel = guild.get_channel(channel_id)
if not isinstance(channel, discord.TextChannel):
await interaction.response.send_message("Invalid order channel.", ephemeral=True)
return
embed = discord.Embed(
title="New Order Placed",
description=f"Submitted by {interaction.user.mention}",
color=0x00ff00 # Green for new orders
)
embed.add_field(name="Item/Commission Type", value=self.comm_type.value, inline=False)
embed = discord.Embed(title=f"New Order from {interaction.user.name}", color=discord.Color.blurple())
embed.add_field(name="Commission Type", value=self.commission_type.value, inline=False)
embed.add_field(name="Payment Status", value=self.payment_status.value, inline=False)
embed.add_field(name="Description", value=self.description.value, inline=False)
if self.questions.value:
embed.add_field(name="Questions", value=self.questions.value, inline=False)
try:
await order_channel.send(embed=embed)
await interaction.response.send_message("Your order has been successfully submitted!", ephemeral=True)
except discord.Forbidden:
await interaction.response.send_message("I don't have permission to send messages in the order channel.", ephemeral=True)
await channel.send(embed=embed)
await interaction.response.send_message("Your order has been submitted!", ephemeral=True)
class ReviewModal(discord.ui.Modal, title="Shop Review"):
item_name = discord.ui.TextInput(label="What item/commission are you reviewing?")
rating = discord.ui.TextInput(label="Rating (out of 10)", max_length=2)
review_text = discord.ui.TextInput(label="Your Review", style=discord.TextStyle.paragraph, max_length=1000)
class ReviewModal(discord.ui.Modal, title="Leave a Review"):
def __init__(self, cog: "KofiShop"):
super().__init__()
self.cog = cog
item_name = discord.ui.TextInput(label="What item/commission are you reviewing?")
rating = discord.ui.TextInput(label="Rating (e.g., 10/10)")
review_text = discord.ui.TextInput(label="Your Review", style=discord.TextStyle.paragraph)
async def on_submit(self, interaction: discord.Interaction):
if not interaction.guild:
return await interaction.response.send_message("This must be used in a server.", ephemeral=True)
guild = interaction.guild
if not guild:
return
review_channel_id = await self.cog.config.guild(interaction.guild).review_channel()
if not review_channel_id:
return await interaction.response.send_message("The review channel has not been set by an admin.", ephemeral=True)
channel_id = await self.cog.config.guild(guild).review_channel()
if not channel_id:
await interaction.response.send_message("Review channel not set.", ephemeral=True)
return
review_channel = interaction.guild.get_channel(review_channel_id)
if not isinstance(review_channel, discord.TextChannel):
return await interaction.response.send_message("The configured review channel is invalid.", ephemeral=True)
channel = guild.get_channel(channel_id)
if not isinstance(channel, discord.TextChannel):
await interaction.response.send_message("Invalid review channel.", ephemeral=True)
return
embed = discord.Embed(
title=f"New Review for: {self.item_name.value}",
description=f"Submitted by {interaction.user.mention}",
color=0xadd8e6 # Pastel Blue
)
embed.add_field(name="Rating", value=self.rating.value, inline=False)
embed = discord.Embed(title=f"New Review for {self.item_name.value}", color=discord.Color.gold())
embed.set_author(name=interaction.user.name, icon_url=interaction.user.display_avatar.url)
embed.add_field(name="Rating", value=f"{self.rating.value}/10")
embed.add_field(name="Review", value=self.review_text.value, inline=False)
try:
await review_channel.send(embed=embed)
await interaction.response.send_message("Thank you! Your review has been submitted.", ephemeral=True)
except discord.Forbidden:
await interaction.response.send_message("I don't have permission to send messages in the review channel.", ephemeral=True)
await channel.send(embed=embed)
await interaction.response.send_message("Thank you for your review!", ephemeral=True)
class KofiShop(commands.Cog):
"""
A cog to manage Ko-fi shop orders and reviews.
An interactive front-end for a Ko-fi store.
"""
def __init__(self, bot: Red):
def __init__(self, bot: "Red"):
self.bot = bot
self.config = Config.get_conf(self, identifier=5566778899, force_registration=True)
self.config = Config.get_conf(self, identifier=1234567894, force_registration=True)
default_guild = {
"order_channel": None,
"review_channel": None,
@@ -96,21 +85,29 @@ class KofiShop(commands.Cog):
}
self.config.register_guild(**default_guild)
# --- Commands ---
@commands.hybrid_command()
@commands.guild_only()
async def order(self, ctx: commands.Context):
"""Place an order for a shop or commission item."""
if not ctx.interaction:
return
# We pass `self` (the cog instance) to the modal
await ctx.interaction.response.send_modal(OrderModal(self))
@app_commands.command()
@app_commands.guild_only()
async def order(self, interaction: discord.Interaction):
"""Place an order for a commission."""
await interaction.response.send_modal(OrderModal(self))
@commands.hybrid_command()
@commands.guild_only()
async def review(self, ctx: commands.Context):
"""Leave a review for a completed shop or commission item."""
if not ctx.interaction:
@app_commands.command()
@app_commands.guild_only()
async def review(self, interaction: discord.Interaction):
"""Leave a review for a completed commission."""
await interaction.response.send_modal(ReviewModal(self))
@app_commands.command()
@app_commands.guild_only()
async def waitlist(self, interaction: discord.Interaction, *, item: str):
"""Add an item to the waitlist."""
guild = interaction.guild
if not guild:
return
channel_id = await self.config.guild(guild).waitlist_channel()
if not channel_id:
await interaction.response.send_message("Waitlist channel not set.", ephemeral=True)
return
await ctx.interaction.response.send_modal(ReviewModal(self))
@@ -132,44 +129,45 @@ class KofiShop(commands.Cog):
message = f"**{item}** ིྀ {user.mention} ✧ in {ctx.channel.mention}"
try:
await waitlist_channel.send(message)
await ctx.send(f"{user.mention} has been added to the waitlist for '{item}'.", ephemeral=True)
except discord.Forbidden:
await ctx.send(f"I don't have permission to send messages in the waitlist channel.", ephemeral=True)
channel = guild.get_channel(channel_id)
if not isinstance(channel, discord.TextChannel):
await interaction.response.send_message("Invalid waitlist channel.", ephemeral=True)
return
embed = discord.Embed(description=f"{item} ིྀ {interaction.user.mention}{interaction.channel.mention if isinstance(interaction.channel, discord.TextChannel) else ''}")
await channel.send(embed=embed)
await interaction.response.send_message("You have been added to the waitlist!", ephemeral=True)
# --- Settings Commands ---
@commands.group(aliases=["kset"]) # type: ignore
@commands.guild_only()
@commands.admin_or_permissions(manage_guild=True)
async def kofiset(self, ctx: commands.Context):
"""
Configure the KofiShop settings.
"""
"""Configure KofiShop settings."""
pass
@kofiset.command(name="orderchannel")
async def kofiset_order(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel where new orders will be sent."""
if not ctx.guild: return
async def set_order_channel(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel for new orders."""
if not ctx.guild:
return
await self.config.guild(ctx.guild).order_channel.set(channel.id)
await ctx.send(f"Order channel has been set to {channel.mention}.")
await ctx.send(f"Order channel set to {channel.mention}")
@kofiset.command(name="reviewchannel")
async def kofiset_review(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel where new reviews will be sent."""
if not ctx.guild: return
async def set_review_channel(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel for new reviews."""
if not ctx.guild:
return
await self.config.guild(ctx.guild).review_channel.set(channel.id)
await ctx.send(f"Review channel has been set to {channel.mention}.")
await ctx.send(f"Review channel set to {channel.mention}")
@kofiset.command(name="waitlistchannel")
async def kofiset_waitlist(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel where waitlist notifications will be sent."""
if not ctx.guild: return
async def set_waitlist_channel(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel for waitlist notifications."""
if not ctx.guild:
return
await self.config.guild(ctx.guild).waitlist_channel.set(channel.id)
await ctx.send(f"Waitlist channel has been set to {channel.mention}.")
await ctx.send(f"Waitlist channel set to {channel.mention}")
async def setup(bot: Red):
async def setup(bot: "Red"):
await bot.add_cog(KofiShop(bot))