From 82e48c23833ae961767abb1d08473ae1bec443ec Mon Sep 17 00:00:00 2001 From: Unstable Kitsune Date: Sat, 20 Sep 2025 20:46:32 -0400 Subject: [PATCH] feat: implement KofiShop cog for Discord bot integration Introduces a new cog to manage Ko-fi shop orders, reviews, and waitlists. Users can submit orders and reviews via interactive modals. Administrators can configure channels and add users to the waitlist. --- kofi-shop/__init__.py | 1 + kofi-shop/kofi-shop.py | 0 kofi-shop/kofi_shop.py | 175 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) delete mode 100644 kofi-shop/kofi-shop.py create mode 100644 kofi-shop/kofi_shop.py diff --git a/kofi-shop/__init__.py b/kofi-shop/__init__.py index e69de29..e816160 100644 --- a/kofi-shop/__init__.py +++ b/kofi-shop/__init__.py @@ -0,0 +1 @@ +from .kofi_shop import setup \ No newline at end of file diff --git a/kofi-shop/kofi-shop.py b/kofi-shop/kofi-shop.py deleted file mode 100644 index e69de29..0000000 diff --git a/kofi-shop/kofi_shop.py b/kofi-shop/kofi_shop.py new file mode 100644 index 0000000..dfd49b4 --- /dev/null +++ b/kofi-shop/kofi_shop.py @@ -0,0 +1,175 @@ +import discord +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/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) + + 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) + + 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) + + 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.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) + +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) + + 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) + + 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) + + 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.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) + + +class KofiShop(commands.Cog): + """ + A cog to manage Ko-fi shop orders and reviews. + """ + + def __init__(self, bot: Red): + self.bot = bot + self.config = Config.get_conf(self, identifier=5566778899, force_registration=True) + + default_guild = { + "order_channel": None, + "review_channel": None, + "waitlist_channel": None + } + 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)) + + @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: + return + await ctx.interaction.response.send_modal(ReviewModal(self)) + + @commands.hybrid_command() # type: ignore + @commands.guild_only() + @commands.admin_or_permissions(manage_guild=True) + async def waitlist(self, ctx: commands.Context, user: discord.Member, *, item: str): + """Add a user and their requested item to the waitlist.""" + if not ctx.guild or not isinstance(ctx.channel, discord.TextChannel): + return await ctx.send("This command must be used in a server's text channel.", ephemeral=True) + + waitlist_channel_id = await self.config.guild(ctx.guild).waitlist_channel() + if not waitlist_channel_id: + return await ctx.send("The waitlist channel has not been set by an admin.", ephemeral=True) + + waitlist_channel = ctx.guild.get_channel(waitlist_channel_id) + if not isinstance(waitlist_channel, discord.TextChannel): + return await ctx.send("The configured waitlist channel is invalid.", ephemeral=True) + + 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) + + # --- 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. + """ + 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 + await self.config.guild(ctx.guild).order_channel.set(channel.id) + await ctx.send(f"Order channel has been 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 + await self.config.guild(ctx.guild).review_channel.set(channel.id) + await ctx.send(f"Review channel has been 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 + await self.config.guild(ctx.guild).waitlist_channel.set(channel.id) + await ctx.send(f"Waitlist channel has been set to {channel.mention}.") + + +async def setup(bot: Red): + await bot.add_cog(KofiShop(bot)) +