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
+79 -33
View File
@@ -20,7 +20,16 @@ import json
load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv("SECRET_KEY")
# Ensure a secret key is set for session security. In production this MUST be
# provided via the SECRET_KEY environment variable. In development we generate
# a temporary one (not suitable for production).
secret = os.getenv("SECRET_KEY")
if not secret:
if os.getenv("FLASK_ENV") == "production":
raise EnvironmentError("SECRET_KEY must be set in production environment")
print("Warning: SECRET_KEY not set; generating a temporary key for development.")
secret = os.urandom(32)
app.secret_key = secret
db = initialize_database()
# Ensure required environment variables are loaded
@@ -30,6 +39,9 @@ DISCORD_REDIRECT_URI = os.getenv("DISCORD_REDIRECT_URI")
DISCORD_API_BASE_URL = "https://discord.com/api"
DISCORD_BOT_TOKEN = os.getenv("TOKEN")
# Default request timeout (seconds) for external HTTP calls
REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "10"))
# Check for missing environment variables
if not all([DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_REDIRECT_URI]):
raise EnvironmentError("One or more required environment variables are missing.")
@@ -71,9 +83,17 @@ def callback():
"redirect_uri": DISCORD_REDIRECT_URI,
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(
f"{DISCORD_API_BASE_URL}/oauth2/token", data=data, headers=headers
)
try:
response = requests.post(
f"{DISCORD_API_BASE_URL}/oauth2/token",
data=data,
headers=headers,
timeout=REQUEST_TIMEOUT,
)
response.raise_for_status()
except requests.RequestException:
flash("Failed to retrieve access token from Discord (network error).")
return redirect(url_for("home"))
if response.status_code != 200:
flash("Failed to retrieve access token from Discord.")
@@ -88,19 +108,32 @@ def callback():
session["access_token"] = access_token
# Fetch user info
user_data = requests.get(
f"{DISCORD_API_BASE_URL}/users/@me",
headers={"Authorization": f"Bearer {access_token}"},
).json()
try:
user_resp = requests.get(
f"{DISCORD_API_BASE_URL}/users/@me",
headers={"Authorization": f"Bearer {access_token}"},
timeout=REQUEST_TIMEOUT,
)
user_resp.raise_for_status()
user_data = user_resp.json()
except requests.RequestException:
flash("Failed to fetch user info from Discord.")
return redirect(url_for("home"))
# Store user information in session
session["user"] = user_data
# Fetch guilds the user is in
guilds_response = requests.get(
f"{DISCORD_API_BASE_URL}/users/@me/guilds",
headers={"Authorization": f"Bearer {access_token}"},
)
try:
guilds_response = requests.get(
f"{DISCORD_API_BASE_URL}/users/@me/guilds",
headers={"Authorization": f"Bearer {access_token}"},
timeout=REQUEST_TIMEOUT,
)
guilds_response.raise_for_status()
except requests.RequestException:
flash("Failed to retrieve guilds from Discord.")
return redirect(url_for("home"))
if guilds_response.status_code == 200:
guilds = guilds_response.json() # Store guilds in a variable
@@ -109,16 +142,20 @@ def callback():
# Fetch member count and permissions for each guild
for guild in guilds:
guild_id = guild["id"]
guild_info_response = requests.get(
f"{DISCORD_API_BASE_URL}/guilds/{guild_id}",
headers={"Authorization": f"Bearer {access_token}"},
)
if guild_info_response.status_code == 200:
try:
guild_info_response = requests.get(
f"{DISCORD_API_BASE_URL}/guilds/{guild_id}",
headers={"Authorization": f"Bearer {access_token}"},
timeout=REQUEST_TIMEOUT,
)
guild_info_response.raise_for_status()
guild_info = guild_info_response.json()
guild["approx_member_count"] = guild_info.get("member_count", "N/A")
guild["permissions"] = guild_info.get(
"permissions", 0
) # Store permissions
except requests.RequestException:
guild["approx_member_count"] = "N/A"
else:
guild["approx_member_count"] = "N/A" # Fallback if unable to fetch
@@ -309,31 +346,38 @@ def guild_settings(guild_id):
flash("You are not logged in.")
return redirect(url_for("login"))
bot_headers = {"Authorization": f"Bot {DISCORD_BOT_TOKEN}"}
guild_info_response = requests.get(
f"{DISCORD_API_BASE_URL}/guilds/{guild_id}?with_counts=true",
headers=bot_headers,
)
if guild_info_response.status_code != 200:
flash("Failed to retrieve guild information (bot may not be in this guild).")
if not DISCORD_BOT_TOKEN:
flash("Bot token not configured on server; cannot fetch guild info.")
return redirect(url_for("home"))
guild_info = guild_info_response.json()
bot_headers = {"Authorization": f"Bot {DISCORD_BOT_TOKEN}"}
try:
guild_info_response = requests.get(
f"{DISCORD_API_BASE_URL}/guilds/{guild_id}?with_counts=true",
headers=bot_headers,
timeout=REQUEST_TIMEOUT,
)
guild_info_response.raise_for_status()
guild_info = guild_info_response.json()
except requests.RequestException:
flash("Failed to retrieve guild information (bot may not be in this guild or network error).")
return redirect(url_for("home"))
owner_id = guild_info.get("owner_id", "N/A")
member_count = guild_info.get("approximate_member_count", "N/A")
# Fetch owner's username using the bot token
owner_name = "Unknown"
if owner_id != "N/A":
owner_response = requests.get(
f"{DISCORD_API_BASE_URL}/users/{owner_id}",
headers=bot_headers,
)
if owner_response.status_code == 200:
try:
owner_response = requests.get(
f"{DISCORD_API_BASE_URL}/users/{owner_id}",
headers=bot_headers,
timeout=REQUEST_TIMEOUT,
)
owner_response.raise_for_status()
owner_data = owner_response.json()
owner_name = f"{owner_data.get('username', 'Unknown')}#{owner_data.get('discriminator', '0000')}"
else:
except requests.RequestException:
owner_name = owner_id # fallback to ID if fetch fails
json_formatted_str = json.dumps(guild_info, indent=2)
@@ -360,4 +404,6 @@ def profile():
if __name__ == "__main__":
app.run(debug=True, port=5000, host="0.0.0.0")
# Running via the Flask development server for local testing.
# For production, run under a WSGI server (gunicorn, waitress, etc.).
app.run(debug=True, port=5000, host="0.0.0.0", use_reloader=False)