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:
@@ -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=}")
|
||||
Reference in New Issue
Block a user