import sys import os if __package__ is None or __package__ == "": # Running as __main__ (e.g. python web/app.py) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from utils.sql_commands import DatabaseManager, initialize_database else: # Imported as a module (e.g. from bot.py) from utils.sql_commands import DatabaseManager, initialize_database import os import requests from flask import Flask, redirect, url_for, session, request, render_template, flash from dotenv import load_dotenv from collections import defaultdict import datetime import json load_dotenv() app = Flask(__name__) # 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 DISCORD_CLIENT_ID = os.getenv("DISCORD_CLIENT_ID") DISCORD_CLIENT_SECRET = os.getenv("DISCORD_CLIENT_SECRET") 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.") # OAuth2 login route @app.route("/login") def login(): discord_auth_url = ( f"{DISCORD_API_BASE_URL}/oauth2/authorize" f"?client_id={DISCORD_CLIENT_ID}&redirect_uri={DISCORD_REDIRECT_URI}" f"&response_type=code&scope=identify%20guilds%20applications.commands%20bot" ) return redirect(discord_auth_url) def can_add_bot(guild): # Check if the user has permission to manage the bot permissions = guild.get( "permissions", 0 ) # Ensure permissions are included in the guild data # Bitwise operation for managing server (assuming manage_server is 0x20) return (permissions & 0x20) != 0 # OAuth2 callback route @app.route("/callback") def callback(): code = request.args.get("code") if not code: flash("Authorization code was not provided.") return redirect(url_for("home")) data = { "client_id": DISCORD_CLIENT_ID, "client_secret": DISCORD_CLIENT_SECRET, "grant_type": "authorization_code", "code": code, "redirect_uri": DISCORD_REDIRECT_URI, } headers = {"Content-Type": "application/x-www-form-urlencoded"} 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.") return redirect(url_for("home")) access_token = response.json().get("access_token") if not access_token: flash("Access token missing in the response.") return redirect(url_for("home")) # Store access token in session for later use session["access_token"] = access_token # Fetch user info 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 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 session["guilds"] = guilds # Store guilds in session # Fetch member count and permissions for each guild for guild in guilds: guild_id = guild["id"] 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 # Filter guilds where user can add bots guilds_with_bot_permission = [guild for guild in guilds if can_add_bot(guild)] session["guilds_with_bot_permission"] = guilds_with_bot_permission else: flash("Failed to retrieve guilds from Discord.") return redirect(url_for("home")) # Logout route @app.route("/logout") def logout(): session.pop("user", None) session.pop("guilds", None) session.pop("access_token", None) # Ensure access token is also cleared session.pop("guilds_with_bot_permission", None) flash("You have been logged out.") return redirect(url_for("home")) @app.route("/main") def main_page(): return render_template("main.html") # Update home route to display user info @app.route("/") def home(): user = session.get("user") if not user: return redirect( url_for("main_page") ) # Redirect to the main page if not logged in guilds_with_permission = session.get("guilds_with_bot_permission", []) user_info = { "id": user.get("id", "N/A"), "username": user.get("username", "Unknown"), "discriminator": user.get("discriminator", "0000"), "avatar": user.get("avatar", None), "servers": len(guilds_with_permission), } return render_template( "home.html", user=user_info, stats=user_info, guilds=guilds_with_permission ) def get_user_balance(user_id): result = db.fetch_one("SELECT BANK, WALLET FROM economy WHERE ID = %s", (user_id,)) print(result) bank_balance = result.get("BANK", 0) if result else 0 wallet_balance = result.get("WALLET", 0) if result else 0 return { "bank": bank_balance, "wallet": wallet_balance, } @app.route("/wallet") def wallet(): user = session.get("user") if not user: flash("You are not logged in.") return redirect(url_for("login")) user_balance = get_user_balance(user["id"]) return render_template("wallet.html", user=user, balance=user_balance) def get_user_transactions(user_id, sort_by="date", order="asc"): valid_sort_fields = { "date": "TIME", "amount": "amount", } sort_column = valid_sort_fields.get(sort_by, "TIME") sort_order = "ASC" if order == "asc" else "DESC" query = f"SELECT * FROM transactions WHERE USERID = %s ORDER BY {sort_column} {sort_order}" transactions = db.fetch_all(query, (user_id,)) return transactions if transactions else [] @app.route("/transactions") def transactions(): user = session.get("user") if not user: flash("You are not logged in.") return redirect(url_for("login")) sort_by = request.args.get("sort", "date") order = request.args.get("order", "asc") user_transactions = get_user_transactions(str(user["id"]), sort_by, order) daily_totals = defaultdict(float) for transaction in user_transactions: time_value = transaction["TIME"] if isinstance(time_value, str): try: time_value = datetime.datetime.fromisoformat(time_value) except Exception: try: time_value = datetime.datetime.strptime( time_value, "%Y-%m-%d %H:%M:%S" ) except Exception: continue # skip if can't parse date = time_value.date() amount = transaction["amount"] daily_totals[date] += float(amount) dates = list(daily_totals.keys()) totals = list(daily_totals.values()) return render_template( "transactions.html", user=user, transactions=user_transactions, dates=dates, totals=totals, sort_by=sort_by, order=order, ) @app.route("/add_command", methods=["GET", "POST"]) def add_command(): user = session.get("user") if not user: flash("You are not logged in.") return redirect(url_for("login")) if request.method == "POST": guild_id = request.form.get("guild_id") command_name = request.form.get("command_name") response_text = request.form.get("response") # Save command to the database db.execute_query( "INSERT INTO custom_commands (GUILDID, COMMANDNAME, RESPONSE) VALUES (%s, %s, %s)", (guild_id, command_name, response_text), ) flash("Custom command created successfully!") return redirect(url_for("home")) return render_template("add_command.html") @app.route("/commands") def list_commands(): user = session.get("user") if not user: flash("You are not logged in.") return redirect(url_for("login")) guild_id = request.args.get("guild_id") commands = [] if guild_id: commands = db.fetch_all( "SELECT * FROM custom_commands WHERE GUILDID = %s", (guild_id,) ) return render_template("list_commands.html", commands=commands) @app.route("/delete_command/", methods=["POST"]) def delete_command(command_id): user = session.get("user") if not user: flash("You are not logged in.") return redirect(url_for("login")) db.execute_query("DELETE FROM custom_commands WHERE ID = %s", (command_id,)) flash("Custom command deleted successfully!") return redirect(url_for("list_commands")) @app.route("/guild//settings", methods=["GET", "POST"]) def guild_settings(guild_id): user = session.get("user") if not user: flash("You are not logged in.") return redirect(url_for("login")) if not DISCORD_BOT_TOKEN: flash("Bot token not configured on server; cannot fetch guild info.") return redirect(url_for("home")) 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": 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')}" except requests.RequestException: owner_name = owner_id # fallback to ID if fetch fails json_formatted_str = json.dumps(guild_info, indent=2) print(json_formatted_str) return render_template( "guild_settings.html", user=user, guild=guild_info, owner_name=owner_name, member_count=member_count, ) @app.route("/profile") def profile(): user = session.get("user") if not user: flash("Please log in to view your profile.") return redirect(url_for("login")) # Example: Fetch more user data if needed # user_data = db.fetch_one("SELECT ... FROM ... WHERE ID = %s", (user["id"],)) return render_template("profile.html", user=user) if __name__ == "__main__": # 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)