From 56123bffe8aa5b2adf0153405aea5a79179fe6a3 Mon Sep 17 00:00:00 2001 From: Valerie Date: Mon, 26 May 2025 02:20:55 -0400 Subject: [PATCH] Refactor Shop cog to replace `ctx.send` with `ctx.message.reply` for improved user interaction and feedback consistency. Simplify command descriptions and enhance error handling for various shop commands, ensuring clearer communication in DMs. --- shop/shop.py | 194 +++++++++++++++++++++------------------------------ 1 file changed, 81 insertions(+), 113 deletions(-) diff --git a/shop/shop.py b/shop/shop.py index 7daf8ed..2800b9d 100644 --- a/shop/shop.py +++ b/shop/shop.py @@ -113,7 +113,7 @@ class Shop(commands.Cog): try: instance = await self.get_instance(ctx, user=ctx.author) except AttributeError: - return await ctx.send("You can't use this command in DMs when not in global mode.") + return await ctx.message.reply("You can't use this command in DMs when not in global mode.") inventory = await instance.Inventory.all() if not inventory: @@ -122,14 +122,14 @@ class Shop(commands.Cog): description="Your inventory is empty.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) embed = discord.Embed( title=f"{ctx.author.display_name}'s Inventory", color=discord.Color.blue() ) view = InventoryView(ctx, inventory) - view.message = await ctx.send(embed=embed, view=view) + view.message = await ctx.message.reply(embed=embed, view=view) try: await view.wait() @@ -171,20 +171,11 @@ class Shop(commands.Cog): @shop.command() async def buy(self, ctx, *purchase): - """Opens the shop menu or directly purchases an item. - - When no argument is specified, opens an interactive shop menu. - For direct purchase, use: "Shop Name" "Item Name" - - Examples: - [p]shop buy "Secret Shop" oil - [p]shop buy Junkyard tire - [p]shop buy "Holy Temple" "Healing Potion" - """ + """Opens the shop menu or directly purchases an item.""" try: instance = await self.get_instance(ctx, settings=True) except AttributeError: - return await ctx.send("You can't use this command in DMs when not in global mode.") + return await ctx.message.reply("You can't use this command in DMs when not in global mode.") if not await instance.Shops(): embed = discord.Embed( @@ -192,7 +183,7 @@ class Shop(commands.Cog): description="No shops have been created yet.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) if await instance.Settings.Closed(): embed = discord.Embed( @@ -200,7 +191,7 @@ class Shop(commands.Cog): description="The shop system is currently closed.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) shops = await instance.Shops.all() available_shops = await self.check_availability(ctx, shops) @@ -211,7 +202,7 @@ class Shop(commands.Cog): description="Either no items have been created, you need a higher role, or this command should be used in a server and not DMs.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) if purchase: try: @@ -222,7 +213,7 @@ class Shop(commands.Cog): description="Too many parameters passed. Use help on this command for more information.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) if shop not in shops: embed = discord.Embed( @@ -230,9 +221,8 @@ class Shop(commands.Cog): description="Either that shop does not exist, or you don't have access to it.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) - # Create purchase view directly for the specified item item_data = shops[shop]["Items"].get(item) if not item_data: embed = discord.Embed( @@ -240,11 +230,11 @@ class Shop(commands.Cog): description=f"Item '{item}' not found in shop '{shop}'.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) view = PurchaseView(ctx, shop, item, item_data) embed = view.build_embed() - view.message = await ctx.send(embed=embed, view=view) + view.message = await ctx.message.reply(embed=embed, view=view) try: await view.wait() @@ -263,7 +253,7 @@ class Shop(commands.Cog): color=discord.Color.blue() ) view = ShopView(ctx, shops) - view.message = await ctx.send(embed=embed, view=view) + view.message = await ctx.message.reply(embed=embed, view=view) try: await view.wait() @@ -274,20 +264,20 @@ class Shop(commands.Cog): except asyncio.TimeoutError: pass # Handled by view's on_timeout - @commands.max_concurrency(1, commands.BucketType.user) @shop.command() + @commands.max_concurrency(1, commands.BucketType.user) async def redeem(self, ctx, *, item: str): """Redeems an item in your inventory.""" try: instance = await self.get_instance(ctx, user=ctx.author) except AttributeError: - return await ctx.send("You can't use this command in DMs when not in global mode.") + return await ctx.message.reply("You can't use this command in DMs when not in global mode.") data = await instance.Inventory.all() if data is None: - return await ctx.send("Your inventory is empty.") + return await ctx.message.reply("Your inventory is empty.") if item not in data: - return await ctx.send("You don't own this item.") + return await ctx.message.reply("You don't own this item.") await self.pending_prompt(ctx, instance, data, item) @@ -295,11 +285,7 @@ class Shop(commands.Cog): @commands.guild_only() @commands.cooldown(1, 5, commands.BucketType.user) async def trade(self, ctx, user: discord.Member, quantity: int, *, item: str): - """Attempts to trade an item with another user. - - Cooldown is a static 60 seconds to prevent abuse. - Cooldown will trigger regardless of the outcome. - """ + """Attempts to trade an item with another user.""" cancel = ctx.prefix + "cancel" author_instance = await self.get_instance(ctx, user=ctx.author) author_inventory = await author_instance.Inventory.all() @@ -307,15 +293,15 @@ class Shop(commands.Cog): user_inv = await user_instance.Inventory.all() if not await user_instance.Trading(): - return await ctx.send("This user has trading turned off.") + return await ctx.message.reply("This user has trading turned off.") if item not in author_inventory: - return await ctx.send("You don't own that item.") + return await ctx.message.reply("You don't own that item.") if 0 < author_inventory[item]["Qty"] < quantity: - return await ctx.send("You don't have that many {}".format(item)) + return await ctx.message.reply("You don't have that many {}".format(item)) - await ctx.send( + await ctx.message.reply( "{} has requested a trade with {}.\n" "They are offering {}x {}.\n Do wish to trade?\n" "*This trade can be canceled at anytime by typing `{}`.*" @@ -330,11 +316,11 @@ class Shop(commands.Cog): try: decision = await ctx.bot.wait_for("message", timeout=25, check=check) except asyncio.TimeoutError: - return await ctx.send("Trade request timed out. Canceled trade.") + return await ctx.message.reply("Trade request timed out. Canceled trade.") if decision.content.lower() in ("no", cancel): - return await ctx.send("Trade canceled.") - await ctx.send("{} What is your counter offer?\n" '*Example: 3 "Healing Potions"*'.format(user.mention)) + return await ctx.message.reply("Trade canceled.") + await ctx.message.reply("{} What is your counter offer?\n" '*Example: 3 "Healing Potions"*'.format(user.mention)) def predicate(m): if m.author in (user, ctx.author) and m.content == cancel: @@ -353,11 +339,11 @@ class Shop(commands.Cog): try: offer = await ctx.bot.wait_for("message", timeout=25, check=predicate) except asyncio.TimeoutError: - return await ctx.send("Trade request timed out. Canceled trade.") + return await ctx.message.reply("Trade request timed out. Canceled trade.") if offer.content.lower() == cancel: - return await ctx.send("Trade canceled.") + return await ctx.message.reply("Trade canceled.") qty, item2 = [x.strip() for x in offer.content.split('"')[:2] if x] - await ctx.send( + await ctx.message.reply( "{} Do you wish to trade {}x {} for {}'s {}x {}?" "".format(ctx.author.mention, quantity, item, user.mention, qty, item2) ) @@ -370,20 +356,19 @@ class Shop(commands.Cog): try: final = await ctx.bot.wait_for("message", timeout=25, check=check2) except asyncio.TimeoutError: - return await ctx.send("Trade request timed out. Canceled trade.") + return await ctx.message.reply("Trade request timed out. Canceled trade.") if final.content.lower() in ("no", cancel): - return await ctx.send("Trade canceled.") + return await ctx.message.reply("Trade canceled.") sm1 = ShopManager(ctx, instance=None, user_data=author_instance) - await sm1.add(item2, user_inv[item2], int(qty)) await sm1.remove(item, number=quantity) sm2 = ShopManager(ctx, instance=None, user_data=user_instance) await sm2.add(item, author_inventory[item], quantity) await sm2.remove(item2, number=int(qty)) - await ctx.send("Trade complete.") + await ctx.message.reply("Trade complete.") @shop.command() async def tradetoggle(self, ctx): @@ -398,24 +383,16 @@ class Shop(commands.Cog): @shop.command() async def refund(self, ctx, *, item: str): - """Refunds a role item back to your inventory. - - This command allows you to remove a role and get the shop item back in your inventory. - You must specify the exact name of the role item from the shop. - - Example: - [p]shop refund VIP Role - """ + """Refunds a role item back to your inventory.""" try: settings = await self.get_instance(ctx, settings=True) user_instance = await self.get_instance(ctx, user=ctx.author) except AttributeError: - return await ctx.send("You can't use this command in DMs when not in global mode.") + return await ctx.message.reply("You can't use this command in DMs when not in global mode.") # Find the role item in any shop role_item = None role_name = None - shop_name = None async with settings.Shops() as shops: for shop in shops.values(): @@ -424,13 +401,13 @@ class Shop(commands.Cog): item_data["Type"].lower() == "role"): role_item = item_name role_name = item_data["Role"] - item_data = deepcopy(item_data) # Store for later use + item_data = deepcopy(item_data) break if role_item: break if not role_item: - return await ctx.send( + return await ctx.message.reply( embed=discord.Embed( title="Item Not Found", description=f"Could not find a role item named '{item}' in any shop.", @@ -446,7 +423,7 @@ class Shop(commands.Cog): role = discord.utils.get(ctx.guild.roles, name=role_name) if not role: - return await ctx.send( + return await ctx.message.reply( embed=discord.Embed( title="Role Not Found", description=f"The role associated with this item no longer exists on the server.", @@ -455,7 +432,7 @@ class Shop(commands.Cog): ) if role not in ctx.author.roles: - return await ctx.send( + return await ctx.message.reply( embed=discord.Embed( title="Role Not Owned", description=f"You don't have the `{role.name}` role to refund.", @@ -469,7 +446,7 @@ class Shop(commands.Cog): description=f"Are you sure you want to remove the `{role.name}` role and get `{role_item}` back in your inventory?", color=discord.Color.blue() ) - msg = await ctx.send(embed=embed) + msg = await ctx.message.reply(embed=embed) # Add reactions for yes/no await msg.add_reaction("✅") @@ -526,28 +503,28 @@ class Shop(commands.Cog): @shop.command() async def version(self, ctx): """Shows the current Shop version.""" - await ctx.send("Shop is running version {}.".format(__version__)) + await ctx.message.reply("Shop is running version {}.".format(__version__)) @shop.command() @commands.is_owner() async def wipe(self, ctx): """Wipes all shop cog data.""" - await ctx.send( + await ctx.message.reply( "You are about to delete all shop and user data from the bot. Are you sure this is what you wish to do?" ) try: choice = await ctx.bot.wait_for("message", timeout=25.0, check=Checks(ctx).confirm) except asyncio.TimeoutError: - return await ctx.send("No Response. Action canceled.") + return await ctx.message.reply("No Response. Action canceled.") if choice.content.lower() == "yes": await self.config.clear_all() msg = "{0.name} ({0.id}) wiped all shop data.".format(ctx.author) log.info(msg) - await ctx.send(msg) + await ctx.message.reply(msg) else: - return await ctx.send("Wipe canceled.") + return await ctx.message.reply("Wipe canceled.") @shop.command() @global_permissions() @@ -707,37 +684,29 @@ class Shop(commands.Cog): ) @shop.command() + @global_permissions() @commands.guild_only() async def gift(self, ctx, user: discord.Member, quantity: int, *, item): - """Gift another user a set number of one of your items. - - The item must be in your inventory and have enough to cover the quantity. - For role items, users can only have one copy at a time. - - Examples - -------- - [p]shop gift Redjumpman 3 Healing Potion - [p]shop give @Navi 1 Demon Sword - """ + """Gift another user a set number of one of your items.""" if quantity < 1: - return await ctx.send(":facepalm: How would that work genius?") + return await ctx.message.reply(":facepalm: How would that work genius?") if user == ctx.author: - return await ctx.send("Really? Maybe you should find some friends.") + return await ctx.message.reply("Really? Maybe you should find some friends.") settings = await self.get_instance(ctx, settings=True) if not await settings.Settings.Gifting(): - return await ctx.send("Gifting is turned off.") + return await ctx.message.reply("Gifting is turned off.") author_instance = await self.get_instance(ctx, user=ctx.author) author_inv = await author_instance.Inventory.all() if item not in author_inv: - return await ctx.send(f"You don't own any `{item}`.") + return await ctx.message.reply(f"You don't own any `{item}`.") if author_inv[item]["Qty"] < quantity: - return await ctx.send(f"You don't have that many `{item}` to give.") + return await ctx.message.reply(f"You don't have that many `{item}` to give.") # Check if it's a role item if author_inv[item]["Type"].lower() == "role": if quantity > 1: - return await ctx.send( + return await ctx.message.reply( embed=discord.Embed( title="Invalid Quantity", description="You can only gift one copy of a role item.", @@ -748,7 +717,7 @@ class Shop(commands.Cog): user_instance = await self.get_instance(ctx, user=user) user_inv = await user_instance.Inventory.all() if item in user_inv: - return await ctx.send( + return await ctx.message.reply( embed=discord.Embed( title="Already Owned", description=f"{user.display_name} already owns this role item. Users can only have one copy at a time.", @@ -764,7 +733,7 @@ class Shop(commands.Cog): success = await sm2.add(item, author_inv[item], quantity) if success: - await ctx.send( + await ctx.message.reply( embed=discord.Embed( title="Gift Successful", description=f"{ctx.author.mention} gifted {user.mention} {quantity}x {item}.", @@ -774,7 +743,7 @@ class Shop(commands.Cog): else: # If gift failed, give item back to original owner await sm1.add(item, author_inv[item], quantity) - await ctx.send( + await ctx.message.reply( embed=discord.Embed( title="Gift Failed", description=f"Could not gift {item} to {user.mention}. The item has been returned to your inventory.", @@ -786,21 +755,9 @@ class Shop(commands.Cog): @global_permissions() @commands.guild_only() async def give(self, ctx, user: discord.Member, quantity: int, *shopitem): - """Administratively gives a user an item. - - Shopitem argument must be a \"Shop Name\" \"Item Name\" format. - - The item must be in the shop in order for this item to be given. - Only basic and role items can be given. - Giving a user an item does not affect the stock in the shop. - - Examples - -------- - [p]shop give Redjumpman 1 "Holy Temple" "Healing Potion" - [p]shop give Redjumpman 1 Junkyard Scrap - """ + """Administratively gives a user an item.""" if quantity < 1: - return await ctx.send(":facepalm: You can't do that.") + return await ctx.message.reply(":facepalm: You can't do that.") if shopitem is None: return await ctx.send_help() @@ -808,35 +765,45 @@ class Shop(commands.Cog): try: shop, item = shopitem except ValueError: - return await ctx.send('Must be a `"Shop Name" "Item Name"` format.') + return await ctx.message.reply('Must be a `"Shop Name" "Item Name"` format.') instance = await self.get_instance(ctx, settings=True) shops = await instance.Shops.all() if shop not in shops: - return await ctx.send("Invalid shop name.") + return await ctx.message.reply("Invalid shop name.") elif item not in shops[shop]["Items"]: - return await ctx.send("That item in not in the {} shop.".format(shop)) + return await ctx.message.reply("That item in not in the {} shop.".format(shop)) elif shops[shop]["Items"][item]["Type"] not in ("basic", "role"): - return await ctx.send("You can only give basic or role type items.") + return await ctx.message.reply("You can only give basic or role type items.") else: data = deepcopy(shops[shop]["Items"][item]) user_instance = await self.get_instance(ctx, user=user) sm = ShopManager(ctx, None, user_instance) await sm.add(item, data, quantity) - await ctx.send("{} just gave {} a {}.".format(ctx.author.mention, user.mention, item)) + await ctx.message.reply("{} just gave {} a {}.".format(ctx.author.mention, user.mention, item)) @shop.command() @global_permissions() @commands.guild_only() async def clearinv(self, ctx, user: discord.Member): """Completely clears a user's inventory.""" - await ctx.send("Are you sure you want to completely wipe {}'s inventory?".format(user.name)) + await ctx.message.reply("Are you sure you want to completely wipe {}'s inventory?".format(user.name)) choice = await ctx.bot.wait_for("message", timeout=25, check=Checks(ctx).confirm) if choice.content.lower() != "yes": - return await ctx.send("Canceled inventory wipe.") + return await ctx.message.reply("Canceled inventory wipe.") instance = await self.get_instance(ctx=ctx, user=user) await instance.Inventory.clear() - await ctx.send("Done. Inventory wiped for {}.".format(user.name)) + await ctx.message.reply("Done. Inventory wiped for {}.".format(user.name)) + + @shop.command() + @global_permissions() + @commands.guild_only() + async def toggle(self, ctx): + """Closes/opens all shops.""" + instance = await self.get_instance(ctx, settings=True) + status = await instance.Settings.Closed() + await instance.Settings.Closed.set(not status) + await ctx.message.reply("Shops are now {}.".format("open" if status else "closed")) @shop.command() @global_permissions() @@ -1111,9 +1078,10 @@ class Shop(commands.Cog): return self.config.member(user) async def assign_role(self, ctx, instance, item, role_name): + """Assign a role to a user.""" if await self.config.Global(): if not ctx.guild: - return await ctx.send( + return await ctx.message.reply( "Unable to assign role, because shop is in global mode." "Try redeeming your item in a server instead of in DMs." ) @@ -1132,7 +1100,7 @@ class Shop(commands.Cog): description=f"Could not assign the role `{role_name}` because it does not exist on the server.", color=discord.Color.red() ) - return await ctx.send(embed=embed) + return await ctx.message.reply(embed=embed) try: # Check if user already has the role @@ -1143,7 +1111,7 @@ class Shop(commands.Cog): description=f"You already have the `{role.name}` role. Would you like to remove it and get a refund?", color=discord.Color.blue() ) - msg = await ctx.send(embed=embed) + msg = await ctx.message.reply(embed=embed) # Add reactions for yes/no await msg.add_reaction("✅") @@ -1198,7 +1166,7 @@ class Shop(commands.Cog): ), color=discord.Color.red() ) - await ctx.send(embed=embed) + await ctx.message.reply(embed=embed) return False # Remove the item from inventory @@ -1213,7 +1181,7 @@ class Shop(commands.Cog): description=f"{ctx.author.display_name} was granted the `{role.name}` role.", color=discord.Color.green() ) - await ctx.send(embed=embed) + await ctx.message.reply(embed=embed) async def pending_prompt(self, ctx, instance, data, item): """Handle item redemption with modern UI.""" @@ -1248,7 +1216,7 @@ class Shop(commands.Cog): prompt = f"{ctx.author.mention} Do you wish to redeem {item}? This will add the item to the pending list for an admin to review and grant. The item will be removed from your inventory while this is processing." view = RedeemView(self) - msg = await ctx.send(prompt, embed=e, view=view) + msg = await ctx.message.reply(prompt, embed=e, view=view) try: await view.wait()