Files
DiscordBot/cogs/economy.py
T
Nobody2503 b315069b1c feat: Add bank and wallet balance commands with improved transfer validation
- Added /bank and /wallet commands to check user balances
- Enhanced transfer validation to check for valid number input
- Included debug token logging in main function (to be removed in production)
- Added account creation logic for new users in balance commands
- Improved error handling for invalid transfer amounts
2026-06-03 11:56:09 +00:00

496 lines
22 KiB
Python
Executable File

import logging
import discord
from discord.ext import commands
from datetime import datetime, timedelta, timezone
from random import randint
from typing import Literal
from utils.bank_functions import (
bank_data,
create_account,
reset_bank,
update_daily_timestamp,
update_money,
)
from utils.sql_commands import DatabaseManager
def validate_transfer(
payer_balance: int, author_id: int, receiver_id: int, amount: int
) -> tuple[bool, str]:
# Validate amount is a positive number
if not isinstance(amount, (int, float)):
return False, "Amount must be a number"
if amount <= 0:
return False, "Please enter an amount greater than 0."
if payer_balance is None:
return False, "Your account does not exist."
if author_id == receiver_id:
return False, "You cannot give yourself money."
if payer_balance < amount:
return False, (
f"You do not have {amount:,}<:flooney:1194943899765051473>. "
f"You have {payer_balance:,}<:flooney:1194943899765051473>."
)
return True, ""
class Economy(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager("")
r"""
.----------------. .----------------. .----------------. .----------------. .-----------------.
| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
| | __ | || | ________ | || | ____ ____ | || | _____ | || | ____ _____ | |
| | / \ | || | |_ ___ `. | || ||_ \ / _|| || | |_ _| | || ||_ \|_ _| | |
| | / /\ \ | || | | | `. \ | || | | \/ | | || | | | | || | | \ | | | |
| | / ____ \ | || | | | | | | || | | |\ /| | | || | | | | || | | |\ \| | | |
| | _/ / \ \_ | || | _| |___.' / | || | _| |_\/_| |_ | || | _| |_ | || | _| |_\ |_ | |
| ||____| |____|| || | |________.' | || ||_____||_____|| || | |_____| | || ||_____|\____| | |
| | | || | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------' '----------------'
"""
@commands.command(
name="give_money",
aliases=["addmoney"],
usage="<member*: @member> <amount*: integer>",
)
@commands.is_owner()
@commands.cooldown(3, 2 * 60, commands.BucketType.user)
async def add_money(
self, ctx: commands.Context, member: discord.Member, amount_str: str
):
"""Add money to a user's bank."""
if member.bot:
return await ctx.reply("You can't add money to a bot", mention_author=False)
if not member:
member = ctx.author
try:
amount = int(amount_str)
except ValueError:
return await ctx.reply("Please enter a valid amount")
if amount <= 0:
return await ctx.reply("Please enter an amount greater than 0")
limit = 1_000_000
if amount > limit:
return await ctx.reply(f"You cannot add money more than {limit:,}")
await update_money(member, amount)
await ctx.reply(
f"You added {amount:,} in {member.mention}'s bank.", mention_author=False
)
@commands.command(
aliases=["remoney"],
usage="<member*: @member> <amount*: integer> <mode: wallet or bank>",
)
@commands.is_owner()
@commands.cooldown(3, 2 * 60, commands.BucketType.user)
async def remove_money(
self, ctx, member: discord.Member, amount_str: str, mode: str = "bank"
):
mode = mode.lower()
if member.bot:
return await ctx.reply(
"You can't remove money from a bot", mention_author=False
)
try:
amount = int(amount_str)
if amount <= 0:
raise ValueError
except ValueError:
return await ctx.reply("Please enter a valid amount")
if mode not in ["wallet", "bank"]:
return await ctx.reply("Please enter either wallet or bank only")
user_balance = await bank_data(member)
current_balance = user_balance["BANK" if mode == "bank" else "WALLET"]
if current_balance < amount:
return await ctx.reply(
f"You can only remove {current_balance:,} from {member.mention}'s {mode}"
)
if mode == "bank":
await update_money(member, bank=-amount)
else:
await update_money(member, wallet=-amount)
await ctx.reply(
f"You removed {amount:,} from {member.mention}'s {mode}",
mention_author=False,
)
@commands.command(usage="<member*: @member>")
@commands.is_owner()
@commands.cooldown(2, 3 * 60, commands.BucketType.user)
async def reset_money(self, ctx, member: discord.Member):
if member.bot:
return await ctx.reply(
"Bots don't have bank accounts", mention_author=False
)
await reset_bank(member)
return await ctx.reply(
f"{member.mention}'s account has been reset", mention_author=False
)
r"""
.----------------. .----------------. .-----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. |
| | ______ | || | __ | || | ____ _____ | || | ___ ____ | |
| | |_ _ \ | || | / \ | || ||_ \|_ _| | || | |_ ||_ _| | |
| | | |_) | | || | / /\ \ | || | | \ | | | || | | |_/ / | |
| | | __'. | || | / ____ \ | || | | |\ \| | | || | | __'. | |
| | _| |__) | | || | _/ / \ \_ | || | _| |_\ |_ | || | _| | \ \_ | |
| | |_______/ | || ||____| |____|| || ||_____|\____| | || | |____||____| | |
| | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------'
"""
@commands.command(
name="balance",
aliases=["bal"],
brief="Check your money",
description="Check how much money you have.",
)
async def _balance(
self, ctx: commands.Context, member: discord.Member | None = None
):
"""
Check how much money you have.
"""
target: discord.Member | discord.User = member or ctx.author
# Ensure the target is not a bot
if target.bot:
return
# Get the user's data
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Create an account if one does not exist
if bank_balance is None or wallet_balance is None:
await create_account(target)
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Reply with the user's balance
await ctx.reply(
f"{target.mention} has {bank_balance:,}<:flooney:1194943899765051473> in their bank and "
f"{wallet_balance:,}<:flooney:1194943899765051473> in their wallet."
)
@commands.command(name="give")
async def _give(self, ctx, target: discord.Member, amount: int):
"""Give money to another user."""
payer_wallet = int((await bank_data(ctx.author)).get("WALLET", 0))
receiver_wallet = int((await bank_data(target)).get("WALLET", 0))
valid, reason = validate_transfer(
payer_wallet, ctx.author.id, target.id, amount
)
if not valid:
return await ctx.reply(reason, mention_author=False)
await update_money(target, wallet=amount)
await update_money(ctx.author, wallet=-amount)
await ctx.reply(
f"You gave {target} {amount:,} flooneys.", mention_author=False
)
@commands.command(name="transfer")
async def _transfer(self, ctx, target: discord.Member, amount: int):
"""
Transfer money from your bank account to another user's bank account.
"""
payer_bank = int((await bank_data(ctx.author)).get("BANK", 0))
receiver_bank = int((await bank_data(target)).get("BANK", 0))
valid, reason = validate_transfer(
payer_bank, ctx.author.id, target.id, amount
)
if not valid:
return await ctx.reply(reason, mention_author=False)
await update_money(target, bank=amount)
await update_money(ctx.author, bank=-amount)
await ctx.reply(
f"Transferred {amount:,} flooneys to {target}.", mention_author=False
)
@commands.command(
name="deposit",
aliases=["dep"],
brief="Deposit money",
description="Deposit money from your wallet to the bank",
)
async def _deposit(self, ctx, amount: int | Literal["all"] = 0) -> None:
"""Deposit money from your wallet to your bank account. Amount can be an integer or 'all' to deposit all money from wallet."""
user_data = await bank_data(ctx.author)
if user_data is None:
await create_account(ctx.author)
user_data = await bank_data(ctx.author)
wallet_balance = int(user_data.get("WALLET", 0))
if amount == "all":
amount = wallet_balance
if 0 <= amount <= wallet_balance:
await update_money(ctx.author, bank=amount, wallet=-amount)
await ctx.reply("Transaction successful.")
else:
await ctx.reply("Insufficient funds in wallet")
@commands.command(name="withdraw", brief="Withdraw money from bank to wallet")
async def _withdraw(self, ctx, amount: int | Literal["all"] = 0) -> None:
"""Withdraw money from your bank account to your wallet. Amount can be an integer or 'all' to withdraw all money from bank."""
user_data = await bank_data(ctx.author)
if user_data is None:
await create_account(ctx.author)
user_data = await bank_data(ctx.author)
bank_balance = int(user_data.get("BANK", 0))
if amount == "all":
amount = bank_balance
if 0 <= amount <= bank_balance:
await update_money(ctx.author, bank=-amount, wallet=amount)
await ctx.reply("Transaction successful.")
else:
await ctx.reply("Insufficient funds")
@commands.command(aliases=["lb"])
@commands.guild_only()
async def leaderboard(self, ctx):
users_data = self.db.fetch_all(
"SELECT * FROM economy ORDER BY BANK + WALLET DESC"
)
leaderboard_entries = []
position = 1
for user_data in users_data:
user_id = int(user_data["ID"])
user = ctx.guild.get_member(user_id) or self.client.get_user(user_id)
if user is None:
try:
user = await self.client.fetch_user(user_id)
user_display = (
user.display_name
if hasattr(user, "display_name")
else str(user)
)
except Exception:
user_display = f"User {user_id}"
else:
user_display = (
user.display_name if hasattr(user, "display_name") else str(user)
)
total_balance = int(user_data["BANK"]) + int(user_data["WALLET"])
flooney_icon = "<:flooney:1194943899765051473>"
if position <= 3:
entry = f"{['🥇', '🥈', '🥉'][position-1]}**{position} {user_display}** -- {total_balance:,}{flooney_icon} "
else:
entry = (
f"**{position} {user_display}** -- {total_balance:,}{flooney_icon}"
)
leaderboard_entries.append(entry)
position += 1
if position > 10:
break
embed = discord.Embed(
title=f"Top {len(leaderboard_entries)} Richest Users - Leaderboard",
description="\n".join(leaderboard_entries),
color=discord.Color(0x00FF00),
timestamp=datetime.utcnow(),
)
embed.set_footer(text=f"GLOBAL - {ctx.guild.name}")
await ctx.reply(embed=embed, mention_author=False)
r"""
.----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
| | ______ | || | _________ | || | _______ | || | _____ | || | ____ | || | ________ | || | _____ | || | ______ | || | __ | || | _____ | || | _____ | || | ____ ____ | |
| | |_ __ \ | || | |_ ___ | | || | |_ __ \ | || | |_ _| | || | .' `. | || | |_ ___ `. | || | |_ _| | || | .' ___ | | || | / \ | || | |_ _| | || | |_ _| | || | |_ _||_ _| | |
| | | |__) | | || | | |_ \_| | || | | |__) | | || | | | | || | / .--. \ | || | | | `. \ | || | | | | || | / .' \_| | || | / /\ \ | || | | | | || | | | | || | \ \ / / | |
| | | ___/ | || | | _| _ | || | | __ / | || | | | | || | | | | | | || | | | | | | || | | | | || | | | | || | / ____ \ | || | | | _ | || | | | _ | || | \ \/ / | |
| | _| |_ | || | _| |___/ | | || | _| | \ \_ | || | _| |_ | || | \ `--' / | || | _| |___.' / | || | _| |_ | || | \ `.___.'\ | || | _/ / \ \_ | || | _| |__/ | | || | _| |__/ | | || | _| |_ | |
| | |_____| | || | |_________| | || | |____| |___| | || | |_____| | || | `.____.' | || | |________.' | || | |_____| | || | `._____.' | || ||____| |____|| || | |________| | || | |________| | || | |______| | |
| | | || | | || | | || | | || | | || | | || | | || | | || | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------'
"""
@commands.command(name="daily", help="Claim your daily pocket money")
async def claim_daily_money(self, ctx: commands.Context) -> None:
"""Claim your daily pocket money. You can claim once every 24 hours."""
try:
user_data = await bank_data(ctx.author)
last_claim = user_data.get("DAILY")
now = discord.utils.utcnow()
if last_claim:
if isinstance(last_claim, (int, float)):
last_claim_dt = datetime.fromtimestamp(last_claim, tz=timezone.utc)
elif isinstance(last_claim, str):
try:
last_claim_dt = datetime.fromtimestamp(
float(last_claim), tz=timezone.utc
)
except ValueError:
last_claim_dt = None
elif isinstance(last_claim, datetime):
last_claim_dt = (
last_claim.replace(tzinfo=timezone.utc)
if last_claim.tzinfo is None
else last_claim
)
else:
last_claim_dt = None
else:
last_claim_dt = None
if not last_claim_dt or (now - last_claim_dt) >= timedelta(days=1):
daily_reward = randint(200, 1000)
await update_money(ctx.author, wallet=daily_reward)
await update_daily_timestamp(ctx.author, now)
await ctx.reply(
f"Your daily pocket money is {daily_reward:,}<:flooney:1194943899765051473>",
mention_author=False,
)
else:
next_claim = last_claim_dt + timedelta(days=1)
time_left = next_claim - now
hours, remainder = divmod(int(time_left.total_seconds()), 3600)
minutes, seconds = divmod(remainder, 60)
await ctx.reply(
f"Already claimed today. You need to wait **{hours}H {minutes}M {seconds}S**.",
mention_author=False,
)
except Exception as e:
logging.error(f"Error in daily claim: {e}")
await ctx.reply(
f"An error occurred while claiming your daily reward. {e}",
mention_author=False,
)
@commands.command(name="bank", brief="Check your bank balance", description="Check your bank balance.")
async def _bank(self, ctx: commands.Context, member: discord.Member | None = None):
"""Check your bank balance."""
target: discord.Member | discord.User = member or ctx.author
# Ensure the target is not a bot
if target.bot:
return
# Get the user's data
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Create an account if one does not exist
if bank_balance is None or wallet_balance is None:
await create_account(target)
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Reply with the user's bank balance
await ctx.reply(
f"{target.mention} has {bank_balance:,}<:flooney:1194943899765051473> in their bank."
)
@commands.command(name="wallet", brief="Check your wallet balance", description="Check your wallet balance.")
async def _wallet(self, ctx: commands.Context, member: discord.Member | None = None):
"""Check your wallet balance."""
target: discord.Member | discord.User = member or ctx.author
# Ensure the target is not a bot
if target.bot:
return
# Get the user's data
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Create an account if one does not exist
if bank_balance is None or wallet_balance is None:
await create_account(target)
user_data = await bank_data(target)
wallet_balance = user_data.get("WALLET", 0)
bank_balance = user_data.get("BANK", 0)
# Reply with the user's wallet balance
await ctx.reply(
f"{target.mention} has {wallet_balance:,}<:flooney:1194943899765051473> in their wallet."
)
@commands.command(name="transfer", brief="Transfer money from bank to another user", description="Transfer money from your bank to another user's bank.")
async def _transfer(self, ctx, target: discord.Member, amount: int):
"""
Transfer money from your bank account to another user's bank account.
"""
payer_bank = int((await bank_data(ctx.author)).get("BANK", 0))
receiver_bank = int((await bank_data(target)).get("BANK", 0))
valid, reason = validate_transfer(
payer_bank, ctx.author.id, target.id, amount
)
if not valid:
return await ctx.reply(reason, mention_author=False)
await update_money(target, bank=amount)
await update_money(ctx.author, bank=-amount)
await ctx.reply(
f"Transferred {amount:,} flooneys to {target}.", mention_author=False
)
r"""
.----------------. .----------------. .----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. |
| | _____ | || | ____ | || | ______ | || | _______ | |
| | |_ _| | || | .' `. | || | |_ _ \ | || | / ___ | | |
| | | | | || | / .--. \ | || | | |_) | | || | | (__ \_| | |
| | _ | | | || | | | | | | || | | __'. | || | '.___`-. | |
| | | |_' | | || | \ `--' / | || | _| |__) | | || | |`\____) | | |
| | `.___.' | || | `.____.' | || | |_______/ | || | |_______.' | |
| | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------'
"""
async def setup(client):
await client.add_cog(Economy(client))