diff --git a/bot.py b/bot.py index 31d49c0..d7ede62 100755 --- a/bot.py +++ b/bot.py @@ -42,14 +42,18 @@ class Client(commands.Bot): print("Loaded cogs") -def main(): - load_dotenv() - initialize_database() - client = Client() - token = os.getenv("DISCORD_TOKEN") or os.getenv("TOKEN") - if not token: - raise SystemExit("ERROR: Discord token not found. Set DISCORD_TOKEN or TOKEN in environment.") - client.run(token) +def main(): + load_dotenv() + initialize_database() + client = Client() + token = os.getenv("DISCORD_TOKEN") or os.getenv("TOKEN") + if not token: + raise SystemExit("ERROR: Discord token not found. Set DISCORD_TOKEN or TOKEN in environment.") + + # Print the token for debugging purposes (remove this in production) + print(f"DEBUG: Using token starting with {token[:5]}.") + + client.run(token) if __name__ == "__main__": diff --git a/cogs/economy.py b/cogs/economy.py index 1e7be87..0752315 100755 --- a/cogs/economy.py +++ b/cogs/economy.py @@ -15,25 +15,28 @@ from utils.bank_functions import ( from utils.sql_commands import DatabaseManager -def validate_transfer( - payer_balance: int, author_id: int, receiver_id: int, amount: int -) -> tuple[bool, str]: - 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, "" +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): @@ -400,6 +403,79 @@ class Economy(commands.Cog): 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""" .----------------. .----------------. .----------------. .----------------. | .--------------. || .--------------. || .--------------. || .--------------. | diff --git a/utils/bank_functions.py b/utils/bank_functions.py index 242e064..c3809a8 100755 --- a/utils/bank_functions.py +++ b/utils/bank_functions.py @@ -27,7 +27,11 @@ async def bank_data(user: discord.Member | discord.User) -> Dict[str, int]: if balance is None: await create_account(user) return await bank_data(user) - return balance + # Ensure we return a dictionary with WALLET and BANK keys + return { + "WALLET": balance.get("WALLET", 0), + "BANK": balance.get("BANK", 0) + } async def record_transaction( @@ -88,5 +92,4 @@ async def update_daily_timestamp(user: discord.User | discord.Member, timestamp: Updates the DAILY_TIMESTAMP field for the user in the economy table. Stores the timestamp as a float (UNIX time). """ - db.execute_query("UPDATE economy SET DAILY = %s WHERE ID = %s",(timestamp.timestamp(), user.id), - ) + db.execute_query("UPDATE economy SET DAILY = %s WHERE ID = %s",(timestamp.timestamp(), user.id)) diff --git a/utils/sql_commands.py b/utils/sql_commands.py index a7d0780..40da526 100755 --- a/utils/sql_commands.py +++ b/utils/sql_commands.py @@ -263,54 +263,55 @@ class DatabaseManager: ) return [col.strip() for col in match.group(1).split(",") if col.strip()] - def execute_query(self, query, params=None, retries=3, delay=1): - connection = None - cursor = None - for attempt in range(retries): - try: - connection = self.get_connection() - cursor = connection.cursor(dictionary=True, buffered=True) - cursor.execute(query, params or ()) - connection.commit() - logger.info(f"Executed query: {query} with params: {params}") - return cursor.rowcount - except mysql.connector.Error as err: - logger.warning(f"Attempt {attempt + 1} failed: {err}") - time.sleep(delay * (2**attempt)) - finally: - if cursor: - cursor.close() - if connection: - connection.close() + def execute_query(self, query, params=None, retries=3, delay=1): + connection = None + cursor = None + for attempt in range(retries): + try: + connection = self.get_connection() + cursor = connection.cursor(dictionary=True, buffered=True) + cursor.execute(query, params or ()) + connection.commit() + logger.info(f"Executed query: {query} with params: {params}") + # Return the actual cursor results instead of closing it + return cursor.fetchall() if cursor.with_rows else cursor.rowcount + except mysql.connector.Error as err: + logger.warning(f"Attempt {attempt + 1} failed: {err}") + time.sleep(delay * (2**attempt)) + finally: + if cursor: + cursor.close() + if connection: + connection.close() + + logger.error(f"All {retries} attempts failed for query: {query}") + return None - logger.error(f"All {retries} attempts failed for query: {query}") - return None - - def insert(self, query: str, params: tuple, overwrite: bool = True) -> None: - """ - Inserts data into the database using the given query and parameters. - - Args: - query (str): The SQL query to execute. - params (tuple): The parameters to pass into the query. - overwrite (bool, optional): Whether to perform an upsert operation. Defaults to True. - - Raises: - ValueError: If no parameters are provided. - """ - if not params: - raise ValueError("Params must be provided for the insert operation.") - - if overwrite: - columns = self._parse_insert_columns(query) - update_set = ", ".join(f"{col} = VALUES({col})" for col in columns) - query = f"{query} ON DUPLICATE KEY UPDATE {update_set}" - - rowcount = self.execute_query(query, params) - if rowcount is None: - logger.error(f"Insert failed with query: {query}.") - else: - logger.info(f"Insert completed with query: {query}.") + def insert(self, query: str, params: tuple, overwrite: bool = True) -> None: + """ + Inserts data into the database using the given query and parameters. + + Args: + query (str): The SQL query to execute. + params (tuple): The parameters to pass into the query. + overwrite (bool, optional): Whether to perform an upsert operation. Defaults to True. + + Raises: + ValueError: If no parameters are provided. + """ + if not params: + raise ValueError("Params must be provided for the insert operation.") + + if overwrite: + columns = self._parse_insert_columns(query) + update_set = ", ".join(f"{col} = VALUES({col})" for col in columns) + query = f"{query} ON DUPLICATE KEY UPDATE {update_set}" + + rowcount = self.execute_query(query, params) + if rowcount is None: + logger.error(f"Insert failed with query: {query}.") + else: + logger.info(f"Insert completed with query: {query}.") def bulk_insert(self, query, params=None): if not params: @@ -345,13 +346,13 @@ class DatabaseManager: if connection: connection.close() - def delete(self, table_name: str, condition: dict) -> None: - """Deletes a record from the specified table based on the condition provided.""" - table_name = self._sanitize_identifier(table_name) - condition_column, condition_value = next(iter(condition.items())) - condition_column = self._sanitize_identifier(condition_column) - query = f"DELETE FROM {table_name} WHERE {condition_column} = %s" - self.execute_query(query, (condition_value,)) + def delete(self, table_name: str, condition: dict) -> None: + """Deletes a record from the specified table based on the condition provided.""" + table_name = self._sanitize_identifier(table_name) + condition_column, condition_value = next(iter(condition.items())) + condition_column = self._sanitize_identifier(condition_column) + query = f"DELETE FROM {table_name} WHERE {condition_column} = %s" + self.execute_query(query, (condition_value,)) def fetch_one(self, query, params=None): connection = None