First Commit

This commit is contained in:
2025-09-16 15:00:16 +02:00
commit c8980f785f
188 changed files with 43407 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+224
View File
@@ -0,0 +1,224 @@
import datetime
import time
import os
import discord
from discord.ext import commands
async def reload_all(client, ctx):
"""
Reloads all cogs (modules) in the 'cogs' folder and syncs the command tree.
Args:
client (discord.Client): The bot's main client object.
ctx (commands.Context): The context in which the command was invoked.
"""
cogs_folder = os.path.abspath(os.path.dirname(__file__)) # Get the cogs folder path
for filename in os.listdir(cogs_folder):
if filename.endswith(".py"):
try:
await client.reload_extension(f"cogs.{filename[:-3]}")
except Exception as e:
print(f"Error reloading {filename}: {e}")
await client.load_extension(f"cogs.{filename[:-3]}")
await client.tree.sync() # Synchronize command tree
await ctx.reply("Reloaded all cogs.")
class Admin(commands.Cog):
"""Cog that provides administrative commands for bot control and information."""
def __init__(self, client):
self.client = client
@commands.command(
name="ping",
brief="Bot Latency",
description="Displays the bot's latency in milliseconds.",
)
async def _ping(self, ctx):
"""
Responds with the bot's latency (ping time).
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
latency = round(self.client.latency * 1000, 2) # Convert to ms
await ctx.reply(f"Pong! 🏓\nLatency: {latency}ms")
@commands.command(
name="info",
brief="Bot Info",
description="Provides information about the bot's owner and team (if applicable).",
)
async def _info(self, ctx):
"""
Displays information about the bot's owner or team members.
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
app_info = await self.client.application_info()
if app_info.team:
team_members = "\n".join(
f"{member.name}#{member.discriminator} ({'Owner' if member.id == app_info.team.owner_id else 'Member'})"
for member in app_info.team.members
)
await ctx.reply(
f"Bot is part of the team: {app_info.team.name}\nMembers:\n{team_members}"
)
else:
await ctx.reply(
f"Bot is owned by {app_info.owner} and is not part of a team."
)
@commands.command(
name="uptime",
brief="Bot Runtime",
description="Displays how long the bot has been running since its last restart.",
)
async def _uptime(self, ctx):
"""
Calculates and sends the bot's uptime based on a start timestamp.
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
with open("time.txt", "r") as file:
start_time = float(file.read())
uptime = str(
datetime.timedelta(seconds=int(time.time() - start_time))
) # Calculate uptime
await ctx.send(f"Uptime: {uptime}")
@commands.hybrid_command(
name="reload",
brief="Reload Cog",
description="Reloads a specific cog or all cogs.",
aliases=["rl"],
)
@commands.is_owner()
async def _reload(self, ctx, extension: str):
"""
Reloads a specified cog/module or all modules if 'all' is passed.
Args:
ctx (commands.Context): The context in which the command was invoked.
extension (str): Name of the cog to reload, or 'all' to reload all cogs.
"""
if extension.lower() == "all":
await reload_all(self.client, ctx)
return
try:
await self.client.unload_extension(f"cogs.{extension}")
await self.client.load_extension(f"cogs.{extension}")
await ctx.send(f"Successfully reloaded {extension}.")
except commands.ExtensionNotLoaded:
await ctx.send(f"Extension {extension} was not loaded, loading it now.")
try:
await self.client.load_extension(f"cogs.{extension}")
await ctx.send(f"Successfully loaded {extension}.")
except Exception as e:
await ctx.send(f"Failed to load {extension}: {e}")
except commands.ExtensionNotFound:
await ctx.send(f"Extension {extension} was not found.")
except Exception as e:
await ctx.send(f"Failed to reload {extension}: {e}")
@commands.hybrid_command(
name="purge",
brief="Delete messages",
description="Deletes a specified number of messages from the current channel (default: 100).",
)
@commands.has_permissions(manage_channels=True)
@commands.cooldown(1, 10, commands.BucketType.user)
async def _purge(self, ctx, n: int = 100):
"""
Deletes a specified number of messages from the current channel.
Args:
ctx (commands.Context): The context in which the command was invoked.
n (int, optional): The number of messages to delete. Defaults to 100.
"""
n += 1
if n <= 0:
return
if n < 100:
await ctx.channel.purge(limit=n)
await ctx.channel.send(
f"Successfully purged last {n-1} messages", delete_after=2
)
elif n == 100:
await ctx.channel.purge()
await ctx.channel.send(
"Successfully purged last 100 messages", delete_after=2
)
else: # n > 100
for i in range(n // 100):
await ctx.channel.purge()
await ctx.channel.purge(limit=n % 100)
await ctx.channel.send(
f"Successfully purged last {n-1} messages", delete_after=2
)
@commands.hybrid_command(
name="nuke",
brief="Clear channel",
description="Deletes all messages by duplicating and deleting the channel.",
)
@commands.has_permissions(manage_channels=True)
@commands.cooldown(1, 30, commands.BucketType.user)
async def _nuke(self, ctx, channel_name: str = "Current"):
"""
Duplicates and deletes a specified channel (or the current channel) to clear messages.
Args:
ctx (commands.Context): The context in which the command was invoked.
channel_name (str, optional): Name of the channel to nuke. Defaults to 'Current' (the invoking channel).
"""
# Determine the channel ID based on the channel_name parameter
if channel_name.lower() == "current":
channel_id = ctx.channel.id
else:
channel_id = discord.utils.get(ctx.guild.channels, name=channel_name)
if channel_id is None:
return await ctx.send(f"Channel **{channel_name}** was not found.")
existing_channel = self.client.get_channel(channel_id)
if existing_channel:
await existing_channel.clone(reason="Channel has been nuked")
await existing_channel.delete()
await self._send_embed(
ctx,
f"Channel **{existing_channel.name}** has been nuked.",
discord.Color.red(),
)
else:
await ctx.send(f"No channel with ID {channel_id} was found.")
@commands.command(
name="guildids",
brief="List all guild IDs",
description="Lists all guild IDs the bot is currently in. Owner only.",
)
@commands.is_owner()
async def _guildids(self, ctx):
"""
Sends a list of all guild IDs the bot is in to the owner.
Args:
ctx (commands.Context): The context in which the command was invoked.
"""
guild_ids = [str(guild.id) for guild in self.client.guilds]
guilds_str = "\n".join(guild_ids)
await ctx.author.send(f"Guild IDs:\n{guilds_str}")
await ctx.reply("Sent you a DM with all guild IDs.")
async def _send_embed(self, ctx, description, color=discord.Color.blurple()):
embed = discord.Embed(description=description, color=color)
await ctx.send(embed=embed)
async def setup(client):
"""Sets up the Admin cog for the bot."""
await client.add_cog(Admin(client))
+224
View File
@@ -0,0 +1,224 @@
import discord, re
from discord.ext import commands
from utils.sql_commands import DatabaseManager
class CustomCommandsCog(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager("")
self.command_cache = {} # {guild_id: [commands_dicts]}
def get_guild_commands(self, guild_id):
if guild_id not in self.command_cache:
self.command_cache[guild_id] = self.db.fetch_all(
"SELECT * FROM custom_commands WHERE GUILDID = %s", (guild_id,)
)
return self.command_cache[guild_id]
def invalidate_cache(self, guild_id):
if guild_id in self.command_cache:
del self.command_cache[guild_id]
@commands.hybrid_command(name="addcommand")
@commands.has_permissions(manage_guild=True)
@commands.cooldown(1, 10, commands.BucketType.user)
async def add_command(self, ctx, command_name: str, *, response: str):
"""Add a new custom command"""
guild_id = str(ctx.guild.id)
command_name = command_name.lower()
existing_command = self.db.fetch_one(
"SELECT 1 FROM custom_commands WHERE GUILDID = %s AND COMMANDNAME = %s",
(guild_id, command_name),
)
if existing_command:
await ctx.send(f"A command with the name `{command_name}` already exists.")
return
try:
# Insert the new command into the database
self.db.execute_query(
"INSERT INTO custom_commands (GUILDID, COMMANDNAME, RESPONSE) VALUES (%s, %s, %s)",
(guild_id, command_name, response),
)
self.invalidate_cache(guild_id) # Invalidate cache after change
await ctx.send(f"Custom command `{command_name}` has been added!")
except Exception as e:
await ctx.send(f"Failed to add command `{command_name}`. Error: {e}")
@commands.hybrid_command(name="delcommand")
@commands.has_permissions(manage_guild=True)
@commands.cooldown(1, 10, commands.BucketType.user)
async def delete_command(self, ctx, command_name: str):
"""Delete a custom command"""
guild_id = str(ctx.guild.id)
command_name = command_name.lower()
try:
# Delete the command from the database
deleted_rows = self.db.execute_query(
"DELETE FROM custom_commands WHERE GUILDID = %s AND COMMANDNAME = %s",
(guild_id, command_name),
)
self.invalidate_cache(guild_id) # Invalidate cache after change
if deleted_rows is not None and len(deleted_rows) > 0:
await ctx.send(f"Custom command `{command_name}` has been deleted!")
else:
await ctx.send(f"Custom command `{command_name}` not found.")
except Exception as e:
await ctx.send(f"Failed to delete command `{command_name}`. Error: {e}")
@commands.hybrid_command(name="listcommands")
async def list_commands(self, ctx):
"""List all custom commands for this guild"""
guild_id = str(ctx.guild.id)
try:
# Retrieve all commands for the guild
commands = self.db.fetch_all(
"SELECT COMMANDNAME, RESPONSE FROM custom_commands WHERE GUILDID = %s",
(guild_id,),
)
if commands:
commands_list = "\n".join(
[f"`{cmd['COMMANDNAME']}`: {cmd['RESPONSE']}" for cmd in commands]
)
await ctx.send(f"**Custom Commands:**\n{commands_list}")
else:
await ctx.send("No custom commands found for this server.")
except Exception as e:
await ctx.send(f"Failed to retrieve commands. Error: {e}")
@commands.Cog.listener()
async def on_message(self, message):
"""Listen for custom command invocations"""
if message.author.bot or not message.guild:
return
guild_id = str(message.guild.id)
command_name = message.content.strip().lower()
# Use cache instead of DB call
data: list = self.get_guild_commands(guild_id)
if not data:
return
result = None
for cmd in data:
if cmd["MATCHTYPE"] == "exact" and command_name == cmd["COMMANDNAME"]:
result = cmd
break
elif cmd["MATCHTYPE"] == "contains" and cmd["COMMANDNAME"] in command_name:
result = cmd
break
elif cmd["MATCHTYPE"] == "startswith" and command_name.startswith(
cmd["COMMANDNAME"]
):
result = cmd
break
elif cmd["MATCHTYPE"] == "endswith" and command_name.endswith(
cmd["COMMANDNAME"]
):
result = cmd
break
elif cmd["MATCHTYPE"] == "regex" and re.match(
re.compile(cmd["REGEX"]), command_name
):
result = cmd
break
else:
return
if result:
response = result["RESPONSE"]
# Build a dictionary of variables to replace
variables = {
"{USER}": message.author.name,
"{USER_MENTION}": message.author.mention,
"{USER_ID}": str(message.author.id),
"{USER_TAG}": str(message.author),
"{USER_AVATAR}": str(message.author.display_avatar.url),
"{USER_TOP_ROLE}": getattr(message.author.top_role, "name", ""),
"{USER_CREATED}": message.author.created_at.strftime(
"%Y-%m-%d %H:%M:%S"
),
"{USER_JOINED}": (
message.author.joined_at.strftime("%Y-%m-%d %H:%M:%S")
if message.author.joined_at
else ""
),
"{USER_NICK}": message.author.nick or message.author.name,
"{USER_COLOR}": str(
getattr(message.author.color, "to_rgb", lambda: "")()
),
"{USER_STATUS}": str(message.author.status),
"{USER_IS_BOT}": str(message.author.bot),
"{USER_DISCRIMINATOR}": message.author.discriminator,
"{CHANNEL}": message.channel.name,
"{CHANNEL_MENTION}": message.channel.mention,
"{CHANNEL_ID}": str(message.channel.id),
"{CHANNEL_TOPIC}": getattr(message.channel, "topic", ""),
"{CHANNEL_TYPE}": str(message.channel.type),
"{CHANNEL_CREATED}": message.channel.created_at.strftime(
"%Y-%m-%d %H:%M:%S"
),
"{GUILD}": message.guild.name,
"{GUILD_ID}": str(message.guild.id),
"{GUILD_OWNER}": str(message.guild.owner),
"{GUILD_OWNER_MENTION}": (
message.guild.owner.mention if message.guild.owner else ""
),
"{GUILD_MEMBERCOUNT}": str(message.guild.member_count),
"{GUILD_ICON}": (
str(message.guild.icon.url) if message.guild.icon else ""
),
"{GUILD_CREATED}": message.guild.created_at.strftime(
"%Y-%m-%d %H:%M:%S"
),
"{GUILD_BOOSTS}": str(
getattr(message.guild, "premium_subscription_count", "")
),
"{GUILD_BOOST_LEVEL}": str(getattr(message.guild, "premium_tier", "")),
"{MESSAGE}": message.content,
"{MESSAGE_ID}": str(message.id),
"{MESSAGE_LINK}": message.jump_url,
"{MESSAGE_TIMESTAMP}": message.created_at.strftime("%Y-%m-%d %H:%M:%S"),
"{TIME}": discord.utils.utcnow().strftime("%H:%M:%S UTC"),
"{DATE}": discord.utils.utcnow().strftime("%Y-%m-%d"),
"{DAY}": discord.utils.utcnow().strftime("%A"),
"{MONTH}": discord.utils.utcnow().strftime("%B"),
"{YEAR}": discord.utils.utcnow().strftime("%Y"),
"{BOT}": (
message.guild.me.name if message.guild else self.client.user.name
),
"{BOT_MENTION}": (
message.guild.me.mention
if message.guild
else self.client.user.mention
),
"{BOT_ID}": str(
message.guild.me.id if message.guild else self.client.user.id
),
"{BOT_AVATAR}": str(
message.guild.me.display_avatar.url
if message.guild
else self.client.user.display_avatar.url
),
}
# Replace all variables in the response
for var, value in variables.items():
if value is None:
continue
response = response.replace(var, value)
await message.channel.send(response)
async def setup(client):
await client.add_cog(CustomCommandsCog(client))
+423
View File
@@ -0,0 +1,423 @@
import logging
import discord
from discord.ext import commands
from utils.bank_functions import *
from discord.ext import commands
from datetime import datetime, timedelta
from random import randint
from typing import Literal
import datetime
async def check_transfer(
ctx: commands.Context,
payer_balance: int,
receiver_balance: int,
member: discord.Member,
amount: int,
) -> bool:
"""Check if a transfer is valid.
Args:
- ctx (commands.Context): The context of the invoked command.
- payer_balance (int): The balance of the payer.
- receiver_balance (int): The balance of the receiver.
- member (discord.Member): The member to check against.
- amount (int): The amount to transfer.
Returns:
- bool: Whether the transfer is valid.
"""
if payer_balance is None or receiver_balance is None:
await ctx.reply("Bank account doesn't exist.")
return False
if payer_balance >= amount > 0:
payer_balance = payer_balance - amount
receiver_balance = receiver_balance + amount
if ctx.author.id != member.id:
return True
else:
await ctx.reply("You cannot give yourself money.")
return False
else:
await ctx.reply(
f"You do not have {int(amount):,}<:flooney:1194943899765051473>.\nYou have {int(payer_balance):,}<:flooney:1194943899765051473>."
)
return False
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))
if await check_transfer(ctx, payer_wallet, receiver_wallet, target, amount):
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))
if await check_transfer(ctx, payer_bank, receiver_bank, target, amount):
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)
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)
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.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() # Modern, timezone-aware
# Convert last_claim to datetime if it's a timestamp (int or float)
if last_claim:
if isinstance(last_claim, (int, float)):
last_claim_dt = datetime.datetime.fromtimestamp(
last_claim, tz=datetime.timezone.utc
)
elif isinstance(last_claim, str):
try:
last_claim_dt = datetime.datetime.fromtimestamp(
float(last_claim), tz=datetime.timezone.utc
)
except Exception:
last_claim_dt = None
elif isinstance(last_claim, datetime.datetime):
# If it's naive, make it aware
if last_claim.tzinfo is None:
last_claim_dt = last_claim.replace(tzinfo=datetime.timezone.utc)
else:
last_claim_dt = last_claim
else:
last_claim_dt = None
else:
last_claim_dt = None
if not last_claim_dt or (now - last_claim_dt).total_seconds() >= 86400:
daily_reward = randint(200, 1000)
await update_money(ctx.author, daily_reward)
# Save the new timestamp as a float (UNIX time)
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,
)
r"""
.----------------. .----------------. .----------------. .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. |
| | _____ | || | ____ | || | ______ | || | _______ | |
| | |_ _| | || | .' `. | || | |_ _ \ | || | / ___ | | |
| | | | | || | / .--. \ | || | | |_) | | || | | (__ \_| | |
| | _ | | | || | | | | | | || | | __'. | || | '.___`-. | |
| | | |_' | | || | \ `--' / | || | _| |__) | | || | |`\____) | | |
| | `.___.' | || | `.____.' | || | |_______/ | || | |_______.' | |
| | | || | | || | | || | | |
| '--------------' || '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------' '----------------'
"""
async def setup(client):
await client.add_cog(Economy(client))
Executable
+979
View File
@@ -0,0 +1,979 @@
import discord
from random import shuffle, choices, choice
from discord.ext import commands
from utils.sql_commands import DatabaseManager
from utils.bank_functions import bank_data, update_money
from datetime import datetime, timedelta
import asyncio
import random
# --- Constants and Configs ---
lottery_list = ["🎁", "🎮", "🎷", "🔫", "📸", "🎃", "🏅"]
weights = [0.15, 0.1, 0.02, 0.2, 0.1, 0.28, 0.2]
lottery_win = {
"🎁": 2,
"🎁🎁": 5,
"🎁🎁🎁": 50,
"🔫🔫🔫": 3,
"📸📸📸": 5,
"🎃🎃🎃": 7,
"🏅🏅🏅": 10,
"🎮🎮🎮": 25,
"🎷🎷🎷": 1000,
}
bj_values = {
"2♦️": 2,
"2♥️": 2,
"2♣️": 2,
"2♠️": 2,
"3♦️": 3,
"3♥️": 3,
"3♣️": 3,
"3♠️": 3,
"4♦️": 4,
"4♥️": 4,
"4♣️": 4,
"4♠️": 4,
"5♦️": 5,
"5♥️": 5,
"5♣️": 5,
"5♠️": 5,
"6♦️": 6,
"6♥️": 6,
"6♣️": 6,
"6♠️": 6,
"7♦️": 7,
"7♥️": 7,
"7♣️": 7,
"7♠️": 7,
"8♦️": 8,
"8♥️": 8,
"8♣️": 8,
"8♠️": 8,
"9♦️": 9,
"9♥️": 9,
"9♣️": 9,
"9♠️": 9,
"10♦️": 10,
"10♥️": 10,
"10♣️": 10,
"10♠️": 10,
"J♦️": 10,
"J♥️": 10,
"J♣️": 10,
"J♠️": 10,
"Q♦️": 10,
"Q♥️": 10,
"Q♣️": 10,
"Q♠️": 10,
"K♦️": 10,
"K♥️": 10,
"K♣️": 10,
"K♠️": 10,
"A♦️": 11,
"A♥️": 11,
"A♣️": 11,
"A♠️": 11,
}
ROOMS_FILE = "active_rooms.json"
# --- Blackjack Classes and Helpers ---
class Deck:
def __init__(self):
self.deck = list(bj_values.keys())
shuffle(self.deck)
async def deal(self):
return self.deck.pop()
class Hand:
def __init__(self, name, bet):
self.cards = []
self.value = 0
self.aces = 0
self.bust = False
self.name = name
self.bet = bet
async def add_card(self, card):
self.cards.append(card)
self.aces = ("".join(self.cards)).count("A")
value = 0
for card in self.cards:
value += bj_values[card]
self.value = value
while self.value > 21 and self.aces:
self.value -= 10
self.aces -= 1
async def hit(deck, hand):
await hand.add_card(await deck.deal())
async def payout(ctx, play, amount, multiplier, db):
player_balance = int(
db.fetch_one("SELECT WALLET FROM economy WHERE ID = %s", (ctx.author.id,)).get(
"WALLET", 0
)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + amount * (multiplier - 1), ctx.author.id),
)
await ctx.reply(
f"{play}\nBet: {amount:,}\nYou won {amount * multiplier:,} <:flooney:1194943899765051473>"
)
async def check_winner(msg: discord.Interaction, player_hand, dealer_hand, db):
p_value, d_value = player_hand.value, dealer_hand.value
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=player_hand.name,
value=f"{', '.join(player_hand.cards)}: {player_hand.value}",
inline=False,
)
embed.add_field(
name=dealer_hand.name,
value=f"{', '.join(dealer_hand.cards)}: {dealer_hand.value}",
inline=False,
)
# Check for Blackjack
if p_value == 21 and len(player_hand.cards) == 2:
if d_value == 21 and len(dealer_hand.cards) == 2:
embed.add_field(
name="Winner:", value="Draw (Both got Blackjack!)", inline=False
)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + player_hand.bet, player_hand.name.id),
)
else:
embed.add_field(
name="Winner:", value=f"{player_hand.name} (Blackjack!)", inline=False
)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + int(player_hand.bet * 3), player_hand.name.id),
)
elif d_value == 21 and len(dealer_hand.cards) == 2:
embed.add_field(
name="Winner:", value=f"{dealer_hand.name} (Blackjack!)", inline=False
)
else:
if (p_value > d_value and not player_hand.bust) or dealer_hand.bust:
embed.add_field(name="Winner:", value=f"{player_hand.name}", inline=False)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + player_hand.bet * 2, player_hand.name.id),
)
elif (p_value < d_value and not dealer_hand.bust) or player_hand.bust:
embed.add_field(name="Winner:", value=f"{dealer_hand.name}", inline=False)
else:
embed.add_field(name="Winner:", value="Draw", inline=False)
player_balance = int(
db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (player_hand.name.id,)
).get("WALLET", 0)
)
db.execute_query(
"UPDATE economy SET WALLET = %s WHERE ID = %s",
(player_balance + player_hand.bet, player_hand.name.id),
)
await msg.edit_original_response(embed=embed)
async def dealer(msg: discord.Interaction, player_hand, dealer_hand, cards, db):
while dealer_hand.value < 17 and not player_hand.bust and not dealer_hand.bust:
await hit(cards, dealer_hand)
if dealer_hand.value > 21:
dealer_hand.bust = True
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=player_hand.name,
value=f"{', '.join(player_hand.cards)}: {player_hand.value}",
inline=False,
)
embed.add_field(
name=dealer_hand.name,
value=f"{', '.join(dealer_hand.cards)}: {dealer_hand.value}",
inline=False,
)
await msg.edit_original_response(embed=embed)
await check_winner(msg, player_hand, dealer_hand, db)
class MyView(discord.ui.View):
def __init__(self, player_hand, dealer_hand, cards, db):
super().__init__()
self.player_hand = player_hand
self.dealer_hand = dealer_hand
self.cards = cards
self.db = db
@discord.ui.button(label="Hit", style=discord.ButtonStyle.primary, emoji="")
async def hit(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id == self.player_hand.name.id:
await hit(self.cards, self.player_hand)
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{self.player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=self.player_hand.name,
value=f"{', '.join(self.player_hand.cards)}: {self.player_hand.value}",
inline=False,
)
embed.add_field(
name=self.dealer_hand.name,
value=f"{(self.dealer_hand.cards)[0]}",
inline=False,
)
msg = interaction
if self.player_hand.value < 21:
await msg.response.edit_message(embed=embed)
else:
await msg.response.edit_message(embed=embed, view=None)
if self.player_hand.value > 21:
self.player_hand.bust = True
await dealer(
msg, self.player_hand, self.dealer_hand, self.cards, self.db
)
@discord.ui.button(label="Stand", style=discord.ButtonStyle.secondary, emoji="🛑")
async def stand(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user.id == self.player_hand.name.id:
embed = discord.Embed(description="Blackjack")
embed.add_field(
name="Bet: ",
value=f"{self.player_hand.bet:,}<:flooney:1194943899765051473>",
inline=True,
)
embed.add_field(
name=self.player_hand.name,
value=f"{', '.join(self.player_hand.cards)}: {self.player_hand.value}",
inline=False,
)
embed.add_field(
name=self.dealer_hand.name,
value=f"{(self.dealer_hand.cards)[0]}",
inline=False,
)
msg = interaction
await msg.response.edit_message(embed=embed, view=None)
await dealer(msg, self.player_hand, self.dealer_hand, self.cards, self.db)
# --- Main Gamble Cog ---
class Gamble(commands.Cog):
"""Cog for all gambling-related commands."""
def __init__(self, client):
self.client = client
self.db = DatabaseManager()
self.active_rooms = {}
self.room_timers = {}
def save_rooms(self):
"""Save all active rooms to the database."""
try:
for channel_id, room in self.active_rooms.items():
invited_str = ",".join(str(uid) for uid in room["invited"])
self.db.execute_query(
"REPLACE INTO gamble_rooms (channel_id, host_id, invited, inactivity) VALUES (%s, %s, %s, %s)",
(channel_id, room["host"], invited_str, room["inactivity"]),
)
except Exception as e:
print(f"[DB ERROR] Failed to save rooms: {e}")
def load_rooms(self):
"""Load all rooms from the database."""
try:
self.active_rooms = {}
rows = self.db.fetch_all("SELECT * FROM gamble_rooms")
for row in rows:
invited = set(int(uid) for uid in row["invited"].split(",") if uid)
self.active_rooms[int(row["channel_id"])] = {
"host": int(row["host_id"]),
"invited": invited,
"inactivity": int(row["inactivity"]),
}
except Exception as e:
print(f"[DB ERROR] Failed to load rooms: {e}")
def delete_room(self, channel_id):
"""Delete a room from the database and memory."""
try:
self.db.execute_query(
"DELETE FROM gamble_rooms WHERE channel_id = %s", (channel_id,)
)
except Exception as e:
print(f"[DB ERROR] Failed to delete room: {e}")
self.active_rooms.pop(channel_id, None)
self.save_rooms()
async def cog_load(self):
"""Load rooms on cog load."""
self.load_rooms()
async def cog_unload(self):
"""Save rooms on cog unload."""
self.save_rooms()
async def _check_limit_and_exclusion(self, ctx, amount: int) -> bool:
"""Returns True if user is allowed to gamble, else sends a message and returns False."""
user = ctx.author
if amount <= 0:
await ctx.reply("⚠️ Bet amount must be positive.", mention_author=False)
return False
try:
limit_row = self.db.fetch_one(
"SELECT * FROM gamble_limits WHERE USERID = %s", (user.id,)
)
except Exception as e:
await ctx.reply(
"⚠️ Database error. Please try again later.", mention_author=False
)
print(f"[DB ERROR] Limit check: {e}")
return False
if limit_row:
if (
limit_row.get("EXCLUDED_UNTIL")
and datetime.utcnow() < limit_row["EXCLUDED_UNTIL"]
):
await ctx.reply(
"🚫 You are currently self-excluded from gambling.",
mention_author=False,
)
return False
if limit_row.get("DAILY_LIMIT") and amount > limit_row["DAILY_LIMIT"]:
await ctx.reply(
f"🚫 Your personal bet limit is {limit_row['DAILY_LIMIT']:,}.",
mention_author=False,
)
return False
return True
async def _send_embed(
self, ctx, description: str, color: discord.Color = discord.Color.blurple()
):
"""Helper to send an embed message."""
embed = discord.Embed(description=description, color=color)
await ctx.reply(embed=embed, mention_author=False)
# --- Coinflip ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="coinflip",
aliases=["cf", "coin_flip"],
usage="<bet_on: heads(H) or tails(T)> <amount: integer>",
description="Flip a coin and bet on heads or tails.",
brief="Bet on heads or tails.",
)
@commands.guild_only()
async def coin_flip(self, ctx, bet_on: str, amount: int):
if not await self._check_limit_and_exclusion(ctx, amount):
return
user = ctx.author
bet_on = "heads" if "h" in bet_on.lower() else "tails"
reward = round(amount / 2)
users = await bank_data(user)
if int(users["WALLET"]) < amount:
return await self._send_embed(
ctx, "❌ You don't have enough money.", discord.Color.red()
)
coin = ["heads", "tails"]
result = choice(coin)
if result != bet_on:
await update_money(user, wallet=-abs(amount))
return await self._send_embed(
ctx,
f"🪙 Got **{result}**, you lost **{amount:,}**.",
discord.Color.red(),
)
await update_money(user, wallet=+abs(amount))
return await self._send_embed(
ctx,
f"🪙 Got **{result}**, you won **{amount + reward:,}**!",
discord.Color.green(),
)
# --- Dice ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="dice",
usage="<amount: integer> <bet_on: integer (1-6)>",
description="Bet on a dice roll (choose a number 1-6).",
brief="Bet on a dice roll.",
)
async def dice(self, ctx, amount: int, bet_on: int = 6):
if not await self._check_limit_and_exclusion(ctx, amount):
return
user = ctx.author
rdice = [1, 2, 3, 4, 5, 6]
if bet_on not in rdice:
return await self._send_embed(
ctx, "🎲 Enter a number of dice (1 - 6).", discord.Color.orange()
)
users = await bank_data(user)
if int(users["WALLET"]) < amount:
return await self._send_embed(
ctx, "❌ You don't have enough money.", discord.Color.red()
)
rand_num = choice(rdice)
if rand_num != bet_on:
await update_money(user, wallet=-abs(amount))
return await self._send_embed(
ctx,
f"🎲 Got **{rand_num}**, you lost **{amount:,}**.",
discord.Color.red(),
)
reward = round(amount / 2)
await update_money(user, wallet=+abs(reward))
await self._send_embed(
ctx,
f"🎲 Got **{rand_num}**, you won **{amount + reward:,}**!",
discord.Color.green(),
)
# --- Slots ---
@commands.cooldown(1, 1, commands.BucketType.user)
@commands.command(
name="slots",
usage="<bet: integer or 'all'>",
description="Spin the slot machine for a chance to win big!",
brief="Spin the slot machine.",
)
async def slots(self, ctx, bet):
user = ctx.author
player_balance = int(
self.db.fetch_one(
"SELECT WALLET FROM economy WHERE ID = %s", (user.id,)
).get("WALLET", 0)
)
bet = int(bet.replace("all", str(player_balance)))
if not await self._check_limit_and_exclusion(ctx, bet):
return
if player_balance < bet:
await self._send_embed(
ctx, "❌ You have insufficient funds.", discord.Color.red()
)
return
# Animation: edit the embed to show spinning, then result
spin_emoji = "<a:slots:1381060458999713843>"
embed = discord.Embed(
description=f"{spin_emoji} {spin_emoji} {spin_emoji}\nSpinning...",
color=discord.Color.blurple(),
)
msg = await ctx.send(embed=embed)
await asyncio.sleep(1.2)
play = "".join(choices(lottery_list, weights=weights, k=3))
result_embed = discord.Embed(
description=f"{play[0]} {play[1]} {play[2]}",
color=(
discord.Color.green()
if play in lottery_win or play.count("🎁") > 0
else discord.Color.red()
),
)
# Ensure description is always a string
if result_embed.description is None:
result_embed.description = ""
if play in lottery_win:
mult = lottery_win[play]
result_embed.description = (result_embed.description or "") + (
f"\nYou won **{bet * mult:,}** <:flooney:1194943899765051473>"
)
await update_money(user, wallet=bet * (mult - 1))
elif play.count("🎁") == 2:
mult = lottery_win["🎁🎁"]
result_embed.description = (result_embed.description or "") + (
f"\nYou won **{bet * mult:,}** <:flooney:1194943899765051473>"
)
await update_money(user, wallet=bet * (mult - 1))
elif play.count("🎁") == 1:
mult = lottery_win["🎁"]
result_embed.description = (result_embed.description or "") + (
f"\nYou won **{bet * mult:,}** <:flooney:1194943899765051473>"
)
await update_money(user, wallet=bet * (mult - 1))
else:
await update_money(user, wallet=-bet)
result_embed.description = (result_embed.description or "") + (
f"\nYou lost **{int(bet):,}** <:flooney:1194943899765051473>"
)
await msg.edit(embed=result_embed)
# --- Roulette ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="roulette",
usage="<bet_type> <amount: integer>",
description=(
"Bet on roulette! Options: "
"`red`, `black`, `green`, `even`, `odd`, "
"`1st12`, `2nd12`, `3rd12` (dozens), "
"`col1`, `col2`, `col3` (columns), or a number (0-36)."
),
brief="Bet on roulette.",
)
async def roulette(self, ctx, bet_type: str, amount: int):
if not await self._check_limit_and_exclusion(ctx, amount):
return
user = ctx.author
colors = {
"red": [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36],
"black": [
2,
4,
6,
8,
10,
11,
13,
15,
17,
20,
22,
24,
26,
28,
29,
31,
33,
35,
],
"green": [0],
}
bet_type = bet_type.lower()
valid_bets = [
"red",
"black",
"green",
"even",
"odd",
"1st12",
"2nd12",
"3rd12",
"col1",
"col2",
"col3",
] + [str(i) for i in range(37)]
if bet_type not in valid_bets:
return await self._send_embed(
ctx,
"Bet on 'red', 'black', 'green', 'even', 'odd', '1st12', '2nd12', '3rd12', 'col1', 'col2', 'col3', or a number (0-36).",
discord.Color.orange(),
)
result = choice(range(37))
color = (
"green" if result == 0 else "red" if result in colors["red"] else "black"
)
payout = 0
# Number bet
if bet_type.isdigit() and int(bet_type) == result:
payout = amount * 35
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Color bet
elif bet_type == color:
payout = amount * (14 if color == "green" else 2)
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Even/Odd
elif bet_type == "even" and result != 0 and result % 2 == 0:
payout = amount * 2
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "odd" and result % 2 == 1:
payout = amount * 2
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Dozens
elif bet_type == "1st12" and 1 <= result <= 12:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "2nd12" and 13 <= result <= 24:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "3rd12" and 25 <= result <= 36:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
# Columns
elif bet_type == "col1" and result in [i for i in range(1, 37, 3)]:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "col2" and result in [i for i in range(2, 37, 3)]:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
elif bet_type == "col3" and result in [i for i in range(3, 37, 3)]:
payout = amount * 3
msg = f"🎲 The ball landed on {result} ({color}). You won {payout:,}!"
else:
payout = -amount
msg = f"🎲 The ball landed on {result} ({color}). You lost {amount:,}."
await update_money(user, wallet=payout)
await self._send_embed(
ctx, msg, discord.Color.green() if payout > 0 else discord.Color.red()
)
# --- Poker ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.is_owner()
@commands.command(
name="poker",
usage="<bet: integer>",
description="Play a simple 5-card draw poker game against the bot.",
brief="Play poker against the bot.",
)
async def poker(self, ctx, bet: int):
if not await self._check_limit_and_exclusion(ctx, bet):
return
user = ctx.author
user_data = await bank_data(user)
if int(user_data["WALLET"]) < bet:
await self._send_embed(
ctx, "❌ You don't have enough money.", discord.Color.red()
)
return
# Poker deck and hands
suits = ["♠️", "♥️", "♦️", "♣️"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
deck = [f"{r}{s}" for r in ranks for s in suits]
random.shuffle(deck)
player_hand = [deck.pop() for _ in range(5)]
bot_hand = [deck.pop() for _ in range(5)]
# Simple hand value: count pairs, triples, etc. (not full poker logic)
def hand_value(hand):
values = [card[:-2] if card[:-2] != "10" else "10" for card in hand]
counts = {v: values.count(v) for v in set(values)}
if 4 in counts.values():
return 7 # Four of a kind
if sorted(counts.values()) == [2, 3]:
return 6 # Full house
if 3 in counts.values():
return 3 # Three of a kind
if list(counts.values()).count(2) == 2:
return 2 # Two pair
if 2 in counts.values():
return 1 # One pair
return 0 # High card
player_score = hand_value(player_hand)
bot_score = hand_value(bot_hand)
if player_score > bot_score:
await update_money(user, wallet=bet)
msg = f"🃏 Your hand: {', '.join(player_hand)}\n🤖 Bot's hand: {', '.join(bot_hand)}\nYou win {bet:,} coins!"
elif player_score < bot_score:
await update_money(user, wallet=-bet)
msg = f"🃏 Your hand: {', '.join(player_hand)}\n🤖 Bot's hand: {', '.join(bot_hand)}\nYou lose {bet:,} coins!"
else:
msg = f"🃏 Your hand: {', '.join(player_hand)}\n🤖 Bot's hand: {', '.join(bot_hand)}\nIt's a draw! No coins won or lost."
await self._send_embed(
ctx,
msg,
(
discord.Color.green()
if "win" in msg
else discord.Color.red() if "lose" in msg else discord.Color.blurple()
),
)
# --- Higher/Lower ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="higherlower",
usage="<bet: integer>",
description="Guess if the next number will be higher or lower.",
brief="Guess higher or lower.",
)
async def higherlower(self, ctx, bet: int):
if not await self._check_limit_and_exclusion(ctx, bet):
return
user = ctx.author
number = random.randint(1, 100)
await self._send_embed(
ctx,
f"The number is **{number}**. Will the next number be higher or lower? Type `higher` or `lower`.",
discord.Color.blurple(),
)
def check(m):
return (
m.author == ctx.author
and m.channel == ctx.channel
and m.content.lower() in ["higher", "lower"]
)
try:
guess_msg = await self.client.wait_for("message", check=check, timeout=15)
except Exception:
await self._send_embed(
ctx, "Timed out. Please try again.", discord.Color.orange()
)
return
next_number = random.randint(1, 100)
win = (guess_msg.content.lower() == "higher" and next_number > number) or (
guess_msg.content.lower() == "lower" and next_number < number
)
if next_number == number:
msg = f"The next number was also **{next_number}**. It's a tie! No coins won or lost."
elif win:
await update_money(user, wallet=bet)
msg = f"The next number was **{next_number}**. You win {bet:,} coins!"
else:
await update_money(user, wallet=-bet)
msg = f"The next number was **{next_number}**. You lose {bet:,} coins!"
await self._send_embed(
ctx, msg, discord.Color.green() if win else discord.Color.red()
)
# --- Scratch ---
@commands.cooldown(1, 2, commands.BucketType.user)
@commands.command(
name="scratch",
usage="<bet: integer>",
description="Buy a scratch card for a chance to win up to 10x your bet.",
brief="Buy a scratch card.",
)
async def scratch(self, ctx, bet: int):
if not await self._check_limit_and_exclusion(ctx, bet):
return
user = ctx.author
symbols = ["🍀", "💎", "", "🍒", "7️⃣"]
card = [random.choice(symbols) for _ in range(3)]
payout = 0
if card.count(card[0]) == 3:
payout = bet * 10
msg = f"Scratch Card: {' '.join(card)}\nJackpot! You win {payout:,} coins!"
elif len(set(card)) == 2:
payout = bet * 2
msg = f"Scratch Card: {' '.join(card)}\nTwo of a kind! You win {payout:,} coins!"
else:
payout = -bet
msg = f"Scratch Card: {' '.join(card)}\nNo match. You lose {bet:,} coins."
await update_money(user, wallet=payout)
await self._send_embed(
ctx, msg, discord.Color.green() if payout > 0 else discord.Color.red()
)
# --- All other user-facing responses (invite, kick, end_gameroom, etc.) ---
# Replace ctx.reply(...) or ctx.send(...) with self._send_embed(...) for consistency.
@commands.command(
name="gameroom",
usage="[game: str] [inactivity: int (minutes)]",
description="Create a private game room that auto-deletes after inactivity.",
brief="Create a private game room.",
)
@commands.guild_only()
async def gameroom(self, ctx, game: str = "game", inactivity: int = 10):
"""Create a private text channel for a game session."""
guild = ctx.guild
overwrites = {
guild.default_role: discord.PermissionOverwrite(read_messages=False),
ctx.author: discord.PermissionOverwrite(
read_messages=True, send_messages=True
),
}
category = ctx.channel.category
channel_name = f"{game}-room-{ctx.author.display_name}".replace(
" ", "-"
).lower()
channel = await guild.create_text_channel(
name=channel_name,
overwrites=overwrites,
category=category,
reason="Private game room",
)
await channel.send(
f"{ctx.author.mention} Your private **{game}** session has started!\n"
f"Use `py invite <user>` and `py kick <user>` in this channel to manage access.\n"
f"This channel will be deleted after **{inactivity} minutes of inactivity**."
)
self.active_rooms[channel.id] = {
"host": ctx.author.id,
"invited": set([ctx.author.id]),
"inactivity": inactivity,
}
self.save_rooms()
self.reset_inactivity_timer(channel.id, inactivity)
def reset_inactivity_timer(self, channel_id, inactivity):
"""Reset the inactivity timer for a game room."""
if channel_id in self.room_timers:
self.room_timers[channel_id].cancel()
task = asyncio.create_task(self.inactivity_task(channel_id, inactivity))
self.room_timers[channel_id] = task
async def inactivity_task(self, channel_id, inactivity):
"""Delete the channel after inactivity period."""
try:
await asyncio.sleep(inactivity * 60)
channel = self.client.get_channel(channel_id)
if channel:
await channel.send("Room deleted due to inactivity.")
await channel.delete(reason="Game room inactive")
self.delete_room(channel_id)
self.room_timers.pop(channel_id, None)
except asyncio.CancelledError:
pass
@commands.Cog.listener()
async def on_message(self, message):
"""Reset inactivity timer on message in a game room."""
if (
hasattr(self, "active_rooms")
and message.channel.id in self.active_rooms
and not message.author.bot
):
inactivity = self.active_rooms[message.channel.id].get("inactivity", 10)
self.reset_inactivity_timer(message.channel.id, inactivity)
@commands.command(
name="invite",
usage="<user: id|name>",
description="Invite a user to your private game room.",
brief="Invite a user to your game room.",
hidden=True,
)
async def invite(
self,
ctx,
member: discord.Member | None = None,
*,
identifier: str | None = None,
):
"""Invite a user to the game room by mention, ID, or username."""
room = self.active_rooms.get(ctx.channel.id)
if not room or ctx.author.id != room["host"]:
await ctx.reply(
"Only the host can invite others, and only in a private game room channel.",
mention_author=False,
)
return
# If member is not provided, try to resolve by ID or name
if member is None and identifier:
try:
member = ctx.guild.get_member(int(identifier))
except ValueError:
member = discord.utils.find(
lambda m: m.name.lower() == identifier.lower()
or m.display_name.lower() == identifier.lower(),
ctx.guild.members,
)
if member is None:
await ctx.reply(
"User not found. Please use a mention, user ID, or username.",
mention_author=False,
)
return
await ctx.channel.set_permissions(
member, read_messages=True, send_messages=True
)
room["invited"].add(member.id)
self.save_rooms()
await ctx.send(
f"{member.mention} has been invited to the game room!",
allowed_mentions=discord.AllowedMentions(users=True),
)
@commands.command(
name="kick",
usage="<user: mention>",
description="Kick a user from your private game room.",
brief="Kick a user from your game room.",
hidden=True,
)
async def kick(self, ctx, member: discord.Member):
"""Kick a user from the game room."""
room = self.active_rooms.get(ctx.channel.id)
if not room or ctx.author.id != room["host"]:
await ctx.reply(
"Only the host can kick others, and only in a private game room channel.",
mention_author=False,
)
return
if member.id == ctx.author.id:
await ctx.reply("You cannot kick yourself (host).", mention_author=False)
return
if member.id not in room["invited"]:
await ctx.reply(
f"{member.mention} is not in this game room.", mention_author=False
)
return
await ctx.channel.set_permissions(member, overwrite=None)
room["invited"].discard(member.id)
self.save_rooms()
await ctx.reply(
f"{member.mention} has been kicked from the game room!",
mention_author=False,
)
@commands.command(
name="end",
aliases=["delete"],
description="End and delete your private game room.",
brief="Delete your game room.",
hidden=True,
)
async def end_gameroom(self, ctx):
"""Allows the host to manually delete the game room channel."""
room = self.active_rooms.get(ctx.channel.id)
if not room or ctx.author.id != room["host"]:
await ctx.reply(
"Only the host can end this game room, and only in a private game room channel.",
mention_author=False,
)
return
await ctx.send("This game room will now be deleted by the host.")
self.delete_room(ctx.channel.id)
await ctx.channel.delete(reason="Game room ended by host")
async def setup(client):
await client.add_cog(Gamble(client))
+216
View File
@@ -0,0 +1,216 @@
import discord
import time
from discord.ext import commands
import logging
from datetime import datetime
from utils.sql_commands import DatabaseManager
# --- Localization dictionary (expand as needed) ---
LOCALE = {
"en": {
"afk_set": "{user} has gone afk.{reason}",
"afk_reason": " Reason: {reason}",
"afk_notify": "{user} is currently unavailable.{reason}",
"feedback_received": "Feedback received. Thank you!",
"missing_perms": "⚠️ The bot is missing the following permissions in **{guild}**: {perms}",
}
}
LANG = "en" # Set your language code here
def _(key, **kwargs):
return LOCALE[LANG][key].format(**kwargs)
class Informational(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager()
async def cog_load(self):
# Bot Permissions Check at startup
for guild in self.client.guilds:
me = guild.me
missing = []
if not me.guild_permissions.manage_roles:
missing.append("Manage Roles")
if not me.guild_permissions.manage_nicknames:
missing.append("Manage Nicknames")
if missing:
owner = guild.owner
try:
await owner.send(
_("missing_perms", guild=guild.name, perms=", ".join(missing))
)
except Exception:
logging.warning(
f"Could not DM owner of {guild.name} about missing permissions."
)
@commands.command(name="whois")
async def _userinfo(self, ctx, member: discord.Member = None): # type: ignore
if member is None:
member = ctx.message.author
roles = [role for role in member.roles][1:]
embed = discord.Embed(
colour=discord.Colour.purple(),
timestamp=ctx.message.created_at,
title=f"User Info - {member}",
)
embed.set_thumbnail(url=member.display_avatar)
embed.set_footer(text=f"Requested by {ctx.author}")
embed.add_field(name="ID:", value=member.id)
embed.add_field(name="Display Name:", value=member.display_name)
embed.add_field(
name="Created Account On:",
value=member.created_at.strftime("%a, %#d %B %Y, %I:%M %p UTC"),
)
embed.add_field(
name="Joined Server On:",
value=member.joined_at.strftime("%a, %#d %B %Y, %I:%M %p UTC"), # type: ignore
)
embed.add_field(name="Roles:", value="".join([role.mention for role in roles]))
embed.add_field(name="Highest Role:", value=member.top_role.mention)
await ctx.send(embed=embed)
@commands.command(name="serverInfo")
@commands.has_permissions(view_audit_log=True) # Example permission
async def _server(self, ctx):
embed = discord.Embed(
title=f"{ctx.guild.name} Info",
description="Information of this Server",
color=discord.Colour.blue(),
)
embed.add_field(name="🆔Server ID", value=f"{ctx.guild.id}", inline=True)
embed.add_field(
name="📆Created On",
value=ctx.guild.created_at.strftime("%b %d %Y"),
inline=True,
)
embed.add_field(name="👑Owner", value=f"{ctx.guild.owner.mention}", inline=True)
embed.add_field(
name="👥Members", value=f"{ctx.guild.member_count} Members", inline=True
)
embed.add_field(
name="💬Channels",
value=f"{len(ctx.guild.text_channels)} Text | {len(ctx.guild.voice_channels)} Voice",
inline=True,
)
embed.set_thumbnail(url=ctx.guild.icon)
embed.set_footer(text="⭐ • Duo")
embed.set_author(name=f"{ctx.author.name}", icon_url=ctx.message.author.avatar)
await ctx.send(embed=embed)
@commands.command(name="afk")
@commands.cooldown(1, 10, commands.BucketType.user)
async def _afk(self, ctx, *reason):
afk_reason = " ".join(reason)
timestamp = datetime.utcnow().isoformat()
# Store AFK reason and timestamp in DB
self.db.execute_query(
"REPLACE INTO afk_status (USERID, GUILDID, REASON, TIMESTAMP) VALUES (%s, %s, %s, %s)",
(ctx.author.id, ctx.guild.id, afk_reason, timestamp),
)
msg = _(
"afk_set",
user=ctx.author.mention,
reason=_("afk_reason", reason=afk_reason) if afk_reason else "",
)
await ctx.send(msg)
# Prevent stacking [AFK]
try:
current_nick = ctx.author.nick or ctx.author.name
if "[AFK]" not in current_nick:
await ctx.author.edit(nick=f"{current_nick} [AFK]")
except discord.Forbidden:
logging.warning(f"Missing permissions to edit nickname for {ctx.author}")
except Exception as e:
logging.error(f"Error editing nickname: {e}")
guild = ctx.guild
try:
role = discord.utils.get(ctx.guild.roles, name="AFK")
if not role:
role = await guild.create_role(name="AFK", hoist=True)
all_roles = await guild.fetch_roles()
num_roles = len(all_roles)
await role.edit(reason=None, position=num_roles - 2)
await ctx.author.add_roles(role)
except discord.Forbidden:
logging.warning("Missing permissions to create/add AFK role.")
except Exception as e:
logging.error(f"Error handling AFK role: {e}")
try:
await ctx.message.delete()
except discord.Forbidden:
pass
@commands.command(name="afklist")
async def afk_list(self, ctx):
afks = self.db.fetch_all(
"SELECT USERID, REASON, TIMESTAMP FROM afk_status WHERE GUILDID = %s",
(ctx.guild.id,),
)
if not afks:
await ctx.reply("No one is AFK right now.")
return
lines = []
for afk in afks:
member = ctx.guild.get_member(int(afk["USERID"]))
reason = afk["REASON"] or "No reason given."
timestamp = afk.get("TIMESTAMP")
# Convert timestamp string to datetime
if isinstance(timestamp, str):
try:
timestamp = datetime.fromisoformat(timestamp)
except Exception:
timestamp = None
if timestamp:
duration = datetime.utcnow() - timestamp
duration_str = str(duration).split(".")[0]
else:
duration_str = "unknown duration"
lines.append(
f"{member.mention if member else afk['USERID']} - {reason} (AFK for {duration_str})"
)
await ctx.send("\n".join(lines))
@commands.command(name="feedback")
@commands.cooldown(1, 30, commands.BucketType.user)
async def _feedback(self, ctx, *info):
feedback_text = " ".join(info)
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
user = f"{ctx.author} ({ctx.author.id})"
# Store feedback in DB
self.db.execute_query(
"INSERT INTO feedback (USER, GUILDID, TIMESTAMP, CONTENT) VALUES (%s, %s, %s, %s)",
(user, ctx.guild.id if ctx.guild else None, timestamp, feedback_text),
)
await ctx.reply(_("feedback_received"))
@commands.Cog.listener()
async def on_command_error(self, ctx, error):
# Notify admin if permissions are missing
if isinstance(
error, (commands.MissingPermissions, commands.BotMissingPermissions)
):
perms = getattr(error, "missing_perms", None)
perms_str = ", ".join(perms) if perms else "Unknown"
owner = ctx.guild.owner if ctx.guild else None
if owner:
try:
await owner.send(
_("missing_perms", guild=ctx.guild.name, perms=perms_str)
)
except Exception:
logging.warning(
f"Could not DM owner of {ctx.guild.name} about missing permissions."
)
raise error # Let default handler run too
async def setup(client):
await client.add_cog(Informational(client))
+308
View File
@@ -0,0 +1,308 @@
import discord
import time
from datetime import timedelta
from discord.ext import commands
from utils.sql_commands import DatabaseManager
import logging
import json
from collections import defaultdict
async def remove_afk_status(member, db=None):
"""Removes AFK status for a member by resetting their nickname and removing the AFK role and DB entry."""
afk_role = discord.utils.get(member.guild.roles, name="AFK")
bot_member = member.guild.me
changed = False
# Remove AFK role if present
if afk_role and afk_role in member.roles:
try:
await member.remove_roles(afk_role)
changed = True
except discord.Forbidden:
logging.warning(f"Missing permissions to remove AFK role for {member.name}")
except discord.HTTPException as e:
logging.error(f"HTTP error removing AFK role for {member.name}: {e}")
# Remove [AFK] from nickname if possible, not owner, and bot has higher role
try:
if (
member.guild.owner_id != member.id
and member.nick
and "[AFK]" in member.nick
and bot_member.top_role > member.top_role
):
await member.edit(
nick=member.nick.replace("[AFK]", "").replace(" ", " ").strip()
)
changed = True
except discord.Forbidden:
logging.warning(f"Missing permissions to edit nickname for {member.name}")
except Exception as e:
logging.error(f"Error editing nickname: {e}")
# Remove AFK entry from DB if provided
if db:
db.execute_query(
"DELETE FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(member.id, member.guild.id),
)
changed = True
return changed if changed else False
class Listeners(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager()
# Load or initialize stats
self.stats_file = "message_command_stats.json"
self.stats = self.load_stats()
# Add new counters if not present
if "total_messages" not in self.stats:
self.stats["total_messages"] = 0
if "command_messages" not in self.stats:
self.stats["command_messages"] = 0
if "non_command_messages" not in self.stats:
self.stats["non_command_messages"] = 0
def load_stats(self):
try:
with open(self.stats_file, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return {
"messages": defaultdict(int),
"commands": defaultdict(int),
"total_messages": 0,
"command_messages": 0,
"non_command_messages": 0,
}
def save_stats(self):
# Convert defaultdicts to dicts for JSON serialization
stats_to_save = {
"messages": dict(self.stats["messages"]),
"commands": dict(self.stats["commands"]),
"channels": dict(self.stats.get("channels", {})),
"guilds": dict(self.stats.get("guilds", {})),
"total_messages": self.stats.get("total_messages", 0),
"command_messages": self.stats.get("command_messages", 0),
"non_command_messages": self.stats.get("non_command_messages", 0),
}
with open(self.stats_file, "w", encoding="utf-8") as f:
json.dump(stats_to_save, f, indent=2)
@commands.Cog.listener()
async def on_ready(self):
print(f"{self.client.user} has been loaded")
activity = discord.Activity(type=discord.ActivityType.watching, name="You :)")
await self.client.change_presence(
status=discord.Status.online, activity=activity
)
# Record the start time in a more readable format
with open("time.txt", "w") as file:
file.write(str(time.time()))
@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error):
user = ctx.author
if isinstance(error, commands.CommandNotFound):
return
if isinstance(error, (commands.MissingPermissions, commands.NotOwner)):
return await ctx.reply("You cannot use this command.", mention_author=False)
if isinstance(error, commands.MemberNotFound):
return await ctx.reply(
"The member you provided is incorrect or not found.",
mention_author=False,
)
if (
isinstance(error, commands.MissingRequiredArgument)
and ctx.command is not None
):
cmd_parent = ctx.command.parent
cmd_name = (
f"{cmd_parent} {ctx.command.name}" if cmd_parent else ctx.command.name
)
cmd_usage = ctx.command.usage or ""
cmd_params = list(ctx.command.params.values())
params = []
for param in cmd_params[1:]: # Skip the 'self' parameter
log = (
f"<{param.name}>"
if param.default is not param.empty
else f"<{param.name}*>"
) # Required args
params.append(log)
usage = (
f"Usage: `{cmd_name} {' '.join(params)}`"
if not cmd_usage
else f"Usage: `{cmd_usage}`"
)
em = discord.Embed(description=f"**Correct usage**\n{usage}")
if ctx.command.aliases:
em.add_field(name="Aliases", value=", ".join(ctx.command.aliases))
em.set_footer(text="' * ' means that argument is required")
return await ctx.reply(embed=em, mention_author=False)
if isinstance(error, commands.CommandOnCooldown):
time_left = timedelta(seconds=error.retry_after)
return await ctx.reply(
f"You are on cooldown. Try after `{time_left}`.", mention_author=False
)
# For any other error, we will raise it to be handled by the default error handler
raise error
@commands.Cog.listener()
async def on_message(self, message):
if message.author.bot or not message.guild:
return
user_id = str(message.author.id)
if user_id not in self.stats["messages"]:
self.stats["messages"][user_id] = {
"total": 0,
"commands": 0,
"non_commands": 0,
}
self.stats["messages"][user_id]["total"] += 1
# --- Per-channel stats by channel name ---
channel_name = message.channel.name
if "channels" not in self.stats:
self.stats["channels"] = {}
if channel_name not in self.stats["channels"]:
self.stats["channels"][channel_name] = 0
self.stats["channels"][channel_name] += 1
# --- Per-guild stats by guild name ---
guild_name = message.guild.name
if "guilds" not in self.stats:
self.stats["guilds"] = {}
if guild_name not in self.stats["guilds"]:
self.stats["guilds"][guild_name] = 0
self.stats["guilds"][guild_name] += 1
ctx = await self.client.get_context(message)
if ctx.valid and ctx.command:
self.stats["messages"][user_id]["commands"] += 1
else:
self.stats["messages"][user_id]["non_commands"] += 1
self.save_stats()
# Prevent AFK removal on the afk command itself
invoked_afk = False
ctx = await self.client.get_context(message)
if ctx.valid and ctx.command and ctx.command.name == "afk":
invoked_afk = True
if not invoked_afk:
afk_role = discord.utils.get(message.guild.roles, name="AFK")
was_afk = False
# Check DB for AFK status (covers owner and anyone else)
afk_db_entry = self.db.fetch_one(
"SELECT REASON FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(message.author.id, message.guild.id),
)
if afk_db_entry:
was_afk = True
# Remove AFK role if present
if afk_role and afk_role in message.author.roles:
try:
await message.author.remove_roles(afk_role)
except discord.Forbidden:
logging.warning("Missing permissions to remove AFK role.")
except Exception as e:
logging.error(f"Error removing AFK role: {e}")
# Remove [AFK] from nickname if possible and not owner/higher role
try:
bot_member = message.guild.me
if (
message.guild.owner_id != message.author.id
and message.author.nick
and "[AFK]" in message.author.nick
and bot_member.top_role > message.author.top_role
):
await message.author.edit(
nick=message.author.nick.replace("[AFK]", "")
.replace(" ", " ")
.strip()
)
except discord.Forbidden:
logging.warning(
f"Missing permissions to edit nickname for {message.author}"
)
except Exception as e:
logging.error(f"Error editing nickname: {e}")
# Remove AFK entry from DB
self.db.execute_query(
"DELETE FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(message.author.id, message.guild.id),
)
# Send a welcome back message if user was AFK (even owner)
if was_afk:
await message.channel.send(
f"Welcome back, {message.author.mention}! You are no longer AFK."
)
# Notify if mentioned user is AFK (check DB, not just role)
for member in message.mentions:
afk_entry = self.db.fetch_one(
"SELECT REASON FROM afk_status WHERE USERID = %s AND GUILDID = %s",
(member.id, message.guild.id),
)
if afk_entry:
reason = afk_entry["REASON"] if afk_entry["REASON"] else ""
msg = f"{member.mention} is currently unavailable." + (
f" Reason: {reason}" if reason else ""
)
await message.channel.send(msg)
break
@commands.Cog.listener()
async def on_command(self, ctx):
# Track command usage
cmd_name = ctx.command.qualified_name if ctx.command else "unknown"
self.stats["commands"][cmd_name] = self.stats["commands"].get(cmd_name, 0) + 1
self.save_stats()
@commands.Cog.listener()
async def on_guild_join(self, guild):
# Log to console
print(f"Joined new guild: {guild.name} (ID: {guild.id})")
# Optionally, send a message to the first text channel the bot can send messages in
for channel in guild.text_channels:
if channel.permissions_for(guild.me).send_messages:
await channel.send(
f"Hello! Thanks for inviting me to **{guild.name}**.\n"
"Use `py help` to see my commands!"
)
break
# Optionally, update your stats file
if hasattr(self, "stats"):
guild_name = guild.name
if "guilds" not in self.stats:
self.stats["guilds"] = {}
self.stats["guilds"][guild_name] = (
self.stats["guilds"].get(guild_name, 0) + 1
)
self.save_stats()
async def setup(client):
await client.add_cog(Listeners(client))
Executable
+135
View File
@@ -0,0 +1,135 @@
import discord
from discord.ext import commands
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from dotenv import load_dotenv
from os import getenv
import html
from datetime import datetime
class Mail(commands.Cog):
def __init__(self, client):
self.client = client
load_dotenv()
from utils.sql_commands import DatabaseManager
self.db = DatabaseManager()
@commands.is_owner()
@commands.command(name="mail_feedback")
async def mail(self, ctx):
password = getenv("EMAILPASS")
username = getenv("EMAILUSER")
server = getenv("EMAILSERVER")
port = getenv("EMAILPORT")
receiver = getenv("FEEDBACKRECEIVER")
if None in [password, username, server, port, receiver]:
await ctx.reply(
"Email configuration is missing. Please check your environment variables.",
delete_after=5,
)
return
try:
port = int(port) # type: ignore # Error invalid as for problem is taken care of above
s = smtplib.SMTP(host=server, port=port) # type: ignore
s.starttls()
s.login(username, password) # type: ignore
msg = MIMEMultipart("alternative")
msg["To"] = receiver # type: ignore
msg["From"] = username # type: ignore
msg["Subject"] = "Py feedback"
# Fetch feedback from the database
feedback_rows = self.db.fetch_all("SELECT * FROM feedback")
all_feedback = ""
for i, row in enumerate(feedback_rows, 1):
content = html.escape(row["CONTENT"])
user = html.escape(row["USER"])
timestamp = html.escape(row["TIMESTAMP"])
all_feedback += f"""
<li style="margin-bottom:25px; border-bottom:1px solid #edf2f7; padding-bottom:20px;">
<div class="feedback-card" style="background:#ffffff; border-radius:8px; position:relative;">
<div style="display:flex; align-items:center; margin-bottom:12px;">
<div style="background:#4361ee; width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; color:white; font-weight:bold; flex-shrink:0;">
{i}
</div>
<div style="margin-left:15px;">
<h3 style="margin:0; font-size:16px; color:#2d3748;">{user}</h3>
<p style="margin:3px 0 0; font-size:13px; color:#718096;">{timestamp}</p>
</div>
</div>
<div style="background:#f8f9fc; padding:15px; border-radius:8px; border-left:3px solid #4361ee;">
<p style="margin:0; font-size:15px; line-height:1.5; color:#4a5568;">{content}</p>
</div>
</div>
</li>
"""
text = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Feedback Report</title>
<style>
@media only screen and (max-width: 600px) {{
.container {{
width: 95% !important;
}}
.feedback-card {{
padding: 12px !important;
}}
}}
</style>
</head>
<body style="margin:0; padding:20px 0; background-color:#f7f9fc; font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<div class="container" style="max-width:600px; margin:0 auto; background:#ffffff; border-radius:10px; box-shadow:0 4px 15px rgba(0,0,0,0.05);">
<!-- Header -->
<div style="background: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%); padding:30px 0; border-radius:10px 10px 0 0; text-align:center;">
<h1 style="color:#fff; margin:0; font-weight:600;">User Feedback Report</h1>
<p style="color:rgba(255,255,255,0.8); margin:8px 0 0; font-size:18px;">New feedback submissions</p>
</div>
<!-- Summary -->
<div style="padding:20px 30px; background:#f0f7ff; border-bottom:1px solid #e3f2fd;">
<p style="margin:0; font-size:16px; color:#2d3748;">
Total feedback submissions: <strong>{len(feedback_rows)}</strong>
</p>
</div>
<!-- Feedback Items -->
<div style="padding:10px 30px 30px;">
<ul style="list-style:none; padding:0; margin:0;">
{all_feedback}
</ul>
</div>
<!-- Footer -->
<div style="padding:20px 30px; text-align:center; background:#f8f9fa; border-top:1px solid #eaeaea; border-radius:0 0 10px 10px; color:#718096; font-size:14px;">
<p style="margin:0;">Generated automatically by PyBot • {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
<p style="margin:8px 0 0;">Do not reply to this automated message</p>
</div>
</div>
</body>
</html>
"""
msg.attach(MIMEText(text, "html"))
s.send_message(msg)
await ctx.reply("Mail sent.", delete_after=2)
s.quit()
except Exception as e:
await ctx.reply(f"Failed to send mail: {e}", delete_after=5)
async def setup(client):
await client.add_cog(Mail(client))
Executable
+245
View File
@@ -0,0 +1,245 @@
import discord
from discord.ext import commands
from utils.sql_commands import DatabaseManager
from utils.bank_functions import *
from random import randint
from math import log2
from datetime import datetime
import io
from PIL import Image, ImageDraw, ImageFont
import logging
import os
def calculate_xp_needed_for_next_level(level: int) -> int:
"""Calculates the XP needed for the next level."""
return int(35 * (level**2) + 35 * level)
class XP(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager("")
@commands.Cog.listener()
async def on_message(self, ctx: discord.Message) -> None:
if ctx.author.bot:
return
try:
data = self.db.fetch_one(
"SELECT XP, LEVEL FROM users WHERE ID = %s", (ctx.author.id,)
)
if data is None:
# Insert a new row for this user
current_level = 0
current_xp = 0
self.db.execute_query(
"INSERT INTO users (ID, XP, LEVEL) VALUES (%s, %s, %s)",
(ctx.author.id, current_xp, current_level),
)
else:
current_level = data.get("LEVEL", 0)
current_xp = data.get("XP", 0)
logging.info(f"XP: {current_xp}, Level: {current_level}")
# Balanced XP calculation
base = randint(5, 10)
length_bonus = (
min(len(ctx.content), 100) // 10
) # +1 XP per 10 chars, max +10
extra_xp = base + length_bonus
extra_xp = min(extra_xp, 20) # cap at 20 XP per message
current_xp += extra_xp
if current_xp > calculate_xp_needed_for_next_level(current_level):
current_level += 1
player_balance = self.db.fetch_one(
"SELECT BANK FROM economy WHERE ID = %s", (ctx.author.id,)
)
player_balance = player_balance.get("BANK", 0) if player_balance else 0
levelup_bonus = randint(500, 2000)
new_balance = player_balance + levelup_bonus
await update_money(ctx.author, bank=new_balance)
await ctx.channel.send(
f"Congrats!! You achieved a new level.\nYou are now level {current_level}. You received {levelup_bonus}<:flooney:1194943899765051473>"
)
current_xp = 0
# Use INSERT ... ON DUPLICATE KEY UPDATE for XP/level
self.db.execute_query(
"INSERT INTO users (ID, XP, LEVEL) VALUES (%s, %s, %s) "
"ON DUPLICATE KEY UPDATE XP = %s, LEVEL = %s",
(ctx.author.id, current_xp, current_level, current_xp, current_level),
)
except Exception as e:
logging.error(f"Error in XP on_message: {e}")
@commands.command()
async def top(self, ctx: commands.Context) -> None:
embed = discord.Embed(
colour=discord.Colour.purple(),
timestamp=ctx.message.created_at,
title="Top Messengers",
)
users = self.db.fetch_all("SELECT ID, XP, LEVEL FROM users")
if not users:
return
sorted_users = sorted(users, key=lambda x: int(x["XP"]), reverse=True)[:10]
data = []
for index, member in enumerate(sorted_users, start=1):
try:
member_obj = await self.client.fetch_user(int(member["ID"]))
member_name = (
member_obj.display_name
if hasattr(member_obj, "display_name")
else str(member_obj)
)
except Exception:
member_name = f"User {member['ID']}"
member_xp, member_lvl = int(member["XP"]), int(member["LEVEL"])
medals = ["🥇", "🥈", "🥉"]
msg = f"**{medals[index - 1] if index <= 3 else index} `{member_name}` -- {member_xp:,}xp, {member_lvl:,}level**"
data.append(msg)
msg = "\n".join(data)
embed.description = f"It's Based on XP of Global Users\n\n{msg}"
guild_name = ctx.guild.name if ctx.guild is not None else "Direct Message"
embed.set_footer(text=f"GLOBAL - {guild_name}")
embed.color = discord.Color(0x00FF00)
await ctx.reply(embed=embed, mention_author=False)
@commands.command()
async def stats(self, ctx: commands.Context) -> None:
"""Shows the user's current XP and level."""
try:
# Use a fallback background if the random one doesn't exist
bg_path = f"images/image_{randint(1,100)}.jpg"
if not os.path.exists(bg_path):
bg_path = "images/default.jpg"
profile_image = await self.create_profile_card(ctx.author, bg_path)
await ctx.send(file=discord.File(profile_image, "profile.png"))
if profile_image is not None:
profile_image.close()
except ZeroDivisionError:
await ctx.send(f"{ctx.author.mention}, you haven't earned any XP yet.")
except Exception as e:
await ctx.send(f"Error creating profile card: {e}")
logging.error(f"Error creating profile card: {e}")
async def create_profile_card(
self, user: discord.Member | discord.User, background_image_path: str
) -> io.BytesIO:
"""Create a profile card image for a user with a custom background and text overlay."""
card_width, card_height = 400, 200
text_color = (255, 255, 255)
progress_bar_color = (0, 255, 0)
outline_color = (255, 255, 255)
tint_color = (50, 50, 50)
transparency = 25
opacity = int(255 * transparency / 100)
# Load and resize the background image
try:
background_image = Image.open(background_image_path).resize(
(card_width, card_height)
)
except Exception:
# Fallback to a plain background if image fails
# type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
background_image = Image.new(
"RGBA", (card_width, card_height), (30, 30, 30, 255) # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
)
card_image = Image.new("RGBA", (card_width, card_height))
card_image.paste(background_image, (0, 0))
overlay = Image.new(
"RGBA",
card_image.size,
tint_color + (opacity,), # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
)
card_image = Image.alpha_composite(card_image, overlay)
draw = ImageDraw.Draw(card_image)
# Load fonts with fallback
font_path = "arial.ttf"
try:
font = ImageFont.truetype(font_path, 20)
except Exception:
font = ImageFont.load_default()
draw.text((10, 10), f"User: {user.display_name}", fill=text_color, font=font)
user_info = self.db.fetch_one(
"SELECT XP, LEVEL FROM users WHERE ID = %s", (user.id,)
)
draw.text(
(10, 40), f"Level: {user_info.get('LEVEL', 0)}", fill=text_color, font=font
)
draw.text(
(10, 70),
f"XP: {user_info.get('XP', 0)}/{calculate_xp_needed_for_next_level(user_info.get('LEVEL', 0))}",
fill=text_color,
font=font,
)
draw.text(
(10, 100),
f"Next level in: {calculate_xp_needed_for_next_level(user_info.get('LEVEL', 0))-user_info.get('XP', 0)}XP",
fill=text_color,
font=font,
)
max_xp = calculate_xp_needed_for_next_level(user_info.get("LEVEL", 0))
progress_ratio = user_info.get("XP", 0) / max_xp if max_xp > 0 else 0
progress_width = progress_ratio * 200
draw.rectangle([(10, 130), (210, 150)], outline=outline_color, width=2)
draw.rectangle([(10, 130), (10 + progress_width, 150)], fill=progress_bar_color)
try:
profile_size = 96
margin = 30
if user.avatar is not None:
profile_picture_data = await user.avatar.read()
profile_image = Image.open(io.BytesIO(profile_picture_data)).resize(
(profile_size, profile_size)
)
else:
# Use a default avatar image if user has no avatar
default_avatar_path = "images/default_avatar.png"
if os.path.exists(default_avatar_path):
profile_image = Image.open(default_avatar_path).resize(
(profile_size, profile_size)
)
else:
# Create a blank image if default not found
# type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
profile_image = Image.new(
"RGBA", (profile_size, profile_size), (100, 100, 100, 255) # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
)
card_image.paste(
profile_image,
(card_width - profile_size - margin, margin),
profile_image.convert("RGBA"),
)
except Exception as error:
logging.error(f"Error fetching profile picture: {error}")
image_bytes = io.BytesIO()
card_image.save(image_bytes, format="PNG")
image_bytes.seek(0)
return image_bytes
async def setup(client):
await client.add_cog(XP(client))