import discord from discord.ui import View, Select, Button, button from typing import Optional, List, Dict, Any from redbot.core.utils.chat_formatting import box, humanize_list class ShopView(View): def __init__(self, ctx, shops: Dict[str, Any], timeout: int = 60): super().__init__(timeout=timeout) self.ctx = ctx self.shops = shops self.current_shop = None self.current_item = None 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 = [] for shop_name, shop_data in self.shops.items(): if shop_data["Items"]: # Only show shops with items options.append(discord.SelectOption( label=shop_name[:25], # Discord limit description=f"{len(shop_data['Items'])} items", value=shop_name )) if options: shop_select = Select( placeholder="Select a shop", options=options, row=0 ) shop_select.callback = self.shop_selected self.add_item(shop_select) async def shop_selected(self, interaction: discord.Interaction): shop_name = interaction.data["values"][0] self.current_shop = shop_name # Clear previous item select if it exists for item in self.children[:]: if isinstance(item, Select) and item.row == 1: self.remove_item(item) # Add new item select 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}" options.append(discord.SelectOption( label=item_name[:25], description=desc, value=item_name )) if options: item_select = Select( placeholder="Select an item", options=options[:25], # Discord limit row=1 ) item_select.callback = self.item_selected self.add_item(item_select) await interaction.response.edit_message(embed=embed, view=self) async def item_selected(self, interaction: discord.Interaction): item_name = interaction.data["values"][0] self.current_item = item_name item_data = self.shops[self.current_shop]["Items"][item_name] # Create purchase confirmation view purchase_view = PurchaseView(self.ctx, self.current_shop, item_name, item_data) embed = purchase_view.build_embed() await interaction.response.edit_message(embed=embed, view=purchase_view) # Wait for the purchase view to complete await purchase_view.wait() self.quantity = purchase_view.quantity # Store the quantity from purchase view self.stop() # Stop the shop view since we're done class PurchaseView(View): def __init__(self, ctx, shop: str, item: str, item_data: Dict[str, Any], timeout: int = 60): super().__init__(timeout=timeout) self.ctx = ctx self.shop = shop self.item = item self.item_data = item_data 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}", description=self.item_data["Info"], color=discord.Color.blue() ) qty = "Infinite" if self.item_data["Qty"] == "--" else self.item_data["Qty"] e.add_field(name="Stock", value=qty, inline=True) e.add_field(name="Cost", value=self.item_data["Cost"], inline=True) 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): 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): 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): 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 ( m.author == self.ctx.author and m.channel == self.ctx.channel and m.content.isdigit() ) try: msg = await self.ctx.bot.wait_for("message", timeout=30.0, check=check) self.quantity = int(msg.content) 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): await interaction.response.edit_message(content="Purchase cancelled.", embed=None, view=None) self.stop() async def handle_purchase(self, interaction: discord.Interaction): # Validate quantity if self.item_data["Type"] == "random" and self.quantity != 1: await interaction.response.send_message( "You can only buy 1 random item at a time.", ephemeral=True ) return if self.item_data["Qty"] != "--" and self.quantity > self.item_data["Qty"]: await interaction.response.send_message( f"Not enough stock! Only {self.item_data['Qty']} available.", ephemeral=True ) return 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(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 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 = [] for item_name, item_data in self.inventory.items(): desc = f"Quantity: {item_data['Qty']} | Type: {item_data['Type']}" options.append(discord.SelectOption( label=item_name[:25], description=desc, value=item_name )) if options: inv_select = Select( placeholder="Select an item to view/use", options=options[:25], # Discord limit row=0 ) inv_select.callback = self.item_selected self.add_item(inv_select) async def item_selected(self, interaction: discord.Interaction): item_name = interaction.data["values"][0] self.selected_item = item_name item_data = self.inventory[item_name] embed = discord.Embed( title=item_name, description=item_data["Info"], color=discord.Color.blue() ) embed.add_field(name="Quantity", value=item_data["Qty"], inline=True) embed.add_field(name="Type", value=item_data["Type"].title(), inline=True) if item_data["Type"] == "role": use_view = UseItemView(self.ctx, item_name, item_data) await interaction.response.edit_message(embed=embed, view=use_view) # Wait for the use view to complete await use_view.wait() if use_view.value: # If item was used self.stop() # Stop the inventory view else: await interaction.response.edit_message(embed=embed) class UseItemView(View): def __init__(self, ctx, item: str, item_data: Dict[str, Any], timeout: int = 60): super().__init__(timeout=timeout) self.ctx = ctx self.item = item self.item_data = item_data 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): 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): self.value = False await interaction.response.edit_message(content="Cancelled.", embed=None, view=None) self.stop()