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:
+79
-33
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user