Refactor Shop cog to enhance user experience by implementing interactive embeds for inventory and shop commands. Improve error handling with descriptive embeds for various scenarios, including empty inventories and invalid parameters. Add cancel buttons to shop and purchase views for better navigation and user control.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
This commit is contained in:
parent
9b7be4987f
commit
5cc572ca7a
2 changed files with 130 additions and 66 deletions
79
shop/shop.py
79
shop/shop.py
|
@ -108,7 +108,6 @@ class Shop(commands.Cog):
|
|||
# -----------------------COMMANDS-------------------------------------
|
||||
|
||||
@commands.command()
|
||||
@commands.max_concurrency(1, commands.BucketType.user)
|
||||
async def inventory(self, ctx):
|
||||
"""Displays your purchased items."""
|
||||
try:
|
||||
|
@ -118,20 +117,26 @@ class Shop(commands.Cog):
|
|||
|
||||
inventory = await instance.Inventory.all()
|
||||
if not inventory:
|
||||
return await ctx.send("Your inventory is empty.")
|
||||
embed = discord.Embed(
|
||||
title="Empty Inventory",
|
||||
description="Your inventory is empty.",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
view = InventoryView(ctx, inventory)
|
||||
await ctx.send(
|
||||
f"{ctx.author.mention}'s Inventory",
|
||||
view=view
|
||||
embed = discord.Embed(
|
||||
title=f"{ctx.author.display_name}'s Inventory",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
view = InventoryView(ctx, inventory)
|
||||
await ctx.send(embed=embed, view=view)
|
||||
|
||||
try:
|
||||
await view.wait()
|
||||
if view.selected_item: # An item was selected to use
|
||||
await self.pending_prompt(ctx, instance, inventory, view.selected_item)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Inventory view timed out.")
|
||||
await ctx.send("Inventory view timed out.", ephemeral=True)
|
||||
|
||||
async def inv_hook(self, user):
|
||||
"""Inventory Hook for outside cogs
|
||||
|
@ -165,7 +170,6 @@ class Shop(commands.Cog):
|
|||
pass
|
||||
|
||||
@shop.command()
|
||||
@commands.max_concurrency(1, commands.BucketType.user)
|
||||
async def buy(self, ctx, *purchase):
|
||||
"""Opens the shop menu or directly purchases an item.
|
||||
|
||||
|
@ -183,33 +187,60 @@ class Shop(commands.Cog):
|
|||
return await ctx.send("You can't use this command in DMs when not in global mode.")
|
||||
|
||||
if not await instance.Shops():
|
||||
return await ctx.send("No shops have been created yet.")
|
||||
embed = discord.Embed(
|
||||
title="No Shops Available",
|
||||
description="No shops have been created yet.",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
if await instance.Settings.Closed():
|
||||
return await ctx.send("The shop system is currently closed.")
|
||||
embed = discord.Embed(
|
||||
title="Shops Closed",
|
||||
description="The shop system is currently closed.",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
shops = await instance.Shops.all()
|
||||
available_shops = await self.check_availability(ctx, shops)
|
||||
|
||||
if not available_shops:
|
||||
return await ctx.send(
|
||||
"Either no items have been created, you need a higher role, "
|
||||
"or this command should be used in a server and not DMs."
|
||||
embed = discord.Embed(
|
||||
title="No Access",
|
||||
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)
|
||||
|
||||
if purchase:
|
||||
try:
|
||||
shop, item = purchase
|
||||
except ValueError:
|
||||
return await ctx.send("Too many parameters passed. Use help on this command for more information.")
|
||||
embed = discord.Embed(
|
||||
title="Invalid Parameters",
|
||||
description="Too many parameters passed. Use help on this command for more information.",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
if shop not in shops:
|
||||
return await ctx.send("Either that shop does not exist, or you don't have access to it.")
|
||||
embed = discord.Embed(
|
||||
title="Shop Not Found",
|
||||
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)
|
||||
|
||||
# Create purchase view directly for the specified item
|
||||
item_data = shops[shop]["Items"].get(item)
|
||||
if not item_data:
|
||||
return await ctx.send(f"Item '{item}' not found in shop '{shop}'.")
|
||||
embed = discord.Embed(
|
||||
title="Item Not Found",
|
||||
description=f"Item '{item}' not found in shop '{shop}'.",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
view = PurchaseView(ctx, shop, item, item_data)
|
||||
embed = view.build_embed()
|
||||
|
@ -222,24 +253,26 @@ class Shop(commands.Cog):
|
|||
sm = ShopManager(ctx, instance, user_data)
|
||||
await sm.order(shop, item, view.quantity)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Purchase menu timed out.")
|
||||
await ctx.send("Purchase menu timed out.", ephemeral=True)
|
||||
|
||||
else:
|
||||
# Open interactive shop menu
|
||||
view = ShopView(ctx, shops)
|
||||
await ctx.send(
|
||||
f"Welcome to the shop, {ctx.author.mention}!",
|
||||
view=view
|
||||
embed = discord.Embed(
|
||||
title="Welcome to the Shop",
|
||||
description=f"Welcome {ctx.author.mention}! Please select a shop to browse.",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
view = ShopView(ctx, shops)
|
||||
await ctx.send(embed=embed, view=view)
|
||||
|
||||
try:
|
||||
await view.wait()
|
||||
if view.current_shop and view.current_item: # An item was selected
|
||||
if view.current_shop and view.current_item and view.quantity:
|
||||
user_data = await self.get_instance(ctx, user=ctx.author)
|
||||
sm = ShopManager(ctx, instance, user_data)
|
||||
await sm.order(view.current_shop, view.current_item, view.quantity)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Shop menu timed out.")
|
||||
await ctx.send("Shop menu timed out.", ephemeral=True)
|
||||
|
||||
@commands.max_concurrency(1, commands.BucketType.user)
|
||||
@shop.command()
|
||||
|
|
117
shop/ui.py
117
shop/ui.py
|
@ -10,8 +10,23 @@ class ShopView(View):
|
|||
self.shops = shops
|
||||
self.current_shop = None
|
||||
self.current_item = None
|
||||
self.quantity = None # Add quantity attribute
|
||||
self.quantity = None
|
||||
self.setup_shop_select()
|
||||
self.add_item(Button(label="Cancel", style=discord.ButtonStyle.red, custom_id="cancel"))
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
if interaction.user != self.ctx.author:
|
||||
await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def on_error(self, interaction: discord.Interaction, error: Exception, item: Item) -> None:
|
||||
await interaction.response.send_message(f"An error occurred: {str(error)}", ephemeral=True)
|
||||
|
||||
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.red, custom_id="cancel")
|
||||
async def cancel(self, interaction: discord.Interaction, button: Button):
|
||||
await interaction.response.edit_message(content="Shop menu cancelled.", embed=None, view=None)
|
||||
self.stop()
|
||||
|
||||
def setup_shop_select(self):
|
||||
options = []
|
||||
|
@ -33,9 +48,6 @@ class ShopView(View):
|
|||
self.add_item(shop_select)
|
||||
|
||||
async def shop_selected(self, interaction: discord.Interaction):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
|
||||
shop_name = interaction.data["values"][0]
|
||||
self.current_shop = shop_name
|
||||
|
||||
|
@ -48,6 +60,12 @@ class ShopView(View):
|
|||
items = self.shops[shop_name]["Items"]
|
||||
options = []
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Shop: {shop_name}",
|
||||
description="Select an item to purchase",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
|
||||
for item_name, item_data in items.items():
|
||||
qty = "∞" if item_data["Qty"] == "--" else item_data["Qty"]
|
||||
desc = f"Cost: {item_data['Cost']} | Stock: {qty}"
|
||||
|
@ -66,12 +84,9 @@ class ShopView(View):
|
|||
item_select.callback = self.item_selected
|
||||
self.add_item(item_select)
|
||||
|
||||
await interaction.response.edit_message(view=self)
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
|
||||
async def item_selected(self, interaction: discord.Interaction):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
|
||||
item_name = interaction.data["values"][0]
|
||||
self.current_item = item_name
|
||||
item_data = self.shops[self.current_shop]["Items"][item_name]
|
||||
|
@ -94,8 +109,14 @@ class PurchaseView(View):
|
|||
self.shop = shop
|
||||
self.item = item
|
||||
self.item_data = item_data
|
||||
self.quantity = 1
|
||||
self.quantity = None
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
if interaction.user != self.ctx.author:
|
||||
await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
def build_embed(self) -> discord.Embed:
|
||||
e = discord.Embed(
|
||||
title=f"Purchase {self.item}",
|
||||
|
@ -108,31 +129,27 @@ class PurchaseView(View):
|
|||
e.add_field(name="Type", value=self.item_data["Type"].title(), inline=True)
|
||||
if self.item_data["Type"] == "role":
|
||||
e.add_field(name="Role", value=self.item_data["Role"], inline=True)
|
||||
e.set_footer(text="Select a quantity to purchase")
|
||||
return e
|
||||
|
||||
@button(label="Buy 1", style=discord.ButtonStyle.green, row=1)
|
||||
async def buy_one(self, interaction: discord.Interaction, button: Button):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
self.quantity = 1
|
||||
await self.handle_purchase(interaction)
|
||||
|
||||
@button(label="Buy 5", style=discord.ButtonStyle.green, row=1)
|
||||
async def buy_five(self, interaction: discord.Interaction, button: Button):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
self.quantity = 5
|
||||
await self.handle_purchase(interaction)
|
||||
|
||||
@button(label="Custom Amount", style=discord.ButtonStyle.blurple, row=1)
|
||||
async def custom_amount(self, interaction: discord.Interaction, button: Button):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
|
||||
await interaction.response.send_message(
|
||||
"How many would you like to buy? Type a number:",
|
||||
ephemeral=True
|
||||
embed = discord.Embed(
|
||||
title="Custom Purchase Amount",
|
||||
description="How many would you like to buy? Type a number in chat.",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
await interaction.response.edit_message(embed=embed)
|
||||
|
||||
def check(m):
|
||||
return (
|
||||
|
@ -147,12 +164,12 @@ class PurchaseView(View):
|
|||
await self.handle_purchase(interaction)
|
||||
except asyncio.TimeoutError:
|
||||
await interaction.followup.send("Purchase cancelled - took too long to respond.", ephemeral=True)
|
||||
self.stop()
|
||||
|
||||
@button(label="Cancel", style=discord.ButtonStyle.red, row=1)
|
||||
async def cancel(self, interaction: discord.Interaction, button: Button):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
await interaction.response.edit_message(content="Purchase cancelled.", embed=None, view=None)
|
||||
self.stop()
|
||||
|
||||
async def handle_purchase(self, interaction: discord.Interaction):
|
||||
# Validate quantity
|
||||
|
@ -172,21 +189,36 @@ class PurchaseView(View):
|
|||
|
||||
total_cost = self.item_data["Cost"] * self.quantity
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Processing Purchase",
|
||||
description=f"Processing purchase of {self.quantity}x {self.item}...",
|
||||
color=discord.Color.green()
|
||||
)
|
||||
embed.add_field(name="Total Cost", value=total_cost)
|
||||
|
||||
# This will be handled by the Shop cog's order method
|
||||
self.stop()
|
||||
await interaction.response.edit_message(
|
||||
content=f"Processing purchase of {self.quantity}x {self.item}...",
|
||||
embed=None,
|
||||
view=None
|
||||
)
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
|
||||
class InventoryView(View):
|
||||
def __init__(self, ctx, inventory: Dict[str, Any], timeout: int = 60):
|
||||
super().__init__(timeout=timeout)
|
||||
self.ctx = ctx
|
||||
self.inventory = inventory
|
||||
self.selected_item = None # Track selected item
|
||||
self.selected_item = None
|
||||
self.setup_inventory_select()
|
||||
self.add_item(Button(label="Close", style=discord.ButtonStyle.red, custom_id="close"))
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
if interaction.user != self.ctx.author:
|
||||
await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
@discord.ui.button(label="Close", style=discord.ButtonStyle.red, custom_id="close")
|
||||
async def close(self, interaction: discord.Interaction, button: Button):
|
||||
await interaction.response.edit_message(content="Inventory closed.", embed=None, view=None)
|
||||
self.stop()
|
||||
|
||||
def setup_inventory_select(self):
|
||||
options = []
|
||||
|
@ -208,11 +240,8 @@ class InventoryView(View):
|
|||
self.add_item(inv_select)
|
||||
|
||||
async def item_selected(self, interaction: discord.Interaction):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
|
||||
item_name = interaction.data["values"][0]
|
||||
self.selected_item = item_name # Store selected item
|
||||
self.selected_item = item_name
|
||||
item_data = self.inventory[item_name]
|
||||
|
||||
embed = discord.Embed(
|
||||
|
@ -240,25 +269,27 @@ class UseItemView(View):
|
|||
self.ctx = ctx
|
||||
self.item = item
|
||||
self.item_data = item_data
|
||||
self.value = False # Track if item was used
|
||||
self.value = False
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
if interaction.user != self.ctx.author:
|
||||
await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
@button(label="Use Item", style=discord.ButtonStyle.green)
|
||||
async def use_item(self, interaction: discord.Interaction, button: Button):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
|
||||
self.value = True # Item was used
|
||||
await interaction.response.edit_message(
|
||||
content=f"Processing use of {self.item}...",
|
||||
embed=None,
|
||||
view=None
|
||||
self.value = True
|
||||
embed = discord.Embed(
|
||||
title="Using Item",
|
||||
description=f"Processing use of {self.item}...",
|
||||
color=discord.Color.green()
|
||||
)
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
self.stop()
|
||||
|
||||
@button(label="Cancel", style=discord.ButtonStyle.red)
|
||||
async def cancel(self, interaction: discord.Interaction, button: Button):
|
||||
if interaction.user != self.ctx.author:
|
||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
||||
self.value = False # Item was not used
|
||||
self.value = False
|
||||
await interaction.response.edit_message(content="Cancelled.", embed=None, view=None)
|
||||
self.stop()
|
Loading…
Add table
Reference in a new issue