Update Shop cog to add refund functionality for role items, allowing users to return roles for inventory credits. Implement interactive confirmation process for refunds and enhance item gifting logic to restrict multiple role item ownership. Improve error handling and user feedback during transactions.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-26 01:54:58 -04:00
parent 2f0baffab2
commit 12bd541b64
3 changed files with 253 additions and 26 deletions

View file

@ -1,7 +1,6 @@
{
"author": [
"Valerie",
"Rose Haven Network"
"Valerie"
],
"install_msg": "Thanks for adding Ruby Cogs! Join our Discord @ https://discord.gg/5CA8sewarU",
"name": "Ruby Cogs",

View file

@ -1,12 +1,30 @@
{
"author" : ["Redjumpman (Redjumpman#1337)"],
"install_msg" : "Thank you for installing shop. Be sure to check out the wiki here: https://github.com/Redjumpman/Jumper-Plugins/wiki/Shop-Red-V3\nThis cog comes with a bundled CSV file as an example for adding bulk shops and items.",
"name" : "Shop",
"short" : "Create, buy, trade, and redeem items.",
"requirements" : ["tabulate"],
"description" : "Shop system that allows for multiple shops with their own list of items for sale. Players can purchase these items with economy currency and redeem them for roles, or other server defined value.",
"permissions" : ["Manage Messages", "Embed Links", "Add Reactions", "Manage Roles"],
"tags" : ["Economy", "Fun", "Shop"],
"min_python_version": [3, 6, 0],
"author": [
"Redjumpman (Redjumpman#1337)",
"Valerie"
],
"install_msg": "Thank you for installing shop. Be sure to check out the wiki here: https://github.com/Redjumpman/Jumper-Plugins/wiki/Shop-Red-V3\nThis cog comes with a bundled CSV file as an example for adding bulk shops and items.",
"name": "Shop",
"short": "Create, buy, trade, and redeem items.",
"requirements": [
"tabulate"
],
"description": "Shop system that allows for multiple shops with their own list of items for sale. Players can purchase these items with economy currency and redeem them for roles, or other server defined value.",
"permissions": [
"Manage Messages",
"Embed Links",
"Add Reactions",
"Manage Roles"
],
"tags": [
"Economy",
"Fun",
"Shop"
],
"min_python_version": [
3,
6,
0
],
"end_user_data_statement": "This cog stores discord IDs as needed for operation."
}
}

View file

@ -396,6 +396,131 @@ class Shop(commands.Cog):
await instance.Trading.set(not status)
await ctx.send("Trading with you is now {}.".format("disabled" if status else "enabled"))
@shop.command()
async def refund(self, ctx, *, item: str):
"""Refunds a role item back to your inventory.
This command allows you to remove a role and get the shop item back in your inventory.
You must specify the exact name of the role item from the shop.
Example:
[p]shop refund VIP Role
"""
try:
instance = await self.get_instance(ctx, settings=True)
except AttributeError:
return await ctx.send("You can't use this command in DMs when not in global mode.")
# Find the role item in any shop
role_item = None
role_name = None
shop_name = None
async with instance.Shops() as shops:
for shop in shops.values():
for item_name, item_data in shop["Items"].items():
if (item_name.lower() == item.lower() and
item_data["Type"].lower() == "role"):
role_item = item_name
role_name = item_data["Role"]
break
if role_item:
break
if not role_item:
return await ctx.send(
embed=discord.Embed(
title="Item Not Found",
description=f"Could not find a role item named '{item}' in any shop.",
color=discord.Color.red()
)
)
# Check if user has the role
if role_name.startswith('<@&') and role_name.endswith('>'):
role_id = int(role_name.strip('<@&>'))
role = ctx.guild.get_role(role_id)
else:
role = discord.utils.get(ctx.guild.roles, name=role_name)
if not role:
return await ctx.send(
embed=discord.Embed(
title="Role Not Found",
description=f"The role associated with this item no longer exists on the server.",
color=discord.Color.red()
)
)
if role not in ctx.author.roles:
return await ctx.send(
embed=discord.Embed(
title="Role Not Owned",
description=f"You don't have the `{role.name}` role to refund.",
color=discord.Color.red()
)
)
# Ask for confirmation
embed = discord.Embed(
title="Confirm Role Refund",
description=f"Are you sure you want to remove the `{role.name}` role and get `{role_item}` back in your inventory?",
color=discord.Color.blue()
)
msg = await ctx.send(embed=embed)
# Add reactions for yes/no
await msg.add_reaction("")
await msg.add_reaction("")
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["", ""]
try:
reaction, user = await ctx.bot.wait_for("reaction_add", timeout=30.0, check=check)
if str(reaction.emoji) == "":
try:
await ctx.author.remove_roles(role, reason="Shop role refund")
# Add the role item back to inventory
async with instance.Inventory() as inv:
if role_item in inv:
inv[role_item]["Qty"] += 1
else:
inv[role_item] = {
"Qty": 1,
"Type": "role",
"Info": f"Grants the {role.name} role",
"Role": role_name
}
embed = discord.Embed(
title="Role Refunded",
description=f"The `{role.name}` role has been removed and `{role_item}` has been added to your inventory.",
color=discord.Color.green()
)
await msg.edit(embed=embed)
except discord.Forbidden:
embed = discord.Embed(
title="Refund Failed",
description="I don't have permission to remove that role.",
color=discord.Color.red()
)
await msg.edit(embed=embed)
else:
embed = discord.Embed(
title="Refund Cancelled",
description="Role refund cancelled.",
color=discord.Color.red()
)
await msg.edit(embed=embed)
except asyncio.TimeoutError:
embed = discord.Embed(
title="Timed Out",
description="Role refund timed out.",
color=discord.Color.red()
)
await msg.edit(embed=embed)
@shop.command()
async def version(self, ctx):
"""Shows the current Shop version."""
@ -585,6 +710,7 @@ class Shop(commands.Cog):
"""Gift another user a set number of one of your items.
The item must be in your inventory and have enough to cover the quantity.
For role items, users can only have one copy at a time.
Examples
--------
@ -598,21 +724,61 @@ class Shop(commands.Cog):
settings = await self.get_instance(ctx, settings=True)
if not await settings.Settings.Gifting():
return await ctx.send("Gifting is turned off.")
author_instance = await self.get_instance(ctx, user=ctx.author)
author_inv = await author_instance.Inventory.all()
if item not in author_inv:
return await ctx.send(f"You don't own any `{item}`.")
if author_inv[item]["Qty"] < quantity:
return await ctx.send(f"You don't have that many `{item}` to give.")
# Check if it's a role item
if author_inv[item]["Type"].lower() == "role":
if quantity > 1:
return await ctx.send(
embed=discord.Embed(
title="Invalid Quantity",
description="You can only gift one copy of a role item.",
color=discord.Color.red()
)
)
# Check if recipient already has the role item
user_instance = await self.get_instance(ctx, user=user)
user_inv = await user_instance.Inventory.all()
if item in user_inv:
return await ctx.send(
embed=discord.Embed(
title="Already Owned",
description=f"{user.display_name} already owns this role item. Users can only have one copy at a time.",
color=discord.Color.red()
)
)
sm1 = ShopManager(ctx, instance=None, user_data=author_instance)
await sm1.remove(item, number=quantity)
user_instance = await self.get_instance(ctx, user=user)
sm2 = ShopManager(ctx, instance=None, user_data=user_instance)
await sm2.add(item, author_inv[item], quantity)
await ctx.send(f"{ctx.author.mention} gifted {user.mention} {quantity}x {item}.")
success = await sm2.add(item, author_inv[item], quantity)
if success:
await ctx.send(
embed=discord.Embed(
title="Gift Successful",
description=f"{ctx.author.mention} gifted {user.mention} {quantity}x {item}.",
color=discord.Color.green()
)
)
else:
# If gift failed, give item back to original owner
await sm1.add(item, author_inv[item], quantity)
await ctx.send(
embed=discord.Embed(
title="Gift Failed",
description=f"Could not gift {item} to {user.mention}. The item has been returned to your inventory.",
color=discord.Color.red()
)
)
@shop.command()
@global_permissions()
@ -1293,6 +1459,27 @@ class ShopManager:
)
)
# Check if trying to buy multiple role items
if item_data["Type"].lower() == "role":
if quantity > 1:
return await self.ctx.send(
embed=discord.Embed(
title="Invalid Quantity",
description="You can only purchase one copy of a role item.",
color=discord.Color.red()
)
)
# Check if user already has this role item
async with self.user_data.Inventory() as inv:
if item in inv:
return await self.ctx.send(
embed=discord.Embed(
title="Already Owned",
description="You already own this role item. You can only have one copy at a time.",
color=discord.Color.red()
)
)
# Validate quantity
if quantity is None:
return await self.ctx.send(
@ -1384,15 +1571,29 @@ class ShopManager:
# Update stock and add to inventory
await self.remove_stock(shop, item, stock, quantity)
await self.add_to_inventory(item, item_data, quantity)
success = await self.add_to_inventory(item, item_data, quantity)
await self.ctx.send(
embed=discord.Embed(
title="Purchase Successful",
description=f"{self.ctx.author.mention} purchased {quantity}x {item} for {total_cost} {cur}.",
color=discord.Color.green()
if success:
await self.ctx.send(
embed=discord.Embed(
title="Purchase Successful",
description=f"{self.ctx.author.mention} purchased {quantity}x {item} for {total_cost} {cur}.",
color=discord.Color.green()
)
)
else:
# Refund if inventory add failed (e.g. already had role item)
try:
await bank.deposit_credits(self.ctx.author, total_cost)
except BalanceTooHigh as e:
await bank.set_balance(self.ctx.author, e.max_balance)
await self.ctx.send(
embed=discord.Embed(
title="Purchase Failed",
description="You already own this item. Purchase has been refunded.",
color=discord.Color.red()
)
)
)
async def remove_stock(self, shop, item, current_stock, amount):
"""Safely remove items from stock."""
@ -1409,11 +1610,20 @@ class ShopManager:
async def add_to_inventory(self, item, item_data, quantity):
"""Safely add items to user inventory."""
async with self.user_data.Inventory() as inv:
if item in inv:
inv[item]["Qty"] += quantity
else:
# For role items, only allow one copy
if item_data["Type"].lower() == "role":
if item in inv:
return False # Already has the role item
inv[item] = deepcopy(item_data)
inv[item]["Qty"] = quantity
inv[item]["Qty"] = 1 # Force quantity to 1 for role items
return True
else:
if item in inv:
inv[item]["Qty"] += quantity
else:
inv[item] = deepcopy(item_data)
inv[item]["Qty"] = quantity
return True
async def remove(self, item, number=1):
"""Remove an item from user's inventory."""