from functools import cache import json from flask import ( Flask, make_response, redirect, request, jsonify, render_template, send_from_directory, send_file, ) import os import json import requests from datetime import datetime import dotenv dotenv.load_dotenv() SPEAKER_NAME = os.getenv("SPEAKER_NAME", "Family Room Speakers") app = Flask(__name__) def log(message: str): """ Log a message to the server log file. """ log_path = "server.log" if not os.path.exists(log_path): with open(log_path, "w") as f: f.write("") with open(log_path, "a") as f: f.write(f"{datetime.now().isoformat()} - {message}\n") print(f"{datetime.now().isoformat()} - {message}") # Also print to console for debugging def find(name, path): for root, dirs, files in os.walk(path): if name in files: return os.path.join(root, name) # Assets routes @app.route("/assets/") def send_assets(path): if path.endswith(".json"): return send_from_directory( "templates/assets", path, mimetype="application/json" ) if os.path.isfile("templates/assets/" + path): return send_from_directory("templates/assets", path) # Try looking in one of the directories filename: str = path.split("/")[-1] if ( filename.endswith(".png") or filename.endswith(".jpg") or filename.endswith(".jpeg") or filename.endswith(".svg") ): if os.path.isfile("templates/assets/img/" + filename): return send_from_directory("templates/assets/img", filename) if os.path.isfile("templates/assets/img/favicon/" + filename): return send_from_directory("templates/assets/img/favicon", filename) return render_template("404.html"), 404 # region Special routes @app.route("/favicon.png") def faviconPNG(): return send_from_directory("templates/assets/img", "favicon.png") @app.route("/.well-known/") def wellknown(path): # Try to proxy to https://nathan.woodburn.au/.well-known/ req = requests.get(f"https://nathan.woodburn.au/.well-known/{path}") return make_response( req.content, 200, {"Content-Type": req.headers["Content-Type"]} ) # endregion # region Main routes @app.route("/") def index(): return render_template("index.html") @app.route("/restart") def restart(): """ Restart the server by redirecting to the same URL. This is a simple way to restart the server without needing to stop it manually. """ # Execute a `pkill spotifyd` command to stop the spotifyd process status = os.system("pkill spotifyd") hookPath = os.path.join(os.getcwd(), "song-hook.sh") # Clear the server log log_path = "server.log" if os.path.exists(log_path): with open(log_path, "w") as f: f.write("") # Start with a new process output = os.system(f"spotifyd -d '{SPEAKER_NAME}' --onevent {hookPath}") if output != 0: log("Failed to restart spotifyd") else: log("spotifyd restarted successfully") return redirect('/') @app.route("/") def catch_all(path: str): if os.path.isfile("templates/" + path): return render_template(path) # Try with .html if os.path.isfile("templates/" + path + ".html"): return render_template(path + ".html") if os.path.isfile("templates/" + path.strip("/") + ".html"): return render_template(path.strip("/") + ".html") # Try to find a file matching if path.count("/") < 1: # Try to find a file matching filename = find(path, "templates") if filename: return send_file(filename) return render_template("404.html"), 404 # endregion # region API routes api_requests = 0 @app.route("/api/v1/data", methods=["GET"]) def api_data(): """ Example API endpoint that returns some data. You can modify this to return whatever data you need. """ global api_requests api_requests += 1 data = { "header": "Sample API Response", "content": f"Hello, this is a sample API response! You have called this endpoint {api_requests} times.", "timestamp": datetime.now().isoformat(), } return jsonify(data) @app.route("/api/v1/logs", methods=["GET"]) def api_logs(): """ Returns the last 100 lines of the server log. """ log_path = "server.log" # Check if the log file exists if not os.path.isfile(log_path): return jsonify({"logs": "Server not running"}), 404 lines = [] try: with open(log_path, "r") as f: lines = f.readlines()[-100:] except Exception as e: lines = [f"Error reading log file: {e}"] return jsonify({"logs": "".join(lines)}) @app.route("/api/v1/current_track", methods=["GET"]) def api_current_track(): # If the current_track.json file exists, read it and return its contents if os.path.isfile("current_track.json"): with open("current_track.json", "r") as f: track_data = json.load(f) return jsonify(track_data) else: return jsonify({"error": "No current track data available"}), 404 # endregion # region Error Catching # 404 catch all @app.errorhandler(404) def not_found(e): return render_template("404.html"), 404 # endregion if __name__ == "__main__": app.run(debug=True, port=5000, host="0.0.0.0")