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-------------------------------------
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.max_concurrency(1, commands.BucketType.user)
|
|
||||||
async def inventory(self, ctx):
|
async def inventory(self, ctx):
|
||||||
"""Displays your purchased items."""
|
"""Displays your purchased items."""
|
||||||
try:
|
try:
|
||||||
|
@ -118,20 +117,26 @@ class Shop(commands.Cog):
|
||||||
|
|
||||||
inventory = await instance.Inventory.all()
|
inventory = await instance.Inventory.all()
|
||||||
if not inventory:
|
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)
|
embed = discord.Embed(
|
||||||
await ctx.send(
|
title=f"{ctx.author.display_name}'s Inventory",
|
||||||
f"{ctx.author.mention}'s Inventory",
|
color=discord.Color.blue()
|
||||||
view=view
|
|
||||||
)
|
)
|
||||||
|
view = InventoryView(ctx, inventory)
|
||||||
|
await ctx.send(embed=embed, view=view)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await view.wait()
|
await view.wait()
|
||||||
if view.selected_item: # An item was selected to use
|
if view.selected_item: # An item was selected to use
|
||||||
await self.pending_prompt(ctx, instance, inventory, view.selected_item)
|
await self.pending_prompt(ctx, instance, inventory, view.selected_item)
|
||||||
except asyncio.TimeoutError:
|
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):
|
async def inv_hook(self, user):
|
||||||
"""Inventory Hook for outside cogs
|
"""Inventory Hook for outside cogs
|
||||||
|
@ -165,7 +170,6 @@ class Shop(commands.Cog):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@shop.command()
|
@shop.command()
|
||||||
@commands.max_concurrency(1, commands.BucketType.user)
|
|
||||||
async def buy(self, ctx, *purchase):
|
async def buy(self, ctx, *purchase):
|
||||||
"""Opens the shop menu or directly purchases an item.
|
"""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.")
|
return await ctx.send("You can't use this command in DMs when not in global mode.")
|
||||||
|
|
||||||
if not await instance.Shops():
|
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():
|
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()
|
shops = await instance.Shops.all()
|
||||||
available_shops = await self.check_availability(ctx, shops)
|
available_shops = await self.check_availability(ctx, shops)
|
||||||
|
|
||||||
if not available_shops:
|
if not available_shops:
|
||||||
return await ctx.send(
|
embed = discord.Embed(
|
||||||
"Either no items have been created, you need a higher role, "
|
title="No Access",
|
||||||
"or this command should be used in a server and not DMs."
|
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:
|
if purchase:
|
||||||
try:
|
try:
|
||||||
shop, item = purchase
|
shop, item = purchase
|
||||||
except ValueError:
|
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:
|
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
|
# Create purchase view directly for the specified item
|
||||||
item_data = shops[shop]["Items"].get(item)
|
item_data = shops[shop]["Items"].get(item)
|
||||||
if not item_data:
|
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)
|
view = PurchaseView(ctx, shop, item, item_data)
|
||||||
embed = view.build_embed()
|
embed = view.build_embed()
|
||||||
|
@ -222,24 +253,26 @@ class Shop(commands.Cog):
|
||||||
sm = ShopManager(ctx, instance, user_data)
|
sm = ShopManager(ctx, instance, user_data)
|
||||||
await sm.order(shop, item, view.quantity)
|
await sm.order(shop, item, view.quantity)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send("Purchase menu timed out.")
|
await ctx.send("Purchase menu timed out.", ephemeral=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Open interactive shop menu
|
# Open interactive shop menu
|
||||||
view = ShopView(ctx, shops)
|
embed = discord.Embed(
|
||||||
await ctx.send(
|
title="Welcome to the Shop",
|
||||||
f"Welcome to the shop, {ctx.author.mention}!",
|
description=f"Welcome {ctx.author.mention}! Please select a shop to browse.",
|
||||||
view=view
|
color=discord.Color.blue()
|
||||||
)
|
)
|
||||||
|
view = ShopView(ctx, shops)
|
||||||
|
await ctx.send(embed=embed, view=view)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await view.wait()
|
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)
|
user_data = await self.get_instance(ctx, user=ctx.author)
|
||||||
sm = ShopManager(ctx, instance, user_data)
|
sm = ShopManager(ctx, instance, user_data)
|
||||||
await sm.order(view.current_shop, view.current_item, view.quantity)
|
await sm.order(view.current_shop, view.current_item, view.quantity)
|
||||||
except asyncio.TimeoutError:
|
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)
|
@commands.max_concurrency(1, commands.BucketType.user)
|
||||||
@shop.command()
|
@shop.command()
|
||||||
|
|
117
shop/ui.py
117
shop/ui.py
|
@ -10,8 +10,23 @@ class ShopView(View):
|
||||||
self.shops = shops
|
self.shops = shops
|
||||||
self.current_shop = None
|
self.current_shop = None
|
||||||
self.current_item = None
|
self.current_item = None
|
||||||
self.quantity = None # Add quantity attribute
|
self.quantity = None
|
||||||
self.setup_shop_select()
|
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):
|
def setup_shop_select(self):
|
||||||
options = []
|
options = []
|
||||||
|
@ -33,9 +48,6 @@ class ShopView(View):
|
||||||
self.add_item(shop_select)
|
self.add_item(shop_select)
|
||||||
|
|
||||||
async def shop_selected(self, interaction: discord.Interaction):
|
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]
|
shop_name = interaction.data["values"][0]
|
||||||
self.current_shop = shop_name
|
self.current_shop = shop_name
|
||||||
|
|
||||||
|
@ -48,6 +60,12 @@ class ShopView(View):
|
||||||
items = self.shops[shop_name]["Items"]
|
items = self.shops[shop_name]["Items"]
|
||||||
options = []
|
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():
|
for item_name, item_data in items.items():
|
||||||
qty = "∞" if item_data["Qty"] == "--" else item_data["Qty"]
|
qty = "∞" if item_data["Qty"] == "--" else item_data["Qty"]
|
||||||
desc = f"Cost: {item_data['Cost']} | Stock: {qty}"
|
desc = f"Cost: {item_data['Cost']} | Stock: {qty}"
|
||||||
|
@ -66,12 +84,9 @@ class ShopView(View):
|
||||||
item_select.callback = self.item_selected
|
item_select.callback = self.item_selected
|
||||||
self.add_item(item_select)
|
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):
|
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]
|
item_name = interaction.data["values"][0]
|
||||||
self.current_item = item_name
|
self.current_item = item_name
|
||||||
item_data = self.shops[self.current_shop]["Items"][item_name]
|
item_data = self.shops[self.current_shop]["Items"][item_name]
|
||||||
|
@ -94,8 +109,14 @@ class PurchaseView(View):
|
||||||
self.shop = shop
|
self.shop = shop
|
||||||
self.item = item
|
self.item = item
|
||||||
self.item_data = item_data
|
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:
|
def build_embed(self) -> discord.Embed:
|
||||||
e = discord.Embed(
|
e = discord.Embed(
|
||||||
title=f"Purchase {self.item}",
|
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)
|
e.add_field(name="Type", value=self.item_data["Type"].title(), inline=True)
|
||||||
if self.item_data["Type"] == "role":
|
if self.item_data["Type"] == "role":
|
||||||
e.add_field(name="Role", value=self.item_data["Role"], inline=True)
|
e.add_field(name="Role", value=self.item_data["Role"], inline=True)
|
||||||
|
e.set_footer(text="Select a quantity to purchase")
|
||||||
return e
|
return e
|
||||||
|
|
||||||
@button(label="Buy 1", style=discord.ButtonStyle.green, row=1)
|
@button(label="Buy 1", style=discord.ButtonStyle.green, row=1)
|
||||||
async def buy_one(self, interaction: discord.Interaction, button: Button):
|
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
|
self.quantity = 1
|
||||||
await self.handle_purchase(interaction)
|
await self.handle_purchase(interaction)
|
||||||
|
|
||||||
@button(label="Buy 5", style=discord.ButtonStyle.green, row=1)
|
@button(label="Buy 5", style=discord.ButtonStyle.green, row=1)
|
||||||
async def buy_five(self, interaction: discord.Interaction, button: Button):
|
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
|
self.quantity = 5
|
||||||
await self.handle_purchase(interaction)
|
await self.handle_purchase(interaction)
|
||||||
|
|
||||||
@button(label="Custom Amount", style=discord.ButtonStyle.blurple, row=1)
|
@button(label="Custom Amount", style=discord.ButtonStyle.blurple, row=1)
|
||||||
async def custom_amount(self, interaction: discord.Interaction, button: Button):
|
async def custom_amount(self, interaction: discord.Interaction, button: Button):
|
||||||
if interaction.user != self.ctx.author:
|
embed = discord.Embed(
|
||||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
title="Custom Purchase Amount",
|
||||||
|
description="How many would you like to buy? Type a number in chat.",
|
||||||
await interaction.response.send_message(
|
color=discord.Color.blue()
|
||||||
"How many would you like to buy? Type a number:",
|
|
||||||
ephemeral=True
|
|
||||||
)
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed)
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
return (
|
return (
|
||||||
|
@ -147,12 +164,12 @@ class PurchaseView(View):
|
||||||
await self.handle_purchase(interaction)
|
await self.handle_purchase(interaction)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await interaction.followup.send("Purchase cancelled - took too long to respond.", ephemeral=True)
|
await interaction.followup.send("Purchase cancelled - took too long to respond.", ephemeral=True)
|
||||||
|
self.stop()
|
||||||
|
|
||||||
@button(label="Cancel", style=discord.ButtonStyle.red, row=1)
|
@button(label="Cancel", style=discord.ButtonStyle.red, row=1)
|
||||||
async def cancel(self, interaction: discord.Interaction, button: Button):
|
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)
|
await interaction.response.edit_message(content="Purchase cancelled.", embed=None, view=None)
|
||||||
|
self.stop()
|
||||||
|
|
||||||
async def handle_purchase(self, interaction: discord.Interaction):
|
async def handle_purchase(self, interaction: discord.Interaction):
|
||||||
# Validate quantity
|
# Validate quantity
|
||||||
|
@ -172,21 +189,36 @@ class PurchaseView(View):
|
||||||
|
|
||||||
total_cost = self.item_data["Cost"] * self.quantity
|
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
|
# This will be handled by the Shop cog's order method
|
||||||
self.stop()
|
self.stop()
|
||||||
await interaction.response.edit_message(
|
await interaction.response.edit_message(embed=embed, view=None)
|
||||||
content=f"Processing purchase of {self.quantity}x {self.item}...",
|
|
||||||
embed=None,
|
|
||||||
view=None
|
|
||||||
)
|
|
||||||
|
|
||||||
class InventoryView(View):
|
class InventoryView(View):
|
||||||
def __init__(self, ctx, inventory: Dict[str, Any], timeout: int = 60):
|
def __init__(self, ctx, inventory: Dict[str, Any], timeout: int = 60):
|
||||||
super().__init__(timeout=timeout)
|
super().__init__(timeout=timeout)
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.inventory = inventory
|
self.inventory = inventory
|
||||||
self.selected_item = None # Track selected item
|
self.selected_item = None
|
||||||
self.setup_inventory_select()
|
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):
|
def setup_inventory_select(self):
|
||||||
options = []
|
options = []
|
||||||
|
@ -208,11 +240,8 @@ class InventoryView(View):
|
||||||
self.add_item(inv_select)
|
self.add_item(inv_select)
|
||||||
|
|
||||||
async def item_selected(self, interaction: discord.Interaction):
|
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]
|
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]
|
item_data = self.inventory[item_name]
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
|
@ -240,25 +269,27 @@ class UseItemView(View):
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.item = item
|
self.item = item
|
||||||
self.item_data = item_data
|
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)
|
@button(label="Use Item", style=discord.ButtonStyle.green)
|
||||||
async def use_item(self, interaction: discord.Interaction, button: Button):
|
async def use_item(self, interaction: discord.Interaction, button: Button):
|
||||||
if interaction.user != self.ctx.author:
|
self.value = True
|
||||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
embed = discord.Embed(
|
||||||
|
title="Using Item",
|
||||||
self.value = True # Item was used
|
description=f"Processing use of {self.item}...",
|
||||||
await interaction.response.edit_message(
|
color=discord.Color.green()
|
||||||
content=f"Processing use of {self.item}...",
|
|
||||||
embed=None,
|
|
||||||
view=None
|
|
||||||
)
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=None)
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
@button(label="Cancel", style=discord.ButtonStyle.red)
|
@button(label="Cancel", style=discord.ButtonStyle.red)
|
||||||
async def cancel(self, interaction: discord.Interaction, button: Button):
|
async def cancel(self, interaction: discord.Interaction, button: Button):
|
||||||
if interaction.user != self.ctx.author:
|
self.value = False
|
||||||
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
|
||||||
self.value = False # Item was not used
|
|
||||||
await interaction.response.edit_message(content="Cancelled.", embed=None, view=None)
|
await interaction.response.edit_message(content="Cancelled.", embed=None, view=None)
|
||||||
self.stop()
|
self.stop()
|
Loading…
Add table
Reference in a new issue