322 lines
No EOL
13 KiB
Python
322 lines
No EOL
13 KiB
Python
import discord
|
|
from discord.ui import View, Select, Button, button, Item
|
|
from typing import Optional, List, Dict, Any
|
|
from redbot.core.utils.chat_formatting import box, humanize_list
|
|
import asyncio
|
|
|
|
class ShopView(View):
|
|
def __init__(self, ctx, shops: Dict[str, Any], timeout: int = 180): # 3 minutes timeout
|
|
super().__init__(timeout=timeout)
|
|
self.ctx = ctx
|
|
self.shops = shops
|
|
self.current_shop = None
|
|
self.current_item = None
|
|
self.quantity = None
|
|
self.message = None # Store message for timeout handling
|
|
self.setup_shop_select()
|
|
cancel_button = Button(label="Cancel", style=discord.ButtonStyle.red, custom_id="cancel", row=4)
|
|
cancel_button.callback = self.cancel
|
|
self.add_item(cancel_button)
|
|
|
|
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)
|
|
|
|
async def cancel(self, interaction: discord.Interaction):
|
|
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 # Explicitly set to row 0
|
|
)
|
|
shop_select.callback = self.shop_selected
|
|
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
|
|
|
|
# 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 # Explicitly set to 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):
|
|
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]
|
|
|
|
# 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
|
|
|
|
async def on_timeout(self) -> None:
|
|
if self.message:
|
|
await self.message.edit(content="Shop menu timed out after 3 minutes of inactivity.", view=None)
|
|
|
|
class PurchaseView(View):
|
|
def __init__(self, ctx, shop: str, item: str, item_data: Dict[str, Any], timeout: int = 180): # 3 minutes timeout
|
|
super().__init__(timeout=timeout)
|
|
self.ctx = ctx
|
|
self.shop = shop
|
|
self.item = item
|
|
self.item_data = item_data
|
|
self.quantity = None
|
|
self.message = None # Store message for timeout handling
|
|
|
|
# Add buttons with explicit row assignments
|
|
buy_one = Button(label="Buy 1", style=discord.ButtonStyle.green, row=0)
|
|
buy_one.callback = self.buy_one
|
|
self.add_item(buy_one)
|
|
|
|
buy_five = Button(label="Buy 5", style=discord.ButtonStyle.green, row=0)
|
|
buy_five.callback = self.buy_five
|
|
self.add_item(buy_five)
|
|
|
|
custom = Button(label="Custom Amount", style=discord.ButtonStyle.blurple, row=0)
|
|
custom.callback = self.custom_amount
|
|
self.add_item(custom)
|
|
|
|
cancel = Button(label="Cancel", style=discord.ButtonStyle.red, row=0)
|
|
cancel.callback = self.cancel
|
|
self.add_item(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
|
|
|
|
def build_embed(self) -> discord.Embed:
|
|
e = discord.Embed(
|
|
title=f"Purchase {self.item}",
|
|
description=self.item_data["Info"],
|
|
color=discord.Color.blue()
|
|
)
|
|
|
|
# First row of fields
|
|
e.add_field(name="Stock", value="Infinite" if self.item_data["Qty"] == "--" else self.item_data["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)
|
|
|
|
# Add role in a new row if it's a role type item
|
|
if self.item_data["Type"] == "role":
|
|
e.add_field(name="Role", value=self.item_data["Role"], inline=False)
|
|
|
|
e.set_footer(text="Select a quantity to purchase")
|
|
return e
|
|
|
|
async def buy_one(self, interaction: discord.Interaction):
|
|
self.quantity = 1
|
|
await self.handle_purchase(interaction)
|
|
|
|
async def buy_five(self, interaction: discord.Interaction):
|
|
self.quantity = 5
|
|
await self.handle_purchase(interaction)
|
|
|
|
async def custom_amount(self, interaction: discord.Interaction):
|
|
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=60.0, check=check) # 1 minute to type amount
|
|
self.quantity = int(msg.content)
|
|
await self.handle_purchase(interaction)
|
|
except asyncio.TimeoutError:
|
|
await interaction.message.reply("Custom amount entry timed out after 1 minute.", mention_author=True)
|
|
self.stop()
|
|
|
|
async def cancel(self, interaction: discord.Interaction):
|
|
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.quantity is None:
|
|
await interaction.response.send_message(
|
|
"Invalid quantity specified.",
|
|
ephemeral=True
|
|
)
|
|
return
|
|
|
|
# Validate quantity for random items
|
|
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)
|
|
|
|
async def on_timeout(self) -> None:
|
|
if self.message:
|
|
await self.message.edit(content="Purchase menu timed out after 3 minutes of inactivity.", view=None)
|
|
|
|
class InventoryView(View):
|
|
def __init__(self, ctx, inventory: Dict[str, Any], timeout: int = 180): # 3 minutes timeout
|
|
super().__init__(timeout=timeout)
|
|
self.ctx = ctx
|
|
self.inventory = inventory
|
|
self.selected_item = None
|
|
self.message = None # Store message for timeout handling
|
|
self.setup_inventory_select()
|
|
|
|
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="Close", style=discord.ButtonStyle.red, custom_id="inventory_close", row=4)
|
|
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):
|
|
if interaction.user != self.ctx.author:
|
|
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
|
|
|
|
self.selected_item = interaction.data["values"][0]
|
|
self.stop()
|
|
|
|
async def on_timeout(self) -> None:
|
|
if self.message:
|
|
await self.message.edit(content="Inventory menu timed out after 3 minutes of inactivity.", view=None)
|
|
|
|
class UseItemView(View):
|
|
def __init__(self, ctx, item_name: str, timeout: int = 60):
|
|
super().__init__(timeout=timeout)
|
|
self.ctx = ctx
|
|
self.item_name = item_name
|
|
self.value = None
|
|
self.message = None
|
|
|
|
@button(label="Use", style=discord.ButtonStyle.green)
|
|
async def use(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
|
|
self.stop()
|
|
await interaction.response.defer()
|
|
|
|
@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
|
|
self.stop()
|
|
await interaction.response.defer()
|
|
|
|
async def on_timeout(self) -> None:
|
|
if self.message:
|
|
await self.message.edit(content="Item use menu timed out.", view=None) |