aboutsummaryrefslogtreecommitdiff
path: root/cogs/commissions.py
diff options
context:
space:
mode:
Diffstat (limited to 'cogs/commissions.py')
-rw-r--r--cogs/commissions.py327
1 files changed, 327 insertions, 0 deletions
diff --git a/cogs/commissions.py b/cogs/commissions.py
new file mode 100644
index 0000000..e2f35e8
--- /dev/null
+++ b/cogs/commissions.py
@@ -0,0 +1,327 @@
+import discord
+from discord import app_commands
+from discord.ext import commands
+from typing import Optional, Literal
+import config
+from utils.database import Database
+from utils.cooldowns import CooldownManager
+
+
+class CommissionModal(discord.ui.Modal, title="Submit a Commission"):
+ """Modal for submitting a new commission request."""
+
+ email = discord.ui.TextInput(
+ label="Email",
+ placeholder="your@email.com",
+ required=True,
+ max_length=255,
+ )
+
+ name = discord.ui.TextInput(
+ label="Name",
+ placeholder="Your name or preferred alias",
+ required=True,
+ max_length=255,
+ )
+
+ commission_type = discord.ui.TextInput(
+ label="Type (art, design, other, unsure)",
+ placeholder="art",
+ required=True,
+ max_length=20,
+ )
+
+ description = discord.ui.TextInput(
+ label="Description",
+ placeholder="Describe what you're looking for...",
+ style=discord.TextStyle.paragraph,
+ required=True,
+ max_length=2000,
+ )
+
+ budget = discord.ui.TextInput(
+ label="Budget (optional)",
+ placeholder="e.g., $50-100",
+ required=False,
+ max_length=100,
+ )
+
+ def __init__(self, cog: "Commissions"):
+ super().__init__()
+ self.cog = cog
+
+ async def on_submit(self, interaction: discord.Interaction):
+ # Validate commission type
+ valid_types = config.COMMISSION_TYPES
+ c_type = self.commission_type.value.lower().strip()
+ if c_type not in valid_types:
+ await interaction.response.send_message(
+ f"Invalid commission type. Please use one of: {', '.join(valid_types)}",
+ ephemeral=True,
+ )
+ return
+
+ # Insert into database
+ query = """
+ INSERT INTO commissions (discord_user_id, email, name, commission_type, description, budget)
+ VALUES (%s, %s, %s, %s, %s, %s)
+ """
+ commission_id = await Database.execute_returning_id(
+ query,
+ (
+ interaction.user.id,
+ self.email.value,
+ self.name.value,
+ c_type,
+ self.description.value,
+ self.budget.value or None,
+ ),
+ )
+
+ # Set cooldown
+ self.cog.cooldown_manager.set(interaction.user.id)
+
+ await interaction.response.send_message(
+ f"Commission submitted! Your commission ID is **#{commission_id}**.\n"
+ "You'll be notified via DM when the status changes.",
+ ephemeral=True,
+ )
+
+ # Notify owner
+ try:
+ owner = await interaction.client.fetch_user(config.OWNER_ID)
+ embed = discord.Embed(
+ title="New Commission Submitted",
+ color=discord.Color.green(),
+ )
+ embed.add_field(name="ID", value=f"#{commission_id}", inline=True)
+ embed.add_field(name="From", value=f"{interaction.user} ({interaction.user.id})", inline=True)
+ embed.add_field(name="Type", value=c_type, inline=True)
+ embed.add_field(name="Email", value=self.email.value, inline=True)
+ embed.add_field(name="Name", value=self.name.value, inline=True)
+ embed.add_field(name="Budget", value=self.budget.value or "Not specified", inline=True)
+ embed.add_field(name="Description", value=self.description.value[:1024], inline=False)
+ await owner.send(embed=embed)
+ except Exception:
+ pass # Don't fail if we can't notify owner
+
+
+class Commissions(commands.Cog):
+ """Commission management commands."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+ self.cooldown_manager = CooldownManager()
+
+ # ============ PUBLIC COMMANDS ============
+
+ @app_commands.command(name="queue", description="Check how many commissions are in the queue")
+ @app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
+ @app_commands.allowed_installs(guilds=True, users=True)
+ async def queue(self, interaction: discord.Interaction):
+ query = """
+ SELECT
+ COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending,
+ COUNT(CASE WHEN status = 'in_progress' THEN 1 END) as in_progress
+ FROM commissions
+ """
+ result = await Database.fetchone(query)
+ pending = result["pending"] if result else 0
+ in_progress = result["in_progress"] if result else 0
+ total = pending + in_progress
+
+ await interaction.response.send_message(
+ f"**Commission Queue:** {total} total ({pending} pending, {in_progress} in progress)"
+ )
+
+ @app_commands.command(name="commission", description="Submit a new commission request")
+ @app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
+ @app_commands.allowed_installs(guilds=True, users=True)
+ async def commission(self, interaction: discord.Interaction):
+ # Check cooldown
+ remaining = self.cooldown_manager.check(interaction.user.id)
+ if remaining is not None:
+ minutes = remaining // 60
+ seconds = remaining % 60
+ await interaction.response.send_message(
+ f"You're on cooldown. Please wait {minutes}m {seconds}s before submitting another commission.",
+ ephemeral=True,
+ )
+ return
+
+ await interaction.response.send_modal(CommissionModal(self))
+
+ # ============ OWNER COMMANDS ============
+
+ commissions_group = app_commands.Group(
+ name="commissions",
+ description="Manage commissions (owner only)",
+ allowed_contexts=app_commands.AppCommandContext(guild=True, dm_channel=True, private_channel=True),
+ allowed_installs=app_commands.AppInstallationType(guild=True, user=True),
+ )
+
+ async def _check_owner(self, interaction: discord.Interaction) -> bool:
+ """Check if user is owner, send error if not."""
+ if interaction.user.id != config.OWNER_ID:
+ await interaction.response.send_message(
+ "You don't have permission to use this command.", ephemeral=True
+ )
+ return False
+ return True
+
+ @commissions_group.command(name="list", description="List all commissions")
+ @app_commands.describe(status="Filter by status")
+ async def list_commissions(
+ self,
+ interaction: discord.Interaction,
+ status: Optional[Literal["pending", "accepted", "in_progress", "completed", "rejected"]] = None,
+ ):
+ if not await self._check_owner(interaction):
+ return
+
+ await interaction.response.defer(ephemeral=True)
+
+ if status:
+ query = "SELECT id, name, commission_type, status, created_at FROM commissions WHERE status = %s ORDER BY created_at DESC LIMIT 25"
+ commissions = await Database.fetchall(query, (status,))
+ else:
+ query = "SELECT id, name, commission_type, status, created_at FROM commissions ORDER BY created_at DESC LIMIT 25"
+ commissions = await Database.fetchall(query)
+
+ if not commissions:
+ await interaction.followup.send("No commissions found.", ephemeral=True)
+ return
+
+ embed = discord.Embed(
+ title="Commissions" + (f" ({status})" if status else ""),
+ color=discord.Color.blue(),
+ )
+
+ for c in commissions:
+ status_emoji = {
+ "pending": ":yellow_circle:",
+ "accepted": ":white_circle:",
+ "in_progress": ":blue_circle:",
+ "completed": ":green_circle:",
+ "rejected": ":red_circle:",
+ }.get(c["status"], ":black_circle:")
+
+ embed.add_field(
+ name=f"#{c['id']} - {c['name']}",
+ value=f"{status_emoji} {c['status']} | {c['commission_type']} | {c['created_at'].strftime('%Y-%m-%d')}",
+ inline=False,
+ )
+
+ await interaction.followup.send(embed=embed, ephemeral=True)
+
+ @commissions_group.command(name="view", description="View full details of a commission")
+ @app_commands.describe(commission_id="The commission ID to view")
+ async def view_commission(self, interaction: discord.Interaction, commission_id: int):
+ if not await self._check_owner(interaction):
+ return
+
+ await interaction.response.defer(ephemeral=True)
+
+ query = "SELECT * FROM commissions WHERE id = %s"
+ c = await Database.fetchone(query, (commission_id,))
+
+ if not c:
+ await interaction.followup.send(f"Commission #{commission_id} not found.", ephemeral=True)
+ return
+
+ embed = discord.Embed(
+ title=f"Commission #{c['id']}",
+ color=discord.Color.blue(),
+ )
+ embed.add_field(name="Name", value=c["name"], inline=True)
+ embed.add_field(name="Email", value=c["email"], inline=True)
+ embed.add_field(name="Type", value=c["commission_type"], inline=True)
+ embed.add_field(name="Status", value=c["status"], inline=True)
+ embed.add_field(name="Budget", value=c["budget"] or "Not specified", inline=True)
+
+ if c["discord_user_id"]:
+ embed.add_field(name="Discord ID", value=str(c["discord_user_id"]), inline=True)
+
+ embed.add_field(name="Description", value=c["description"][:1024], inline=False)
+
+ if c["rejection_reason"]:
+ embed.add_field(name="Rejection Reason", value=c["rejection_reason"], inline=False)
+
+ embed.set_footer(text=f"Created: {c['created_at']} | Updated: {c['updated_at']}")
+
+ await interaction.followup.send(embed=embed, ephemeral=True)
+
+ async def _update_status(
+ self,
+ interaction: discord.Interaction,
+ commission_id: int,
+ new_status: str,
+ rejection_reason: Optional[str] = None,
+ ):
+ """Update commission status and notify user."""
+ if not await self._check_owner(interaction):
+ return
+
+ await interaction.response.defer(ephemeral=True)
+
+ # Get current commission
+ query = "SELECT * FROM commissions WHERE id = %s"
+ c = await Database.fetchone(query, (commission_id,))
+
+ if not c:
+ await interaction.followup.send(f"Commission #{commission_id} not found.", ephemeral=True)
+ return
+
+ # Update status
+ if rejection_reason:
+ update_query = "UPDATE commissions SET status = %s, rejection_reason = %s WHERE id = %s"
+ await Database.execute(update_query, (new_status, rejection_reason, commission_id))
+ else:
+ update_query = "UPDATE commissions SET status = %s WHERE id = %s"
+ await Database.execute(update_query, (new_status, commission_id))
+
+ await interaction.followup.send(
+ f"Commission #{commission_id} marked as **{new_status}**.", ephemeral=True
+ )
+
+ # Notify user via DM if they submitted via Discord
+ if c["discord_user_id"]:
+ try:
+ user = await self.bot.fetch_user(c["discord_user_id"])
+ status_messages = {
+ "accepted": f"Your commission (#{commission_id}) has been **accepted**!",
+ "in_progress": f"Your commission (#{commission_id}) is now **in progress**!",
+ "completed": f"Your commission (#{commission_id}) has been **completed**!",
+ "rejected": f"Your commission (#{commission_id}) has been **rejected**."
+ + (f"\nReason: {rejection_reason}" if rejection_reason else ""),
+ }
+ message = status_messages.get(new_status, f"Your commission (#{commission_id}) status updated to: {new_status}")
+ await user.send(message)
+ except Exception:
+ pass # Don't fail if we can't DM
+
+ @commissions_group.command(name="accept", description="Accept a pending commission")
+ @app_commands.describe(commission_id="The commission ID to accept")
+ async def accept_commission(self, interaction: discord.Interaction, commission_id: int):
+ await self._update_status(interaction, commission_id, "accepted")
+
+ @commissions_group.command(name="progress", description="Mark a commission as in progress")
+ @app_commands.describe(commission_id="The commission ID to mark in progress")
+ async def progress_commission(self, interaction: discord.Interaction, commission_id: int):
+ await self._update_status(interaction, commission_id, "in_progress")
+
+ @commissions_group.command(name="done", description="Mark a commission as completed")
+ @app_commands.describe(commission_id="The commission ID to mark completed")
+ async def done_commission(self, interaction: discord.Interaction, commission_id: int):
+ await self._update_status(interaction, commission_id, "completed")
+
+ @commissions_group.command(name="reject", description="Reject a commission")
+ @app_commands.describe(commission_id="The commission ID to reject", reason="Reason for rejection")
+ async def reject_commission(
+ self, interaction: discord.Interaction, commission_id: int, reason: Optional[str] = None
+ ):
+ await self._update_status(interaction, commission_id, "rejected", reason)
+
+
+async def setup(bot: commands.Bot):
+ await bot.add_cog(Commissions(bot))