Implement user-specific interaction checks in ShopView, PurchaseView, and InventoryView to prevent unauthorized access. Update timeout handling to provide clearer feedback messages upon inactivity. Refactor button layout for better organization and improve embed field formatting for item details.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-26 00:46:18 -04:00
parent 35e0ac182d
commit dab195c57f

View file

@ -2,6 +2,7 @@ import discord
from discord.ui import View, Select, Button, button, Item from discord.ui import View, Select, Button, button, Item
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from redbot.core.utils.chat_formatting import box, humanize_list from redbot.core.utils.chat_formatting import box, humanize_list
import asyncio
class ShopView(View): class ShopView(View):
def __init__(self, ctx, shops: Dict[str, Any], timeout: int = 180): # 3 minutes timeout def __init__(self, ctx, shops: Dict[str, Any], timeout: int = 180): # 3 minutes timeout
@ -50,6 +51,9 @@ 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
@ -89,6 +93,9 @@ class ShopView(View):
await interaction.response.edit_message(embed=embed, 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]
@ -106,8 +113,7 @@ class ShopView(View):
async def on_timeout(self) -> None: async def on_timeout(self) -> None:
if self.message: if self.message:
await self.message.reply("Shop menu timed out after 3 minutes of inactivity.", mention_author=True) await self.message.edit(content="Shop menu timed out after 3 minutes of inactivity.", view=None)
await self.message.edit(view=None)
class PurchaseView(View): class PurchaseView(View):
def __init__(self, ctx, shop: str, item: str, item_data: Dict[str, Any], timeout: int = 180): # 3 minutes timeout def __init__(self, ctx, shop: str, item: str, item_data: Dict[str, Any], timeout: int = 180): # 3 minutes timeout
@ -120,19 +126,19 @@ class PurchaseView(View):
self.message = None # Store message for timeout handling self.message = None # Store message for timeout handling
# Add buttons with explicit row assignments # Add buttons with explicit row assignments
buy_one = Button(label="Buy 1", style=discord.ButtonStyle.green, row=1) buy_one = Button(label="Buy 1", style=discord.ButtonStyle.green, row=0)
buy_one.callback = self.buy_one buy_one.callback = self.buy_one
self.add_item(buy_one) self.add_item(buy_one)
buy_five = Button(label="Buy 5", style=discord.ButtonStyle.green, row=1) buy_five = Button(label="Buy 5", style=discord.ButtonStyle.green, row=0)
buy_five.callback = self.buy_five buy_five.callback = self.buy_five
self.add_item(buy_five) self.add_item(buy_five)
custom = Button(label="Custom Amount", style=discord.ButtonStyle.blurple, row=1) custom = Button(label="Custom Amount", style=discord.ButtonStyle.blurple, row=0)
custom.callback = self.custom_amount custom.callback = self.custom_amount
self.add_item(custom) self.add_item(custom)
cancel = Button(label="Cancel", style=discord.ButtonStyle.red, row=1) cancel = Button(label="Cancel", style=discord.ButtonStyle.red, row=0)
cancel.callback = self.cancel cancel.callback = self.cancel
self.add_item(cancel) self.add_item(cancel)
@ -148,26 +154,27 @@ class PurchaseView(View):
description=self.item_data["Info"], description=self.item_data["Info"],
color=discord.Color.blue() 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) # 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="Cost", value=self.item_data["Cost"], inline=True)
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)
# Add role in a new row if it's a role type item
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=False)
e.set_footer(text="Select a quantity to purchase") e.set_footer(text="Select a quantity to purchase")
return e return e
@button(label="Buy 1", style=discord.ButtonStyle.green, row=1) async def buy_one(self, interaction: discord.Interaction):
async def buy_one(self, interaction: discord.Interaction, button: Button):
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) async def buy_five(self, interaction: discord.Interaction):
async def buy_five(self, interaction: discord.Interaction, button: Button):
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)
async def custom_amount(self, interaction: discord.Interaction): async def custom_amount(self, interaction: discord.Interaction):
embed = discord.Embed( embed = discord.Embed(
title="Custom Purchase Amount", title="Custom Purchase Amount",
@ -191,8 +198,7 @@ class PurchaseView(View):
await interaction.message.reply("Custom amount entry timed out after 1 minute.", mention_author=True) await interaction.message.reply("Custom amount entry timed out after 1 minute.", mention_author=True)
self.stop() self.stop()
@button(label="Cancel", style=discord.ButtonStyle.red, row=1) async def cancel(self, interaction: discord.Interaction):
async def cancel(self, interaction: discord.Interaction, button: Button):
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() self.stop()
@ -235,8 +241,7 @@ class PurchaseView(View):
async def on_timeout(self) -> None: async def on_timeout(self) -> None:
if self.message: if self.message:
await self.message.reply("Purchase menu timed out after 3 minutes of inactivity.", mention_author=True) await self.message.edit(content="Purchase menu timed out after 3 minutes of inactivity.", view=None)
await self.message.edit(view=None)
class InventoryView(View): class InventoryView(View):
def __init__(self, ctx, inventory: Dict[str, Any], timeout: int = 180): # 3 minutes timeout def __init__(self, ctx, inventory: Dict[str, Any], timeout: int = 180): # 3 minutes timeout
@ -281,67 +286,40 @@ 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):
item_name = interaction.data["values"][0] if interaction.user != self.ctx.author:
self.selected_item = item_name return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
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 self.selected_item = interaction.data["values"][0]
await use_view.wait() self.stop()
if use_view.value: # If item was used
self.stop() # Stop the inventory view
else:
await interaction.response.edit_message(embed=embed)
async def on_timeout(self) -> None: async def on_timeout(self) -> None:
if self.message: if self.message:
await self.message.reply("Inventory menu timed out after 3 minutes of inactivity.", mention_author=True) await self.message.edit(content="Inventory menu timed out after 3 minutes of inactivity.", view=None)
await self.message.edit(view=None)
class UseItemView(View): class UseItemView(View):
def __init__(self, ctx, item: str, item_data: Dict[str, Any], timeout: int = 180): # 3 minutes timeout def __init__(self, ctx, item_name: str, timeout: int = 60):
super().__init__(timeout=timeout) super().__init__(timeout=timeout)
self.ctx = ctx self.ctx = ctx
self.item = item self.item_name = item_name
self.item_data = item_data self.value = None
self.value = False self.message = None
self.message = None # Store message for timeout handling
async def interaction_check(self, interaction: discord.Interaction) -> bool: @button(label="Use", style=discord.ButtonStyle.green)
async def use(self, interaction: discord.Interaction, button: Button):
if interaction.user != self.ctx.author: if interaction.user != self.ctx.author:
await interaction.response.send_message("This menu is not for you!", ephemeral=True) return 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 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() self.stop()
await interaction.response.defer()
@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:
return await interaction.response.send_message("This menu is not for you!", ephemeral=True)
self.value = False self.value = False
await interaction.response.edit_message(content="Cancelled.", embed=None, view=None)
self.stop() self.stop()
await interaction.response.defer()
async def on_timeout(self) -> None: async def on_timeout(self) -> None:
if self.message: if self.message:
await self.message.reply("Item use menu timed out after 3 minutes of inactivity.", mention_author=True) await self.message.edit(content="Item use menu timed out.", view=None)
await self.message.edit(view=None)