diff --git a/blueprints/spotify.py b/blueprints/spotify.py
new file mode 100644
index 0000000..52cf20f
--- /dev/null
+++ b/blueprints/spotify.py
@@ -0,0 +1,122 @@
+from flask import redirect, request, Blueprint, url_for
+from tools import json_response
+import os
+import requests
+import time
+import base64
+
+spotify_bp = Blueprint('spotify', __name__)
+
+CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
+CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
+ALLOWED_SPOTIFY_USER_ID = os.getenv("SPOTIFY_USER_ID")
+
+SPOTIFY_AUTH_URL = "https://accounts.spotify.com/authorize"
+SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
+SPOTIFY_CURRENTLY_PLAYING_URL = "https://api.spotify.com/v1/me/player/currently-playing"
+
+SCOPE = "user-read-currently-playing user-read-playback-state"
+
+ACCESS_TOKEN = None
+REFRESH_TOKEN = os.getenv("SPOTIFY_REFRESH_TOKEN")
+TOKEN_EXPIRES = 0
+
+def refresh_access_token():
+ """Refresh Spotify access token when expired."""
+ global ACCESS_TOKEN, TOKEN_EXPIRES
+
+ # If still valid, reuse it
+ if ACCESS_TOKEN and time.time() < TOKEN_EXPIRES - 60:
+ return ACCESS_TOKEN
+
+ auth_str = f"{CLIENT_ID}:{CLIENT_SECRET}"
+ b64_auth = base64.b64encode(auth_str.encode()).decode()
+
+ data = {
+ "grant_type": "refresh_token",
+ "refresh_token": REFRESH_TOKEN,
+ }
+ headers = {"Authorization": f"Basic {b64_auth}"}
+
+ response = requests.post(SPOTIFY_TOKEN_URL, data=data, headers=headers)
+ if response.status_code != 200:
+ print("Failed to refresh token:", response.text)
+ return None
+
+ token_info = response.json()
+ ACCESS_TOKEN = token_info["access_token"]
+ TOKEN_EXPIRES = time.time() + token_info.get("expires_in", 3600)
+ return ACCESS_TOKEN
+
+
+
+@spotify_bp.route("/login")
+def login():
+ auth_query = (
+ f"{SPOTIFY_AUTH_URL}?response_type=code&client_id={CLIENT_ID}"
+ f"&redirect_uri={url_for("spotify.callback", _external=True)}&scope={SCOPE}"
+ )
+ return redirect(auth_query)
+
+@spotify_bp.route("/callback")
+def callback():
+ code = request.args.get("code")
+ if not code:
+ return "Authorization failed.", 400
+
+ data = {
+ "grant_type": "authorization_code",
+ "code": code,
+ "redirect_uri": url_for("spotify.callback", _external=True),
+ "client_id": CLIENT_ID,
+ "client_secret": CLIENT_SECRET,
+ }
+ response = requests.post(SPOTIFY_TOKEN_URL, data=data)
+ token_info = response.json()
+ if "access_token" not in token_info:
+ return json_response(request, {"error": "Failed to obtain token", "details": token_info}, 400)
+
+ access_token = token_info["access_token"]
+ me = requests.get(
+ "https://api.spotify.com/v1/me",
+ headers={"Authorization": f"Bearer {access_token}"}
+ ).json()
+
+ if me.get("id") != ALLOWED_SPOTIFY_USER_ID:
+ return json_response(request, {"error": "Unauthorized user"}, 403)
+
+ global REFRESH_TOKEN
+ REFRESH_TOKEN = token_info.get("refresh_token")
+ print("Spotify authorization successful.")
+ print("Refresh Token:", REFRESH_TOKEN)
+ return redirect(url_for("spotify.currently_playing"))
+
+@spotify_bp.route("/")
+@spotify_bp.route("/currently-playing")
+def currently_playing():
+ """Public endpoint showing your current track."""
+ token = refresh_access_token()
+ if not token:
+ return json_response(request, {"error": "Failed to refresh access token"}, 500)
+
+ headers = {"Authorization": f"Bearer {token}"}
+ response = requests.get(SPOTIFY_CURRENTLY_PLAYING_URL, headers=headers)
+
+ if response.status_code == 204:
+ return json_response(request, {"message": "Nothing is currently playing."}, 200)
+ elif response.status_code != 200:
+ return json_response(request, {"error": "Spotify API error", "status": response.status_code}, response.status_code)
+
+ data = response.json()
+ if not data.get("item"):
+ return json_response(request, {"message": "Nothing is currently playing."}, 200)
+
+
+ track = {
+ "song_name": data["item"]["name"],
+ "artist": ", ".join([artist["name"] for artist in data["item"]["artists"]]),
+ "album_name": data["item"]["album"]["name"],
+ "album_art": data["item"]["album"]["images"][0]["url"],
+ "is_playing": data["is_playing"]
+ }
+ return json_response(request, {"spotify":track}, 200)
\ No newline at end of file
diff --git a/server.py b/server.py
index b4ce245..a581350 100644
--- a/server.py
+++ b/server.py
@@ -25,6 +25,7 @@ from blueprints.wellknown import wk_bp
from blueprints.api import api_bp
from blueprints.podcast import podcast_bp
from blueprints.acme import acme_bp
+from blueprints.spotify import spotify_bp
from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getHandshakeScript, get_tools_data
from curl import curl_response
@@ -38,6 +39,7 @@ app.register_blueprint(wk_bp, url_prefix='/.well-known')
app.register_blueprint(api_bp, url_prefix='/api/v1')
app.register_blueprint(podcast_bp)
app.register_blueprint(acme_bp)
+app.register_blueprint(spotify_bp, url_prefix='/spotify')
dotenv.load_dotenv()
diff --git a/templates/assets/css/brand-reveal.min.css b/templates/assets/css/brand-reveal.min.css
index 1d2261d..6fc6619 100644
--- a/templates/assets/css/brand-reveal.min.css
+++ b/templates/assets/css/brand-reveal.min.css
@@ -1 +1 @@
-.name-container{display:inline-flex;align-items:center;overflow:hidden;position:absolute;width:fit-content;left:50%;transform:translateX(-50%)}.slider{position:relative;left:0;animation:1s linear 1s forwards slide}@keyframes slide{0%{left:0}100%{left:calc(100%)}}.brand{mask-image:linear-gradient(to right,black 50%,transparent 50%);-webkit-mask-image:linear-gradient(to right,black 50%,transparent 50%);mask-position:100% 0;-webkit-mask-position:100% 0;mask-size:200%;-webkit-mask-size:200%;animation:1s linear 1s forwards reveal}@keyframes reveal{0%{mask-position:100% 0;-webkit-mask-position:100% 0}100%{mask-position:0 0;-webkit-mask-position:0 0}}
\ No newline at end of file
+.name-container{display:inline-flex;align-items:center;overflow:hidden;position:absolute;width:fit-content;left:50%;transform:translateX(-50%)}.slider{position:relative;left:0;animation:1s linear 1s forwards slide}@keyframes slide{0%{left:0}100%{left:calc(100%)}}.brand{mask-image:linear-gradient(to right,black 50%,transparent 50%);-webkit-mask-image:linear-gradient(to right,black 50%,transparent 50%);mask-position:100% 0;-webkit-mask-position:100% 0;mask-size:200%;-webkit-mask-size:200%;animation:1s linear 1s forwards reveal}@keyframes reveal{0%{mask-position:100% 0;-webkit-mask-position:100% 0}100%{mask-position:0 0;-webkit-mask-position:0 0}}.now-playing{position:fixed;bottom:0;right:0;border-top-left-radius:10px;background:#10101039;padding:1em}
\ No newline at end of file
diff --git a/templates/assets/img/external/spotify.png b/templates/assets/img/external/spotify.png
new file mode 100644
index 0000000..a296f59
Binary files /dev/null and b/templates/assets/img/external/spotify.png differ
diff --git a/templates/index.html b/templates/index.html
index 01669e9..41d0f4d 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -292,7 +292,161 @@ Check them out here!{{time|safe}}
+ {{time|safe}}
+
+
+