Refactor bot server setup to use Waitress for production; fallback to Flask dev server for local development. Added timeout to HTTP requests in Fun and Test cogs. Improved error handling for missing environment variables. Enhanced secret key management in Flask app. Added request timeout configuration. Introduced new experimental features including user profile and balance cards, and a Tic-Tac-Toe game with Minimax AI. Addressed various database and security issues, and improved code quality across multiple files.

This commit is contained in:
2026-05-31 12:01:12 +00:00
parent be89cc3acd
commit 1b91cbcb2f
17 changed files with 284 additions and 61 deletions
-251
View File
@@ -1,251 +0,0 @@
import discord
from discord.ext import commands
from PIL import Image, ImageDraw, ImageFont, ImageOps
import io
import os
from dotenv import load_dotenv
import requests
from random import randint
# Bot setup
load_dotenv()
TOKEN = os.getenv("TOKEN")
intents = discord.Intents.all()
bot = commands.Bot(command_prefix="!?", intents=intents)
# Simple user data structure
user_data = {}
def get_user_data(user_id):
"""Get or initialize user data."""
if user_id not in user_data:
user_data[user_id] = {"xp": 0, "level": 53}
return user_data[user_id]
import io
from PIL import Image, ImageDraw, ImageFont
async def create_profile_card(user, background_image_path):
"""
Create a profile card image for a user with a custom background and text overlay.
Args:
user (discord.Member): The user whose profile card is being generated.
background_image_path (str): Path to the background image.
Returns:
io.BytesIO: A BytesIO object containing the profile card image.
"""
# Profile card dimensions
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
background_image = Image.open(background_image_path).resize((card_width, card_height))
# Create a new image with the background
card_image = Image.new("RGBA", (card_width, card_height))
card_image.paste(background_image, (0, 0))
# Create a semi-transparent overlay
overlay = Image.new("RGBA", card_image.size, tint_color + (opacity,))
card_image = Image.alpha_composite(card_image, overlay)
draw = ImageDraw.Draw(card_image)
# Load fonts
font_path = "arial.ttf"
font = ImageFont.truetype(font_path, 20)
# Draw user info
draw.text((10, 10), f"User: {user.display_name}", fill=text_color, font=font)
user_info = get_user_data(user.id)
draw.text((10, 40), f"Level: {user_info['level']}", fill=text_color, font=font)
draw.text((10, 70), f"XP: {user_info['xp']}/{user_info['level'] * 100}", fill=text_color, font=font)
# Draw the progress bar
max_xp = user_info['level'] * 100
progress_ratio = user_info['xp'] / max_xp if max_xp > 0 else 0
progress_width = int(progress_ratio * 200)
draw.rectangle([(10, 100), (210, 120)], outline=outline_color, width=2)
draw.rectangle([(10, 100), (10 + progress_width, 120)], fill=progress_bar_color)
# Draw the user's profile picture
try:
profile_size = 96
margin = 30
profile_picture_data = await user.avatar.read()
profile_image = Image.open(io.BytesIO(profile_picture_data)).resize((profile_size, profile_size))
card_image.paste(profile_image, (card_width - profile_size - margin, margin), profile_image.convert("RGBA"))
except Exception as error:
print(f"Error fetching profile picture: {error}")
# Save image to a BytesIO object
image_bytes = io.BytesIO()
card_image.save(image_bytes, format="PNG")
image_bytes.seek(0)
return image_bytes
async def create_balance_card(user, credits, tokens, bucks):
# Card dimensions
width, height = 450, 250
card = Image.new("RGB", (width, height), (255, 255, 255))
draw = ImageDraw.Draw(card)
# Gradient Background
gradient_color_1 = (38, 0, 77) # Dark purple
gradient_color_2 = (128, 0, 128) # Purple
for y in range(height):
blend = y / height
r = int(gradient_color_1[0] * (1 - blend) + gradient_color_2[0] * blend)
g = int(gradient_color_1[1] * (1 - blend) + gradient_color_2[1] * blend)
b = int(gradient_color_1[2] * (1 - blend) + gradient_color_2[2] * blend)
draw.line([(0, y), (width, y)], fill=(r, g, b))
# Avatar with circular border
avatar_asset = user.avatar
avatar_response = requests.get(avatar_asset)
avatar = Image.open(io.BytesIO(avatar_response.content)).resize((60, 60))
mask = Image.new("L", avatar.size, 0)
ImageDraw.Draw(mask).ellipse((0, 0, 60, 60), fill=255)
avatar = ImageOps.fit(avatar, mask.size, centering=(0.5, 0.5))
avatar.putalpha(mask)
# Draw avatar circle background
avatar_bg = Image.new("RGBA", (70, 70), (255, 255, 255, 0))
draw_bg = ImageDraw.Draw(avatar_bg)
draw_bg.ellipse((0, 0, 70, 70), fill=(255, 255, 255, 100)) # Border color
card.paste(avatar_bg, (20, height - 110), avatar_bg)
card.paste(avatar, (25, height - 105), avatar)
# Fonts
title_font = ImageFont.load_default()
balance_font = ImageFont.load_default()
# Header Text
draw.text((110, 20), "tatsu.", font=title_font, fill="white")
draw.text((width - 90, 25), "Member", font=title_font, fill="white")
# Balance Background Box
balance_box = (100, 70, width - 20, 200)
draw.rounded_rectangle(balance_box, radius=15, fill=(255, 255, 255, 50))
# Balance Items
icon_x = 120
text_x = icon_x + 35
# Credits
draw.ellipse((icon_x, 90, icon_x + 25, 115), fill=(255, 215, 0)) # Coin icon color
draw.text((text_x, 90), f"{credits:,} Credits", font=balance_font, fill="black")
# Tokens
draw.ellipse(
(icon_x, 125, icon_x + 25, 150), fill=(0, 255, 127)
) # Token icon color
draw.text((text_x, 125), f"{tokens:,} Tokens", font=balance_font, fill="black")
# Bucks
draw.ellipse(
(icon_x, 160, icon_x + 25, 185), fill=(50, 205, 50)
) # Bucks icon color
draw.text((text_x, 160), f"{bucks:,} Guild Bucks", font=balance_font, fill="black")
# User tag
user_tag = f"{user.name}#{user.discriminator}"
draw.text((110, height - 35), user_tag, font=title_font, fill="white")
# Add Chip (Credit Card Style)
chip_width, chip_height = 50, 35
chip_x = width - 80
chip_y = 100
draw.rectangle(
[chip_x, chip_y, chip_x + chip_width, chip_y + chip_height],
fill=(192, 192, 192), # Light gray color for the chip
outline="white",
width=2,
)
# Chip lines for texture
line_spacing = 7
for i in range(1, chip_height // line_spacing):
y = chip_y + i * line_spacing
draw.line(
[(chip_x + 5, y), (chip_x + chip_width - 5, y)], fill="white", width=1
)
# Save to a BytesIO object to send on Discord
with io.BytesIO() as image_binary:
card.save(image_binary, "PNG")
image_binary.seek(0)
return discord.File(fp=image_binary, filename="balance_card.png")
@bot.event
async def on_ready():
if bot.user is not None:
print(f"Logged in as {bot.user.name}")
else:
print(f"Logged in")
@bot.command(name="profile")
async def profile(ctx):
"""Command to display user profile."""
user = ctx.author
user_info = get_user_data(user.id)
# Create profile card image
profile_image = create_profile_card(user, f"images/image_{randint(1,100)}.jpg")
# Send the image in the channel
await ctx.send(file=discord.File(await profile_image, "profile.png"))
@bot.command()
async def balance(ctx):
user = ctx.author
# Example balance data; replace with actual data retrieval
credits = 9493006
tokens = 58
bucks = 234328
file = await create_balance_card(user, credits, tokens, bucks)
await ctx.send(file=file)
# Example command to add XP for testing
@bot.command(name="addxp")
async def add_xp(ctx, amount: int):
"""Command to add XP for testing."""
user_info = get_user_data(ctx.author.id)
user_info["xp"] += amount
# Level up logic (example: every 100 XP earns a level)
if user_info["xp"] >= user_info["level"] * 100:
user_info["level"] += 1
user_info["xp"] = 0 # Reset XP after leveling up
await ctx.send(
f"Added {amount} XP to {ctx.author.mention}. Total XP: {user_info['xp']} | Level: {user_info['level']}"
)
if TOKEN is not None:
bot.run(TOKEN)
else:
print(f"{TOKEN=}")