be89cc3acd
- Removed the old npc_memory.db file. - Updated time.txt with a new timestamp. - Refactored transaction recording in bank_functions.py to use parameterized queries. - Enhanced DatabaseManager in sql_commands.py to support singleton pattern and improved table creation logic. - Added methods for sanitizing SQL identifiers and parsing insert columns for upsert operations. - Improved error handling and connection management in execute_query, fetch_one, fetch_all, and fetch_as_dataframe methods. - Introduced a new bootstrap_database.py script for initializing the database schema. - Updated app.py to use the new initialize_database function for database management.
364 lines
12 KiB
Python
Executable File
364 lines
12 KiB
Python
Executable File
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__)
|
|
app.secret_key = os.getenv("SECRET_KEY")
|
|
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")
|
|
|
|
# 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"}
|
|
response = requests.post(
|
|
f"{DISCORD_API_BASE_URL}/oauth2/token", data=data, headers=headers
|
|
)
|
|
|
|
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
|
|
user_data = requests.get(
|
|
f"{DISCORD_API_BASE_URL}/users/@me",
|
|
headers={"Authorization": f"Bearer {access_token}"},
|
|
).json()
|
|
|
|
# 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}"},
|
|
)
|
|
|
|
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"]
|
|
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:
|
|
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
|
|
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/<int:command_id>", 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/<guild_id>/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"))
|
|
|
|
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).")
|
|
return redirect(url_for("home"))
|
|
|
|
guild_info = guild_info_response.json()
|
|
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:
|
|
owner_data = owner_response.json()
|
|
owner_name = f"{owner_data.get('username', 'Unknown')}#{owner_data.get('discriminator', '0000')}"
|
|
else:
|
|
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__":
|
|
app.run(debug=True, port=5000, host="0.0.0.0")
|