1536 lines
56 KiB
Python
1536 lines
56 KiB
Python
from PIL import Image
|
|
import matplotlib
|
|
matplotlib.use('Agg')
|
|
import matplotlib.pyplot as plt
|
|
from io import BytesIO
|
|
import matplotlib.dates as mdates
|
|
from flask import Flask, request, render_template, redirect, url_for, session, jsonify, Response, send_file
|
|
from flask_socketio import SocketIO, join_room, leave_room, emit
|
|
from pymongo import MongoClient
|
|
from bson.objectid import ObjectId
|
|
import uuid
|
|
import datetime
|
|
from flask_wtf.csrf import CSRFProtect
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
import subprocess
|
|
import signal
|
|
import threading
|
|
import psutil
|
|
import time
|
|
import json
|
|
import requests
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
|
|
app = Flask(__name__, static_folder='/var/www/admin_website/static', template_folder='/var/www/admin_website/templates')
|
|
app.secret_key = os.urandom(24)
|
|
socketio = SocketIO(app, cors_allowed_origins="*", async_mode="threading")
|
|
|
|
csrf = CSRFProtect(app)
|
|
|
|
client = MongoClient("mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/")
|
|
MONGO_URI = "mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/"
|
|
db = client["moonlab"]
|
|
collection = db["anti_cheat"]
|
|
accounts_collection = db["website_accounts"]
|
|
delete_requests_collection = db["delete_requests"]
|
|
logs_collection = db["logs"]
|
|
user_ages_collection = db["user_ages"]
|
|
projects_collection = db["projects"]
|
|
changelogs_collection = db["changelogs"]
|
|
appointments_collection = db["appointments"]
|
|
scpsl_playerdata_collection = db["scpsl-playerdata"]
|
|
|
|
ETS2_DB_NAME = "ets2_license_db"
|
|
ETS2_COLLECTION_NAME = "licenses"
|
|
ets2_db = client[ETS2_DB_NAME]
|
|
ets2_licenses = ets2_db[ETS2_COLLECTION_NAME]
|
|
|
|
SCP_SL_API_URL = "https://api.scplist.kr/api/servers/85734"
|
|
|
|
app.config.update(
|
|
SESSION_COOKIE_HTTPONLY=True,
|
|
SESSION_COOKIE_SECURE=True,
|
|
SESSION_COOKIE_SAMESITE='Lax',
|
|
REMEMBER_COOKIE_HTTPONLY=True,
|
|
REMEMBER_COOKIE_SECURE=True,
|
|
REMEMBER_COOKIE_SAMESITE='Lax'
|
|
)
|
|
|
|
if accounts_collection.count_documents({}) == 0:
|
|
accounts_collection.insert_one({
|
|
"username": "Admin",
|
|
"display_name": "Admin",
|
|
"password": generate_password_hash("1812"),
|
|
"permissions": ["user_management", "permission_management", "delete_requests", "logs", "create_license_key",
|
|
"view_license_keys", "create_event_feedback", "view_event_feedback", "verify_age",
|
|
"project_manager", "ip_checker", "changelogs"]
|
|
})
|
|
|
|
|
|
def generate_license_key():
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
def add_license_key_to_db(license_key):
|
|
if collection.find_one({"license_key": license_key}):
|
|
return False
|
|
else:
|
|
collection.insert_one({"license_key": license_key, "status": "unused"})
|
|
return True
|
|
|
|
|
|
def add_log(message):
|
|
logs_collection.insert_one({"timestamp": datetime.now(), "message": message})
|
|
|
|
|
|
def stream_logs(process):
|
|
def generate():
|
|
for line in iter(process.stdout.readline, b''):
|
|
yield line.decode('utf-8')
|
|
|
|
return Response(generate(), mimetype='text/plain')
|
|
|
|
|
|
def load_bot_config():
|
|
try:
|
|
with open("bot_config.json", "r") as file:
|
|
return json.load(file)
|
|
except FileNotFoundError:
|
|
return {}
|
|
|
|
|
|
def save_bot_config(config):
|
|
with open("bot_config.json", "w") as file:
|
|
json.dump(config, file, indent=4)
|
|
|
|
|
|
bot_config = load_bot_config()
|
|
|
|
|
|
@app.route("/website.py", methods=["GET"])
|
|
def deny_access():
|
|
return "Access denied", 403
|
|
|
|
|
|
@app.route("/", methods=["GET", "POST"])
|
|
def login():
|
|
error = None
|
|
if request.method == "POST":
|
|
username = request.form["username"]
|
|
password = request.form["password"]
|
|
remember_me = "remember_me" in request.form
|
|
|
|
user = accounts_collection.find_one({"username": username})
|
|
if user and check_password_hash(user["password"], password):
|
|
session["user_id"] = str(user["_id"])
|
|
session["username"] = user["username"]
|
|
session["display_name"] = user.get("display_name", user["username"])
|
|
session["permissions"] = user.get("permissions", [])
|
|
add_log(f"{username} hat sich eingeloggt")
|
|
|
|
if remember_me:
|
|
session.permanent = True
|
|
|
|
return redirect(url_for("dashboard"))
|
|
else:
|
|
error = "Du hast ein(en) falsches Passwort/Benutzernamen eingegeben"
|
|
return render_template("login.html", error=error)
|
|
|
|
|
|
@app.route("/dashboard")
|
|
def dashboard():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
user = accounts_collection.find_one({"_id": ObjectId(session["user_id"])})
|
|
if not user:
|
|
return redirect(url_for("login"))
|
|
|
|
session["permissions"] = user.get("permissions", [])
|
|
|
|
if "Style" not in user:
|
|
accounts_collection.update_one({"_id": ObjectId(session["user_id"])}, {"$set": {"Style": "lightmode"}})
|
|
session["theme"] = "lightmode"
|
|
else:
|
|
session["theme"] = user["Style"]
|
|
|
|
if session["theme"] == "darkmode":
|
|
return render_template("dashboard_dark.html")
|
|
else:
|
|
return render_template("dashboard_light.html")
|
|
|
|
|
|
@app.route('/view_changelogs', methods=['GET'])
|
|
def view_changelogs():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
changelogs = [
|
|
{"text": changelog["text"], "timestamp": changelog["timestamp"]}
|
|
for changelog in changelogs_collection.find().sort("timestamp", -1)
|
|
]
|
|
return render_template("view_changelogs.html", changelogs=changelogs)
|
|
|
|
|
|
@app.route("/user_management", methods=["GET", "POST"])
|
|
def user_management():
|
|
if "username" not in session or "user_management" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
users = accounts_collection.find()
|
|
return render_template("user_management.html", users=users)
|
|
|
|
|
|
@app.route("/add_user", methods=["POST"])
|
|
def add_user():
|
|
if "username" not in session or "user_management" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
new_username = request.form["username"]
|
|
new_display_name = request.form["display_name"]
|
|
new_password = request.form["password"]
|
|
new_permissions = request.form.getlist("permissions")
|
|
|
|
if accounts_collection.find_one({"username": new_username}):
|
|
return "Benutzername existiert bereits", 400
|
|
|
|
accounts_collection.insert_one({
|
|
"username": new_username,
|
|
"display_name": new_display_name,
|
|
"password": generate_password_hash(new_password),
|
|
"permissions": new_permissions
|
|
})
|
|
add_log(f"{session['username']} created or modified user {new_username}")
|
|
return redirect(url_for("user_management"))
|
|
|
|
|
|
@app.route("/change_permissions/<user_id>", methods=["POST"])
|
|
def change_permissions(user_id):
|
|
if "username" not in session or "permission_management" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
user = accounts_collection.find_one({"_id": ObjectId(user_id)})
|
|
if not user:
|
|
return "User not found", 404
|
|
|
|
if "remove_permission" in request.form:
|
|
permission_to_remove = request.form["remove_permission"]
|
|
accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$pull": {"permissions": permission_to_remove}})
|
|
add_log(f"{session['username']} removed permission {permission_to_remove} from user {user['username']}")
|
|
|
|
if "add_permission" in request.form:
|
|
permission_to_add = request.form["add_permission"]
|
|
accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$addToSet": {"permissions": permission_to_add}})
|
|
add_log(f"{session['username']} added permission {permission_to_add} to user {user['username']}")
|
|
|
|
if str(user["_id"]) == session.get("user_id"):
|
|
updated_user = accounts_collection.find_one({"_id": ObjectId(user_id)})
|
|
session["permissions"] = updated_user.get("permissions", [])
|
|
|
|
return redirect(url_for("user_management"))
|
|
|
|
|
|
@app.route("/delete_user/<user_id>", methods=["POST"])
|
|
def delete_user(user_id):
|
|
if "username" not in session or "user_management" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
user = accounts_collection.find_one({"_id": ObjectId(user_id)})
|
|
if user:
|
|
accounts_collection.delete_one({"_id": ObjectId(user_id)})
|
|
add_log(f"{session['username']} deleted user {user['username']}")
|
|
return redirect(url_for("user_management"))
|
|
|
|
|
|
UPLOAD_FOLDER = "/var/www/admin_website/static/pfps"
|
|
ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
|
|
|
|
|
|
def allowed_file(filename):
|
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
|
|
|
|
@app.route("/edit_user/<user_id>", methods=["GET", "POST"])
|
|
def edit_user(user_id):
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
user = accounts_collection.find_one({"_id": ObjectId(user_id)})
|
|
|
|
if "permission_management" not in session["permissions"] and session["user_id"] != str(user["_id"]):
|
|
return "Access denied", 403
|
|
|
|
if request.method == "POST":
|
|
new_display_name = request.form.get("display_name")
|
|
new_password = request.form.get("password")
|
|
profile_pic = request.files.get("pfp")
|
|
|
|
update_data = {"display_name": new_display_name}
|
|
|
|
pfp_uploaded = False
|
|
pfp_url = None
|
|
|
|
if new_password:
|
|
update_data["password"] = generate_password_hash(new_password, method='scrypt')
|
|
|
|
if profile_pic and profile_pic.filename != "":
|
|
ext = profile_pic.filename.rsplit('.', 1)[-1].lower()
|
|
|
|
if ext in ["png", "jpg", "jpeg"]:
|
|
filename = f"{user_id}.png"
|
|
upload_folder = "/var/www/admin_website/static/pfps"
|
|
os.makedirs(upload_folder, exist_ok=True)
|
|
save_path = os.path.join(upload_folder, filename)
|
|
try:
|
|
img = Image.open(profile_pic.stream)
|
|
if img.mode == 'RGBA' or (img.mode == 'P' and 'transparency' in img.info):
|
|
img.save(save_path, "PNG")
|
|
else:
|
|
img = img.convert("RGB")
|
|
img.save(save_path, "PNG")
|
|
website_data = user.get("website_accounts", {})
|
|
website_data["pfp_last_updated"] = int(time.time())
|
|
website_data["pfp_filename"] = filename
|
|
update_data["website_accounts"] = website_data
|
|
pfp_uploaded = True
|
|
pfp_url = f"/static/pfps/{filename}"
|
|
except Exception as e:
|
|
print(f"Fehler beim Speichern oder Konvertieren des Profilbildes: {e}")
|
|
else:
|
|
print(f"Unerlaubter Dateityp für Profilbild: {ext}")
|
|
|
|
accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$set": update_data})
|
|
|
|
if user_id == session.get("user_id"):
|
|
session["display_name"] = new_display_name
|
|
if pfp_uploaded:
|
|
session["pfp_last_updated"] = update_data["website_accounts"]["pfp_last_updated"]
|
|
|
|
if pfp_uploaded:
|
|
return jsonify({"success": True, "message": "Profilbild erfolgreich aktualisiert!", "pfp_url": pfp_url})
|
|
else:
|
|
return jsonify({"success": True, "message": "Profil erfolgreich aktualisiert!", "pfp_url": None})
|
|
|
|
return render_template("edit_user.html", user=user)
|
|
|
|
|
|
@app.route('/get_data', methods=['GET'])
|
|
def get_data():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
data = collection.find()
|
|
result = []
|
|
for doc in data:
|
|
if 'ingame_name' in doc and doc['ingame_name']:
|
|
doc['_id'] = str(doc['_id'])
|
|
result.append(doc)
|
|
return render_template("get_data.html", data=result)
|
|
|
|
|
|
@app.route('/license=<license_key>', methods=['GET'])
|
|
def view_license(license_key):
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
license_data = collection.find_one({"license_key": license_key})
|
|
if license_data:
|
|
license_data['_id'] = str(license_data['_id'])
|
|
return render_template("view_license.html", license_data=license_data)
|
|
else:
|
|
return "Lizenzschlüssel nicht gefunden", 404
|
|
|
|
|
|
@app.route('/delete_license/<license_key>', methods=['POST'])
|
|
def delete_license(license_key):
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
collection.delete_one({"license_key": license_key})
|
|
add_log(f"{session['username']} hat Lizenzschlüssel {license_key} gelöscht")
|
|
return redirect(url_for("get_data"))
|
|
|
|
|
|
@app.route('/request_delete_license/<license_key>', methods=['POST'])
|
|
def request_delete_license(license_key):
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
delete_requests_collection.insert_one({"license_key": license_key, "username": session["username"]})
|
|
add_log(f"{session['username']} hat eine Delete-Request für Lizenzschlüssel {license_key} gestellt")
|
|
return redirect(url_for("get_data"))
|
|
|
|
|
|
@app.route('/delete_requests', methods=['GET'])
|
|
def delete_requests():
|
|
if "username" not in session or "delete_requests" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
delete_requests = list(delete_requests_collection.find())
|
|
return render_template("delete_request.html", delete_requests=delete_requests)
|
|
|
|
|
|
@app.route('/approve_delete_request/<request_id>', methods=['POST'])
|
|
def approve_delete_request(request_id):
|
|
if "username" not in session or "delete_requests" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
request = delete_requests_collection.find_one({"_id": ObjectId(request_id)})
|
|
if request:
|
|
collection.delete_one({"license_key": request["license_key"]})
|
|
delete_requests_collection.delete_one({"_id": ObjectId(request_id)})
|
|
add_log(f"{session['username']} hat die Delete-Request für Lizenzschlüssel {request['license_key']} genehmigt")
|
|
return redirect(url_for("delete_requests"))
|
|
|
|
|
|
@app.route('/deny_delete_request/<request_id>', methods=['POST'])
|
|
def deny_delete_request(request_id):
|
|
if "username" not in session or "delete_requests" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
delete_requests_collection.delete_one({"_id": ObjectId(request_id)})
|
|
add_log(f"{session['username']} hat die Delete-Request für Lizenzschlüssel {request_id} abgelehnt")
|
|
return redirect(url_for("delete_requests"))
|
|
|
|
|
|
@app.route('/add_data', methods=['POST'])
|
|
def add_data():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
new_data = request.get_json()
|
|
|
|
result = collection.insert_one(new_data)
|
|
|
|
return jsonify({"message": "Daten erfolgreich hinzugefügt", "inserted_id": str(result.inserted_id)})
|
|
|
|
|
|
@app.route('/create_license_key', methods=['GET', 'POST'])
|
|
def create_license_key():
|
|
if "username" not in session or "create_license_key" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
if request.method == "POST":
|
|
license_key = generate_license_key()
|
|
if add_license_key_to_db(license_key):
|
|
add_log(f"{session['username']} hat Lizenzschlüssel {license_key} erstellt")
|
|
return render_template("create_license_key.html", license_key=license_key)
|
|
else:
|
|
return render_template("create_license_key.html", error="Fehler beim Erstellen des Lizenzschlüssels")
|
|
return render_template("create_license_key.html")
|
|
|
|
|
|
@app.route('/database_entries', methods=['GET'])
|
|
def database_entries():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
data = collection.find()
|
|
result = []
|
|
for doc in data:
|
|
doc['_id'] = str(doc['_id'])
|
|
result.append(doc)
|
|
return render_template("view_license.html", data=result)
|
|
|
|
|
|
@app.route('/license_keys', methods=['GET'])
|
|
def license_keys():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
return render_template("license_keys.html")
|
|
|
|
|
|
@app.route('/search_license_key', methods=['GET'])
|
|
def search_license_key():
|
|
license_key = request.args.get('license_key')
|
|
license_data = collection.find_one({"license_key": license_key})
|
|
if license_data:
|
|
return jsonify({"found": True})
|
|
else:
|
|
return jsonify({"found": False})
|
|
|
|
|
|
@app.route('/logs', methods=['GET'])
|
|
def logs():
|
|
if "username" not in session or "logs" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
logs = list(logs_collection.find().sort("timestamp", -1))
|
|
return render_template("logs.html", logs=logs)
|
|
|
|
|
|
@app.route('/event_feedback', methods=['GET', 'POST'])
|
|
def event_feedback():
|
|
if "username" not in session or "create_event_feedback" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
if request.method == "POST":
|
|
event_name = request.form["event_name"]
|
|
if event_name == "Anderes":
|
|
event_name = request.form["other_event_name"]
|
|
event_progress = request.form["event_progress"]
|
|
if event_progress == "Anderes":
|
|
event_progress = request.form["other_event_progress"]
|
|
event_end = request.form["event_end"]
|
|
if event_end == "Anderes":
|
|
event_end = request.form["other_event_end"]
|
|
community_feedback = request.form["community_feedback"]
|
|
if community_feedback == "Anderes":
|
|
community_feedback = request.form["other_community_feedback"]
|
|
improvement_suggestions = request.form["improvement_suggestions"]
|
|
feedback_data = {
|
|
"event_name": event_name,
|
|
"event_progress": event_progress,
|
|
"event_end": event_end,
|
|
"community_feedback": community_feedback,
|
|
"improvement_suggestions": improvement_suggestions,
|
|
"timestamp": datetime.now(),
|
|
"username": session["username"]
|
|
}
|
|
db.event_feedback.insert_one(feedback_data)
|
|
add_log(f"{session['username']} hat Feedback für das Event {event_name} abgegeben")
|
|
return redirect(url_for("dashboard"))
|
|
return render_template("event_feedback.html")
|
|
|
|
|
|
@app.route('/event=<event_id>', methods=['GET'])
|
|
def view_event_feedback(event_id):
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
feedback = db.event_feedback.find_one({"_id": ObjectId(event_id)})
|
|
if feedback:
|
|
feedback['_id'] = str(feedback['_id'])
|
|
return render_template("view_event_feedback.html", feedback=feedback)
|
|
else:
|
|
return "Event-Feedback nicht gefunden", 404
|
|
|
|
|
|
@app.route('/view_event_feedbacks', methods=['GET'])
|
|
def view_event_feedbacks():
|
|
if "username" not in session or "view_event_feedback" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
feedbacks = list(db.event_feedback.find().sort("timestamp", -1))
|
|
return render_template("view_event_feedbacks.html", feedbacks=feedbacks)
|
|
|
|
|
|
@app.route("/events", methods=["GET", "POST"])
|
|
@csrf.exempt
|
|
def events():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
if request.method == "POST":
|
|
event_name = request.form["event_name"]
|
|
community_feedback = request.form["community_feedback"]
|
|
players = int(request.form["players"])
|
|
time = request.form["time"]
|
|
weekday = request.form["weekday"]
|
|
top = 1 if community_feedback == "top" else 0
|
|
flop = 1 if community_feedback == "flop" else 0
|
|
etwas = 1 if community_feedback == "etwas" else 0
|
|
rating = top - flop + etwas
|
|
|
|
event = db.events.find_one({"name": event_name, "time": time, "weekday": weekday})
|
|
if event:
|
|
db.events.update_one(
|
|
{"_id": event["_id"]},
|
|
{"$inc": {"top": top, "flop": flop, "etwas": etwas, "rating": rating}}
|
|
)
|
|
add_log(f"{session['username']} updated event {event_name} with rating {rating}")
|
|
else:
|
|
db.events.insert_one({
|
|
"name": event_name,
|
|
"players": players,
|
|
"time": time,
|
|
"weekday": weekday,
|
|
"top": top,
|
|
"flop": flop,
|
|
"etwas": etwas,
|
|
"rating": rating,
|
|
"timestamp": datetime.now()
|
|
})
|
|
add_log(f"{session['username']} added event {event_name} with rating {rating}")
|
|
|
|
events = list(db.events.find().sort("timestamp", -1))
|
|
best_event = None
|
|
best_events = None
|
|
if request.args.get("calculate_best"):
|
|
current_players = int(request.args.get("current_players"))
|
|
current_time = request.args.get("current_time")
|
|
current_weekday = request.args.get("current_weekday")
|
|
event_type = request.args.get("event_type", None)
|
|
best_events = calculate_best_events(current_players, current_time, current_weekday, event_type)
|
|
if best_events:
|
|
best_event = best_events[0]
|
|
|
|
return render_template("events.html", events=events, best_event=best_event)
|
|
|
|
|
|
def calculate_best_events(current_players, current_time, current_weekday, event_type=None):
|
|
events = list(db.events.find())
|
|
scored_events = []
|
|
for event in events:
|
|
similarity_score = 0
|
|
similarity_score -= abs(event['players'] - current_players) * 0.5
|
|
if event['weekday'] != current_weekday:
|
|
similarity_score -= 1
|
|
similarity_score -= abs(int(event['time'].split(':')[0]) - int(current_time.split(':')[0])) * 0.2
|
|
similarity_score += (event['rating'] * 1.0)
|
|
if event_type and event.get('type') == event_type:
|
|
similarity_score += 0.1
|
|
scored_events.append((similarity_score, event))
|
|
scored_events.sort(reverse=True, key=lambda x: x[0])
|
|
return [e for _, e in scored_events[:5]]
|
|
|
|
|
|
@app.route("/dc_bots", methods=["GET"])
|
|
def dc_bots():
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
bots = list(db.dc_bots.find())
|
|
return render_template("dc_bots.html", bots=bots)
|
|
|
|
|
|
@app.route("/add_dc_bot", methods=["POST"])
|
|
def add_dc_bot():
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
bot_name = request.form["bot_name"]
|
|
bot_directory = request.form["bot_directory"]
|
|
db.dc_bots.insert_one({"name": bot_name, "directory": bot_directory})
|
|
add_log(f"{session['username']} added bot {bot_name}")
|
|
return redirect(url_for("dc_bots"))
|
|
|
|
|
|
@app.route("/upload_bot_version/<bot_name>", methods=["POST"])
|
|
def upload_bot_version(bot_name):
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
bot = db.dc_bots.find_one({"name": bot_name})
|
|
if not bot:
|
|
return f"Bot {bot_name} nicht gefunden", 404
|
|
|
|
if "bot_file" not in request.files:
|
|
return "Keine Datei hochgeladen", 400
|
|
|
|
bot_file = request.files["bot_file"]
|
|
if bot_file.filename == "":
|
|
return "Keine Datei ausgewählt", 400
|
|
|
|
version = bot.get("version", "1.0")
|
|
major, minor = map(int, version.split("."))
|
|
new_version = f"{major}.{minor + 1}"
|
|
|
|
base_directory_name = bot['directory'].rsplit('_v', 1)[0] if '_v' in bot['directory'] else bot['directory']
|
|
bot_directory_with_version = f"{base_directory_name}_v{new_version}"
|
|
|
|
os.makedirs(bot_directory_with_version, exist_ok=True)
|
|
bot_file.save(os.path.join(bot_directory_with_version, bot_file.filename))
|
|
|
|
db.dc_bots.update_one({"name": bot_name},
|
|
{"$set": {"directory": bot_directory_with_version, "version": new_version}})
|
|
add_log(f"{session['username']} uploaded new version {new_version} for bot {bot_name}")
|
|
|
|
return redirect(url_for("dc_bots"))
|
|
|
|
|
|
@app.route("/revert_bot_version/<bot_name>/<version>", methods=["POST"])
|
|
def revert_bot_version(bot_name, version):
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
bot = db.dc_bots.find_one({"name": bot_name})
|
|
if not bot:
|
|
return f"Bot {bot_name} nicht gefunden", 404
|
|
|
|
base_directory_name = bot['directory'].rsplit('_v', 1)[0] if '_v' in bot['directory'] else bot['directory']
|
|
bot_directory = f"{base_directory_name}_v{version}"
|
|
|
|
if not os.path.exists(bot_directory):
|
|
return f"Version {version} für Bot {bot_name} nicht gefunden", 404
|
|
|
|
db.dc_bots.update_one({"name": bot_name}, {"$set": {"directory": bot_directory, "version": version}})
|
|
add_log(f"{session['username']} reverted bot {bot_name} to version {version}")
|
|
|
|
return redirect(url_for("dc_bots"))
|
|
|
|
|
|
def initialize_bot_versions():
|
|
bots = db.dc_bots.find({"version": {"$exists": False}})
|
|
for bot in bots:
|
|
db.dc_bots.update_one({"_id": bot["_id"]}, {"$set": {"version": "1.0"}})
|
|
add_log(f"Initialized version 1.0 for bot {bot['name']}")
|
|
|
|
|
|
initialize_bot_versions()
|
|
|
|
if db.events.count_documents({}) == 0:
|
|
db.events.insert_one({
|
|
"name": "Sample Event",
|
|
"top": 0,
|
|
"flop": 0,
|
|
"rating": 0,
|
|
"timestamp": datetime.now()
|
|
})
|
|
|
|
|
|
def save_logs_to_file(process, log_file_path):
|
|
try:
|
|
with open(log_file_path, "w") as log_file:
|
|
while True:
|
|
output = process.stdout.readline()
|
|
error = process.stderr.readline()
|
|
|
|
if output:
|
|
log_file.write(repr(output) + "\n")
|
|
log_file.flush()
|
|
print(output, end="")
|
|
if error:
|
|
log_file.write("[ERROR] " + repr(error) + "\n")
|
|
log_file.flush()
|
|
print("[ERROR] " + error, end="")
|
|
|
|
if process.poll() is not None:
|
|
for line in process.stdout.readlines():
|
|
log_file.write(repr(line) + "\n")
|
|
for line in process.stderr.readlines():
|
|
log_file.write("[ERROR] " + repr(line) + "\n")
|
|
break
|
|
|
|
time.sleep(0.5)
|
|
except Exception as e:
|
|
print(f"Fehler beim Speichern der Logs: {str(e)}")
|
|
|
|
|
|
@app.route("/turn_on_bot/<bot_name>", methods=["POST"])
|
|
def turn_on_bot(bot_name):
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
bot = db.dc_bots.find_one({"name": bot_name})
|
|
if bot:
|
|
if "pid" in bot and psutil.pid_exists(bot["pid"]):
|
|
add_log(f"Bot {bot_name} is already running with PID {bot['pid']}")
|
|
return f"Bot {bot_name} läuft bereits.", 400
|
|
|
|
bot_directory = bot["directory"]
|
|
try:
|
|
command = ["python", bot_directory]
|
|
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
|
|
log_file_path = f"/tmp/{bot_name}_log.txt"
|
|
db.dc_bots.update_one({"name": bot_name}, {
|
|
"$set": {
|
|
"pid": process.pid,
|
|
"log_file": log_file_path
|
|
}
|
|
})
|
|
|
|
threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()
|
|
|
|
bot_config[bot_name] = {"status": "on", "pid": process.pid}
|
|
save_bot_config(bot_config)
|
|
|
|
add_log(f"{session['username']} turned on {bot_name}")
|
|
except Exception as e:
|
|
add_log(f"Error starting bot {bot_name}: {str(e)}")
|
|
return f"Fehler beim Starten des Bots {bot_name}: {str(e)}", 500
|
|
else:
|
|
return f"Bot {bot_name} nicht gefunden", 404
|
|
|
|
return redirect(url_for("dc_bots"))
|
|
|
|
|
|
@app.route("/turn_off_bot/<bot_name>", methods=["POST"])
|
|
def turn_off_bot(bot_name):
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
bot = db.dc_bots.find_one({"name": bot_name})
|
|
if bot and "pid" in bot:
|
|
try:
|
|
pid = bot["pid"]
|
|
|
|
if psutil.pid_exists(pid):
|
|
os.kill(pid, signal.SIGTERM)
|
|
db.dc_bots.update_one({"name": bot_name}, {"$unset": {"pid": ""}})
|
|
|
|
bot_config[bot_name] = {"status": "off"}
|
|
save_bot_config(bot_config)
|
|
|
|
add_log(f"{session['username']} turned off {bot_name}")
|
|
else:
|
|
add_log(f"Process for bot {bot_name} does not exist")
|
|
db.dc_bots.update_one({"name": bot_name}, {"$unset": {"pid": ""}})
|
|
except Exception as e:
|
|
add_log(f"Error stopping bot {bot_name}: {str(e)}")
|
|
return f"Fehler beim Stoppen des Bots {bot_name}: {str(e)}", 500
|
|
else:
|
|
return f"Bot {bot_name} nicht gefunden oder kein aktiver Prozess vorhanden", 404
|
|
|
|
return redirect(url_for("dc_bots"))
|
|
|
|
|
|
@app.route("/turn_on_all_bots", methods=["POST"])
|
|
def turn_on_all_bots():
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
bots = list(db.dc_bots.find())
|
|
for bot in bots:
|
|
if "pid" not in bot or not psutil.pid_exists(bot["pid"]):
|
|
bot_directory = bot["directory"]
|
|
try:
|
|
command = ["python", bot_directory]
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
log_file_path = f"/tmp/{bot['name']}_log.txt"
|
|
db.dc_bots.update_one({"name": bot["name"]}, {
|
|
"$set": {
|
|
"pid": process.pid,
|
|
"log_file": log_file_path
|
|
}
|
|
})
|
|
threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()
|
|
|
|
bot_config[bot["name"]] = {"status": "on", "pid": process.pid}
|
|
save_bot_config(bot_config)
|
|
|
|
add_log(f"{session['username']} turned on {bot['name']}")
|
|
except Exception as e:
|
|
add_log(f"Error starting bot {bot['name']}: {str(e)}")
|
|
|
|
return redirect(url_for("dc_bots"))
|
|
|
|
|
|
@app.route("/turn_off_all_bots", methods=["POST"])
|
|
def turn_off_all_bots():
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
bots = list(db.dc_bots.find())
|
|
for bot in bots:
|
|
if "pid" in bot:
|
|
try:
|
|
pid = bot["pid"]
|
|
if psutil.pid_exists(pid):
|
|
os.kill(pid, signal.SIGTERM)
|
|
db.dc_bots.update_one({"name": bot["name"]}, {"$unset": {"pid": ""}})
|
|
|
|
bot_config[bot["name"]] = {"status": "off"}
|
|
save_bot_config(bot_config)
|
|
|
|
add_log(f"{session['username']} turned off {bot['name']}")
|
|
else:
|
|
add_log(f"Process for bot {bot['name']} does not exist")
|
|
db.dc_bots.update_one({"name": bot["name"]}, {"$unset": {"pid": ""}})
|
|
except Exception as e:
|
|
add_log(f"Error stopping bot {bot['name']}: {str(e)}")
|
|
|
|
return redirect(url_for("dc_bots"))
|
|
|
|
|
|
@app.route("/bot_logs/<bot_name>", methods=["GET"])
|
|
def bot_logs(bot_name):
|
|
if "username" not in session or "discord_bots" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
bot = db.dc_bots.find_one({"name": bot_name})
|
|
if bot and "log_file" in bot:
|
|
log_file_path = bot["log_file"]
|
|
|
|
try:
|
|
with open(log_file_path, "r") as log_file:
|
|
logs = log_file.readlines()
|
|
except FileNotFoundError:
|
|
logs = ["Keine Logs gefunden."]
|
|
|
|
return render_template("bot_logs.html", bot_name=bot_name, logs=logs)
|
|
else:
|
|
return f"Logs für Bot {bot_name} nicht gefunden", 404
|
|
|
|
|
|
def start_bots_on_server_start():
|
|
for bot_name, config in bot_config.items():
|
|
if config["status"] == "on":
|
|
bot = db.dc_bots.find_one({"name": bot_name})
|
|
if bot:
|
|
if "pid" in bot and psutil.pid_exists(bot["pid"]):
|
|
add_log(f"Bot {bot_name} is already running with PID {bot['pid']}")
|
|
continue
|
|
|
|
bot_directory = bot["directory"]
|
|
try:
|
|
command = ["python", bot_directory]
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
log_file_path = f"/tmp/{bot_name}_log.txt"
|
|
db.dc_bots.update_one({"name": bot_name}, {
|
|
"$set": {
|
|
"pid": process.pid,
|
|
"log_file": log_file_path
|
|
}
|
|
})
|
|
threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()
|
|
|
|
bot_config[bot_name] = {"status": "on", "pid": process.pid}
|
|
save_bot_config(bot_config)
|
|
|
|
add_log(f"Bot {bot_name} automatically started on server start")
|
|
except Exception as e:
|
|
add_log(f"Error starting bot {bot_name} on server start: {str(e)}")
|
|
|
|
|
|
start_bots_on_server_start()
|
|
|
|
|
|
@app.route("/verify_age", methods=["GET", "POST"])
|
|
@csrf.exempt
|
|
def verify_age():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
age = None
|
|
birthdate_dt = None
|
|
today = datetime.today()
|
|
if request.method == "POST":
|
|
birthdate = request.form["birthdate"]
|
|
steam_id = request.form["steam_id"]
|
|
ingame_name = request.form["ingame_name"]
|
|
|
|
birthdate_dt = datetime.strptime(birthdate, "%Y-%m-%d")
|
|
age = today.year - birthdate_dt.year - ((today.month, today.day) < (birthdate_dt.month, birthdate_dt.day))
|
|
|
|
user_ages_collection.insert_one({
|
|
"username": session["username"],
|
|
"birthdate": birthdate,
|
|
"age": age,
|
|
"steam_id": steam_id,
|
|
"ingame_name": ingame_name,
|
|
"timestamp": datetime.now()
|
|
})
|
|
|
|
search_query = request.args.get('search')
|
|
if search_query:
|
|
users = user_ages_collection.find({
|
|
"$or": [
|
|
{"ingame_name": {"$regex": search_query, "$options": "i"}},
|
|
{"steam_id": {"$regex": search_query, "$options": "i"}}
|
|
]
|
|
})
|
|
else:
|
|
users = user_ages_collection.find().limit(10)
|
|
|
|
return render_template("verify_age.html", age=age, birthdate_dt=birthdate_dt, today=today, users=users)
|
|
|
|
|
|
@app.route("/verify_age/<user_id>", methods=["GET"])
|
|
def view_user_age(user_id):
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
user = user_ages_collection.find_one({"_id": ObjectId(user_id)})
|
|
if user:
|
|
user['_id'] = str(user['_id'])
|
|
return render_template("view_user_age.html", user=user)
|
|
else:
|
|
return "Benutzer nicht gefunden", 404
|
|
|
|
|
|
@app.route('/logout')
|
|
def logout():
|
|
add_log(f"{session['username']} hat sich ausgeloggt")
|
|
session.pop("username", None)
|
|
session.pop("permissions", None)
|
|
session.pop("user_id", None)
|
|
return redirect(url_for("login"))
|
|
|
|
|
|
@app.route('/toggle-theme', methods=['POST'])
|
|
@csrf.exempt
|
|
def toggle_theme():
|
|
if "username" not in session:
|
|
return "Unauthorized", 401
|
|
|
|
user = accounts_collection.find_one({"_id": ObjectId(session["user_id"])})
|
|
if not user:
|
|
return "User not found", 404
|
|
|
|
current_theme = user.get("Style", "lightmode")
|
|
new_theme = "darkmode" if current_theme == "lightmode" else "lightmode"
|
|
|
|
accounts_collection.update_one({"_id": ObjectId(session["user_id"])}, {"$set": {"Style": new_theme}})
|
|
session["theme"] = new_theme
|
|
|
|
return "", 204
|
|
|
|
|
|
@app.route("/ip_checker", methods=["GET", "POST"])
|
|
def ip_checker():
|
|
if "username" not in session or "ip_checker" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
import ipaddress
|
|
|
|
ip_data = None
|
|
error = None
|
|
similar_ips = []
|
|
is_cheater_network = False
|
|
similar_cheater_ips = []
|
|
|
|
if request.method == "POST":
|
|
ip_address = request.form.get("ip_address")
|
|
is_cheater = request.form.get("is_cheater") == "yes"
|
|
steam_id = request.form.get("steam_id") if is_cheater else None
|
|
|
|
if not ip_address:
|
|
error = "Bitte eine IP-Adresse eingeben."
|
|
elif is_cheater and not steam_id:
|
|
error = "SteamID ist erforderlich, wenn die IP als Cheater markiert wird."
|
|
else:
|
|
try:
|
|
api_key = "015ada9472664329affb2f26d7036d47"
|
|
response = requests.get(f"https://vpnapi.io/api/{ip_address}?key={api_key}")
|
|
if response.status_code == 200:
|
|
ip_data = response.json()
|
|
|
|
if "security" not in ip_data or not isinstance(ip_data["security"], dict):
|
|
ip_data["security"] = {}
|
|
if "location" not in ip_data or not isinstance(ip_data["location"], dict):
|
|
ip_data["location"] = {}
|
|
if "network" not in ip_data or not isinstance(ip_data["network"], dict):
|
|
ip_data["network"] = {}
|
|
|
|
checked_network = None
|
|
if "network" in ip_data and "network" in ip_data["network"]:
|
|
cidr = str(ip_data["network"]["network"]).replace(" ", "")
|
|
try:
|
|
checked_network = ipaddress.ip_network(cidr, strict=False)
|
|
except Exception:
|
|
checked_network = None
|
|
|
|
cheater_ips = list(db.ip_infos.find({"is_cheater": True}))
|
|
for cheater in cheater_ips:
|
|
cheater_data = cheater.get("data", {})
|
|
cheater_network = None
|
|
if "network" in cheater_data and "network" in cheater_data["network"]:
|
|
cheater_cidr = str(cheater_data["network"]["network"]).replace(" ", "")
|
|
try:
|
|
cheater_network = ipaddress.ip_network(cheater_cidr, strict=False)
|
|
except Exception:
|
|
cheater_network = None
|
|
cheater_ip = cheater.get("ip")
|
|
if cheater_network and checked_network:
|
|
if checked_network.overlaps(cheater_network):
|
|
is_cheater_network = True
|
|
similar_cheater_ips.append(cheater)
|
|
if cheater_ip and checked_network:
|
|
try:
|
|
if ipaddress.ip_address(cheater_ip) in checked_network:
|
|
is_cheater_network = True
|
|
similar_cheater_ips.append(cheater)
|
|
except Exception:
|
|
pass
|
|
|
|
similar_ips = list(db.ip_infos.find({
|
|
"$or": [
|
|
{"ip": ip_address},
|
|
{"data.location.city": ip_data["location"].get("city")},
|
|
{"data.network.autonomous_system_number": ip_data["network"].get(
|
|
"autonomous_system_number")}
|
|
]
|
|
}))
|
|
|
|
db.ip_infos.insert_one({
|
|
"ip": ip_address,
|
|
"is_cheater": is_cheater,
|
|
"steam_id": steam_id,
|
|
"data": ip_data,
|
|
"added_by": session["username"],
|
|
"timestamp": datetime.now()
|
|
})
|
|
else:
|
|
error = "Fehler beim Abrufen der IP-Daten."
|
|
except Exception as e:
|
|
error = f"Fehler: {str(e)}"
|
|
|
|
return render_template(
|
|
"ip_checker.html",
|
|
ip_data=ip_data,
|
|
error=error,
|
|
similar_ips=similar_ips,
|
|
is_cheater_network=is_cheater_network,
|
|
similar_cheater_ips=similar_cheater_ips
|
|
)
|
|
|
|
|
|
@app.route('/add-changelog', methods=['GET', 'POST'])
|
|
def add_changelog():
|
|
if 'changelogs' not in session.get('permissions', []):
|
|
return "Unauthorized", 403
|
|
|
|
if request.method == 'POST':
|
|
changelog_text = request.form.get('changelog')
|
|
if changelog_text:
|
|
changelogs_collection.insert_one({
|
|
'text': changelog_text,
|
|
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
})
|
|
return redirect(url_for('dashboard'))
|
|
|
|
return render_template('add_changelog.html')
|
|
|
|
|
|
@app.route("/appointments", methods=["GET", "POST"])
|
|
def appointments():
|
|
if "username" not in session or "appointments" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
if request.method == "POST":
|
|
title = request.form["title"]
|
|
start_date = request.form["start_date"]
|
|
end_date = request.form["end_date"]
|
|
description = request.form.get("description", "")
|
|
|
|
appointments_collection.insert_one({
|
|
"username": session["username"],
|
|
"title": title,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"description": description,
|
|
"created_at": datetime.now()
|
|
})
|
|
|
|
add_log(f"{session['username']} hat einen neuen Termin erstellt: {title}")
|
|
|
|
return redirect(url_for("appointments"))
|
|
|
|
user_appointments = list(appointments_collection.find({"username": session["username"]}).sort("start_date", 1))
|
|
|
|
return render_template("appointments.html", appointments=user_appointments)
|
|
|
|
|
|
@app.route("/delete_appointment/<appointment_id>", methods=["POST"])
|
|
def delete_appointment(appointment_id):
|
|
if "username" not in session or "appointments" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
appointment = appointments_collection.find_one({"_id": ObjectId(appointment_id), "username": session["username"]})
|
|
if appointment:
|
|
appointments_collection.delete_one({"_id": ObjectId(appointment_id)})
|
|
add_log(f"{session['username']} hat den Termin '{appointment['title']}' gelöscht")
|
|
|
|
return redirect(url_for("appointments"))
|
|
|
|
|
|
@app.route("/edit_appointment/<appointment_id>", methods=["GET", "POST"])
|
|
def edit_appointment(appointment_id):
|
|
if "username" not in session or "appointments" not in session["permissions"]:
|
|
return redirect(url_for("login"))
|
|
|
|
appointment = appointments_collection.find_one({"_id": ObjectId(appointment_id), "username": session["username"]})
|
|
if not appointment:
|
|
return "Termin nicht gefunden", 404
|
|
|
|
if request.method == "POST":
|
|
title = request.form["title"]
|
|
start_date = request.form["start_date"]
|
|
end_date = request.form["end_date"]
|
|
description = request.form.get("description", "")
|
|
|
|
appointments_collection.update_one(
|
|
{"_id": ObjectId(appointment_id)},
|
|
{"$set": {
|
|
"title": title,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"description": description
|
|
}}
|
|
)
|
|
|
|
add_log(f"{session['username']} hat den Termin '{title}' bearbeitet")
|
|
|
|
return redirect(url_for("appointments"))
|
|
|
|
return render_template("edit_appointment.html", appointment=appointment)
|
|
|
|
|
|
@app.route("/pcmachwas", methods=["GET", "POST"])
|
|
def pcmachwas():
|
|
return render_template("stinker.html")
|
|
|
|
|
|
@app.route("/settings", methods=["GET"])
|
|
def settings():
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
return redirect(url_for("edit_user", user_id=session["user_id"]))
|
|
|
|
|
|
@app.route("/ets2_map_cracked")
|
|
def ets2_map_cracked():
|
|
scs_dir = "/var/www/admin_website/static/ets2_map_cracked"
|
|
if not os.path.isdir(scs_dir):
|
|
return f"Verzeichnis nicht gefunden oder keine Rechte: {scs_dir}", 404
|
|
try:
|
|
files = [
|
|
f for f in os.listdir(scs_dir)
|
|
if f.endswith(".scs") and os.path.isfile(os.path.join(scs_dir, f))
|
|
]
|
|
except Exception as e:
|
|
return f"Fehler beim Lesen des Verzeichnisses: {e}", 500
|
|
if not files:
|
|
return "Keine .scs Dateien gefunden.", 404
|
|
links = "".join(
|
|
f'<a href="/download_ets2_map_cracked/{fname}" download id="dl_{i}" style="display:none"></a>'
|
|
for i, fname in enumerate(files)
|
|
)
|
|
js = f"""
|
|
<script>
|
|
const files = {files!r};
|
|
let idx = 0;
|
|
function downloadNext() {{
|
|
if(idx >= files.length) return;
|
|
document.getElementById('dl_' + idx).click();
|
|
idx++;
|
|
setTimeout(downloadNext, 1000);
|
|
}}
|
|
window.onload = downloadNext;
|
|
</script>
|
|
"""
|
|
return f"<h2>ETS2 Maps Cracked Downloads</h2>{links}{js}<p>Downloads werden automatisch gestartet...</p>"
|
|
|
|
|
|
@app.route("/download_ets2_map_cracked/<filename>")
|
|
def download_ets2_map_cracked(filename):
|
|
scs_dir = "/var/www/admin_website/static/ets2_map_cracked"
|
|
file_path = os.path.join(scs_dir, filename)
|
|
if os.path.isfile(file_path) and filename.endswith(".scs"):
|
|
return send_file(file_path, as_attachment=True)
|
|
return "Datei nicht gefunden.", 404
|
|
|
|
|
|
# wallpaper
|
|
@app.route("/wallpaper")
|
|
def wallpaper():
|
|
return render_template("wallpaper.html")
|
|
|
|
|
|
# NEUE PROXY-ROUTE für die SCP:SL API
|
|
@app.route('/api/scpsl_proxy')
|
|
def scpsl_proxy():
|
|
try:
|
|
response = requests.get(SCP_SL_API_URL, timeout=5)
|
|
response.raise_for_status()
|
|
return jsonify(response.json())
|
|
except requests.exceptions.Timeout:
|
|
return jsonify({"error": "Request timed out"}), 504
|
|
except requests.exceptions.ConnectionError:
|
|
return jsonify({"error": "Connection error"}), 503
|
|
except requests.exceptions.RequestException as e:
|
|
return jsonify({"error": f"Failed to fetch data: {e}"}), 500
|
|
|
|
|
|
def fetch_and_store_player_count():
|
|
with app.app_context():
|
|
try:
|
|
response = requests.get(SCP_SL_API_URL, timeout=5)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
players_str_full = data.get('players', '0/0')
|
|
current_players = 0
|
|
if '/' in players_str_full:
|
|
try:
|
|
current_players = int(players_str_full.split('/')[0])
|
|
except ValueError:
|
|
current_players = 0
|
|
else:
|
|
try:
|
|
current_players = int(players_str_full)
|
|
except ValueError:
|
|
current_players = 0
|
|
scpsl_playerdata_collection.insert_one({
|
|
"timestamp": datetime.utcnow(),
|
|
"players": current_players
|
|
})
|
|
scpsl_playerdata_collection.create_index("timestamp")
|
|
except requests.exceptions.RequestException as e:
|
|
print(f"Error fetching player count: {e}")
|
|
except Exception as e:
|
|
print(f"Unexpected error fetching/storing to MongoDB: {e}")
|
|
|
|
|
|
def background_task():
|
|
while True:
|
|
fetch_and_store_player_count()
|
|
time.sleep(60)
|
|
|
|
|
|
threading.Thread(target=background_task, daemon=True).start()
|
|
|
|
|
|
@app.route('/api/player_history')
|
|
def player_history_api():
|
|
graph_data = []
|
|
one_day_ago = datetime.utcnow() - timedelta(hours=24)
|
|
history_cursor = scpsl_playerdata_collection.find(
|
|
{"timestamp": {"$gte": one_day_ago}},
|
|
{"_id": 0, "timestamp": 1, "players": 1}
|
|
).sort("timestamp", 1)
|
|
|
|
zero_start = None
|
|
for doc in history_cursor:
|
|
t = doc["timestamp"]
|
|
p = doc["players"]
|
|
if p == 0:
|
|
if zero_start is None:
|
|
zero_start = t
|
|
continue
|
|
else:
|
|
if zero_start is not None:
|
|
duration = (t - zero_start).total_seconds()
|
|
if duration > 20:
|
|
graph_data.append({
|
|
"time": zero_start.isoformat() + "Z",
|
|
"players": 0
|
|
})
|
|
graph_data.append({
|
|
"time": t.isoformat() + "Z",
|
|
"players": p
|
|
})
|
|
zero_start = None
|
|
graph_data.append({
|
|
"time": t.isoformat() + "Z",
|
|
"players": p
|
|
})
|
|
if zero_start is not None:
|
|
last_time = datetime.utcnow()
|
|
duration = (last_time - zero_start).total_seconds()
|
|
if duration > 20:
|
|
graph_data.append({
|
|
"time": zero_start.isoformat() + "Z",
|
|
"players": 0
|
|
})
|
|
graph_data.append({
|
|
"time": last_time.isoformat() + "Z",
|
|
"players": 0
|
|
})
|
|
return jsonify(graph_data)
|
|
|
|
|
|
@app.route('/player_history_graph')
|
|
def player_history_graph():
|
|
html = """
|
|
<html>
|
|
<head><title>SCP:SL Player Count (Letzte 24h)</title></head>
|
|
<body style="background:#1a1a1a;">
|
|
<img id="player_graph" src="/player_history_graph_img" style="width:90vw;max-width:1200px;border:2px solid #444;">
|
|
<script>
|
|
setInterval(function() {
|
|
var img = document.getElementById('player_graph');
|
|
img.src = '/player_history_graph_img?ts=' + new Date().getTime();
|
|
}, 10000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
return html
|
|
|
|
|
|
@app.route('/player_history_graph_img')
|
|
def player_history_graph_img():
|
|
one_day_ago = datetime.utcnow() - timedelta(hours=24)
|
|
history_cursor = scpsl_playerdata_collection.find(
|
|
{"timestamp": {"$gte": one_day_ago}},
|
|
{"_id": 0, "timestamp": 1, "players": 1}
|
|
).sort("timestamp", 1)
|
|
zero_start = None
|
|
filtered_times = []
|
|
filtered_players = []
|
|
for doc in history_cursor:
|
|
t = doc["timestamp"]
|
|
p = doc["players"]
|
|
if p == 0:
|
|
if zero_start is None:
|
|
zero_start = t
|
|
continue
|
|
else:
|
|
if zero_start is not None:
|
|
duration = (t - zero_start).total_seconds()
|
|
if duration > 20:
|
|
filtered_times.append(zero_start)
|
|
filtered_players.append(0)
|
|
filtered_times.append(t)
|
|
filtered_players.append(p)
|
|
zero_start = None
|
|
filtered_times.append(t)
|
|
filtered_players.append(p)
|
|
if zero_start is not None:
|
|
last_time = datetime.utcnow()
|
|
duration = (last_time - zero_start).total_seconds()
|
|
if duration > 20:
|
|
filtered_times.append(zero_start)
|
|
filtered_players.append(0)
|
|
filtered_times.append(last_time)
|
|
filtered_players.append(0)
|
|
fig, ax = plt.subplots(figsize=(10, 6))
|
|
if not filtered_times:
|
|
ax.text(0.5, 0.5, 'No player data available for the last 24 hours.',
|
|
horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=12, color='red')
|
|
ax.set_ylim(bottom=0, top=36)
|
|
else:
|
|
ax.plot(filtered_times, filtered_players, color='lime', linewidth=2)
|
|
max_players_in_history = max(filtered_players) if filtered_players else 0
|
|
if max_players_in_history > 0:
|
|
ax.set_ylim(bottom=0, top=max_players_in_history + 5)
|
|
else:
|
|
ax.set_ylim(bottom=0, top=36)
|
|
ax.set_title('SCP:SL Player Count (Letzte 24h)', color='white')
|
|
ax.set_xlabel('Zeit', color='white')
|
|
ax.set_ylabel('Spieleranzahl', color='white')
|
|
ax.set_facecolor('#1a1a1a')
|
|
fig.patch.set_facecolor('#1a1a1a')
|
|
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
|
ax.xaxis.set_major_locator(mdates.HourLocator(interval=3))
|
|
fig.autofmt_xdate()
|
|
ax.tick_params(axis='x', colors='white')
|
|
ax.tick_params(axis='y', colors='white')
|
|
ax.yaxis.set_major_locator(plt.MaxNLocator(integer=True))
|
|
ax.grid(True, linestyle='--', alpha=0.5, color='#444444')
|
|
ax.spines['bottom'].set_color('white')
|
|
ax.spines['top'].set_color('white')
|
|
ax.spines['left'].set_color('white')
|
|
ax.spines['right'].set_color('white')
|
|
buffer = BytesIO()
|
|
plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
|
|
buffer.seek(0)
|
|
plt.close(fig)
|
|
return send_file(buffer, mimetype='image/png')
|
|
|
|
|
|
# Voice-Channel Übersicht
|
|
@app.route("/vcs")
|
|
def vcs_overview():
|
|
# Beispiel: Channels können dynamisch sein, hier statisch
|
|
channels = [
|
|
{"id": "talk1", "name": "Teamtalk"},
|
|
{"id": "talk2", "name": "Privat"},
|
|
{"id": "talk3", "name": "Privat 2"}
|
|
]
|
|
return render_template("schisscord.html", channels=channels)
|
|
|
|
|
|
# Voice-Channel Join
|
|
@app.route("/join/<channel_id>")
|
|
def join_vc(channel_id):
|
|
if "username" not in session:
|
|
return redirect(url_for("login"))
|
|
return render_template("vc_room.html", channel_id=channel_id, username=session["username"])
|
|
|
|
|
|
# SocketIO: Nutzer tritt Channel bei
|
|
@socketio.on("join_channel")
|
|
def handle_join_channel(data):
|
|
channel = data.get("channel")
|
|
username = data.get("username")
|
|
join_room(channel)
|
|
emit("user_joined", {"username": username}, room=channel)
|
|
|
|
|
|
@socketio.on("leave_channel")
|
|
def handle_leave_channel(data):
|
|
channel = data.get("channel")
|
|
username = data.get("username")
|
|
leave_room(channel)
|
|
emit("user_left", {"username": username}, room=channel)
|
|
|
|
|
|
@socketio.on("signal")
|
|
def handle_signal(data):
|
|
channel = data.get("channel")
|
|
signal_data = data.get("signal")
|
|
sender = data.get("username")
|
|
emit("signal", {"signal": signal_data, "username": sender}, room=channel, include_self=False)
|
|
|
|
|
|
MONGO_URI = "mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/"
|
|
client = MongoClient(MONGO_URI)
|
|
ETS2_DB_NAME = "ets2_license_db"
|
|
ETS2_COLLECTION_NAME = "licenses"
|
|
ets2_db = client[ETS2_DB_NAME]
|
|
ets2_licenses = ets2_db[ETS2_COLLECTION_NAME]
|
|
print(f"[DEBUG] Using DB: {ETS2_DB_NAME}, Collection: {ETS2_COLLECTION_NAME}")
|
|
|
|
# --- DLC Folder ---
|
|
DLC_FOLDER = "/var/www/admin_website/static/ets2_dlcs"
|
|
|
|
# --- License Check Endpoint ---
|
|
@app.route("/checklicense", methods=["GET"])
|
|
def check_license():
|
|
key = request.args.get("key")
|
|
user = request.args.get("user")
|
|
print(f"[DEBUG] /checklicense called with key='{key}' user='{user}'")
|
|
|
|
if not key or not user:
|
|
print("[DEBUG] Missing key or user")
|
|
return "invalid", 400
|
|
|
|
entry = ets2_licenses.find_one({"license_key": key})
|
|
print(f"[DEBUG] MongoDB entry found: {entry}")
|
|
|
|
if entry:
|
|
if not entry.get("windows_user"):
|
|
ets2_licenses.update_one({"license_key": key}, {"$set": {"windows_user": user}})
|
|
print(f"[DEBUG] Key '{key}' registered to user '{user}'")
|
|
return "valid"
|
|
elif entry["windows_user"] == user:
|
|
print(f"[DEBUG] Key '{key}' valid for user '{user}'")
|
|
return "valid"
|
|
else:
|
|
print(f"[DEBUG] Key '{key}' already used by another user '{entry['windows_user']}'")
|
|
return "invalid_user"
|
|
else:
|
|
print(f"[DEBUG] Key '{key}' not found in database")
|
|
return "invalid"
|
|
|
|
# --- DLC Download Endpoint ---
|
|
@app.route("/download", methods=["GET"])
|
|
def download_dlc():
|
|
key = request.headers.get("License-Key")
|
|
user = request.headers.get("Windows-User")
|
|
dlc_type = request.args.get("type", "all") # "all_map_dlcs" oder "all_dlcs"
|
|
print(f"[DEBUG] /download called with key='{key}', user='{user}', type='{dlc_type}'")
|
|
|
|
if not key or not user:
|
|
print("[DEBUG] Missing headers")
|
|
return "missing_headers", 400
|
|
|
|
entry = ets2_licenses.find_one({"license_key": key})
|
|
print(f"[DEBUG] MongoDB entry for download: {entry}")
|
|
|
|
if not entry or entry.get("windows_user") != user:
|
|
print(f"[DEBUG] Unauthorized access for key '{key}' user '{user}'")
|
|
return "unauthorized", 403
|
|
|
|
# Bestimme die Datei basierend auf DLC Typ
|
|
if dlc_type == "all_map_dlcs":
|
|
file_name = "all_map_dlcs.zip"
|
|
else:
|
|
file_name = "all_dlcs.zip"
|
|
|
|
file_path = os.path.join(DLC_FOLDER, file_name)
|
|
if not os.path.exists(file_path):
|
|
print(f"[DEBUG] File not found at {file_path}")
|
|
return "not_found", 404
|
|
|
|
print(f"[DEBUG] Sending file {file_path} to user '{user}'")
|
|
return send_file(file_path, as_attachment=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("[DEBUG] Starting ETS2 DLC Server")
|
|
print(f"[DEBUG] Using DB: {ETS2_DB_NAME}, Collection: {ETS2_COLLECTION_NAME}")
|
|
socketio.run(app, debug=True, host='0.0.0.0', port=5000)
|