lenpanda commited on
Commit
2d033ff
Β·
verified Β·
1 Parent(s): 7a38972

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -129
app.py CHANGED
@@ -1,91 +1,30 @@
1
  import os
2
  import requests
 
3
  import gradio as gr
4
- from flask import redirect
 
5
 
6
- # Spotify API credentials (set in HF Space secrets)
7
- CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
8
- CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
9
- REDIRECT_URI = os.getenv("REDIRECT_URI", "https://lenpanda-spotify-mcp.hf.space/callback")
10
  SPOTIFY_AUTH_URL = "https://accounts.spotify.com/authorize"
11
  SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
12
- SCOPE = "playlist-modify-public playlist-modify-private"
13
 
14
- # Store access token (for demo; use a session store in production)
 
 
 
15
  access_token = None
 
16
 
17
- # Flask-like route handlers
18
  def home():
19
- return """
20
- <a href="/login">Login with Spotify</a><br><br>
21
- <div id="song-form">
22
- <input id="song-name" placeholder="Song Name" type="text">
23
- <input id="artist-name" placeholder="Artist Name (optional)" type="text">
24
- <button onclick="addSong()">Add Song to Playlist</button>
25
- <div id="result"></div>
26
- </div>
27
- <script>
28
- async function getTrackUri(songName, accessToken, artistName = null) {
29
- let query = `track:${songName}`;
30
- if (artistName) query += ` artist:${artistName}`;
31
- const url = `https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=track&limit=1`;
32
- const headers = { "Authorization": `Bearer ${accessToken}` };
33
- try {
34
- const response = await fetch(url, { headers });
35
- const data = await response.json();
36
- if (data.tracks && data.tracks.items.length > 0) {
37
- return data.tracks.items[0].uri;
38
- }
39
- throw new Error("Song not found");
40
- } catch (error) {
41
- throw new Error(`Search error: ${error.message}`);
42
- }
43
- }
44
-
45
- async function addSongToPlaylist(playlistId, trackUri, accessToken) {
46
- const url = `https://api.spotify.com/v1/playlists/${playlistId}/tracks`;
47
- const headers = {
48
- "Authorization": `Bearer ${accessToken}`,
49
- "Content-Type": "application/json",
50
- };
51
- const body = { uris: [trackUri], position: 0 };
52
- try {
53
- const response = await fetch(url, {
54
- method: "POST",
55
- headers,
56
- body: JSON.stringify(body),
57
- });
58
- const data = await response.json();
59
- if (response.status === 201) {
60
- return "Song added to playlist!";
61
- }
62
- throw new Error(`Error adding song: ${data.error?.message || "Unknown error"}`);
63
- } catch (error) {
64
- throw new Error(`Playlist error: ${error.message}`);
65
- }
66
- }
67
-
68
- async function addSong() {
69
- const songName = document.getElementById("song-name").value;
70
- const artistName = document.getElementById("artist-name").value;
71
- const resultDiv = document.getElementById("result");
72
- const accessToken = "{{ACCESS_TOKEN}}"; // Replaced server-side
73
- const playlistId = "YOUR_PLAYLIST_ID"; // Replace with actual ID
74
- if (!accessToken || accessToken === "Not authenticated") {
75
- resultDiv.innerText = "Please authenticate with Spotify first.";
76
- return;
77
- }
78
- try {
79
- const trackUri = await getTrackUri(songName, accessToken, artistName);
80
- const result = await addSongToPlaylist(playlistId, trackUri, accessToken);
81
- resultDiv.innerText = result;
82
- } catch (error) {
83
- resultDiv.innerText = error.message;
84
- }
85
- }
86
- </script>
87
- """.replace("{{ACCESS_TOKEN}}", access_token or "Not authenticated")
88
 
 
89
  def login():
90
  auth_url = (
91
  f"{SPOTIFY_AUTH_URL}?client_id={CLIENT_ID}&response_type=code"
@@ -93,11 +32,14 @@ def login():
93
  )
94
  return redirect(auth_url)
95
 
96
- def callback(code=None):
97
- global access_token
 
 
98
  if not code:
99
  return "Error: No authorization code provided", 400
100
-
 
101
  token_data = {
102
  "grant_type": "authorization_code",
103
  "code": code,
@@ -105,68 +47,183 @@ def callback(code=None):
105
  "client_id": CLIENT_ID,
106
  "client_secret": CLIENT_SECRET
107
  }
 
108
  response = requests.post(SPOTIFY_TOKEN_URL, data=token_data)
109
  token_json = response.json()
110
-
111
  if response.status_code == 200:
112
  access_token = token_json.get("access_token")
113
- return redirect("/")
 
 
 
 
 
 
 
114
  else:
115
  return f"Error: {token_json.get('error_description', 'Failed to get access token')}", 400
116
 
117
- # Gradio interface for server-side song addition (optional fallback)
118
- def gradio_interface(song_name="", artist_name=""):
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  if not access_token:
120
- return "Error: Not authenticated. Please log in via /login."
121
- if not song_name:
122
- return "Please enter a song name."
123
- try:
124
- query = f"track:{song_name}"
125
- if artist_name:
126
- query += f" artist:{artist_name}"
127
- url = f"https://api.spotify.com/v1/search?q={query}&type=track&limit=1"
128
- headers = {"Authorization": f"Bearer {access_token}"}
129
- response = requests.get(url, headers=headers)
 
130
  data = response.json()
131
- if not data.get("tracks", {}).get("items"):
132
- return "Error: Song not found."
133
- track_uri = data["tracks"]["items"][0]["uri"]
134
-
135
- playlist_id = "YOUR_PLAYLIST_ID" # Replace with actual ID
136
- url = f"https://api.spotify.com/v1/playlists/{playlist_id}/tracks"
137
- headers = {
138
- "Authorization": f"Bearer {access_token}",
139
- "Content-Type": "application/json",
140
- }
141
- data = {"uris": [track_uri], "position": 0}
142
- response = requests.post(url, headers=headers, json=data)
143
- if response.status_code == 201:
144
- return f"Added '{song_name}' to playlist!"
145
- return f"Error adding song: {response.json().get('error', {}).get('message', 'Unknown error')}"
146
- except Exception as e:
147
- return f"Error: {str(e)}"
148
 
149
- # Create Gradio Blocks
150
- demo = gr.Blocks()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- # Define routes outside Blocks context
153
- demo.route("/", home)
154
- demo.route("/login", login)
155
- demo.route("/callback", callback)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- # Define Gradio UI
158
- with demo:
159
- gr.Markdown("## Spotify Playlist Manager")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  with gr.Row():
161
- song_input = gr.Textbox(label="Song Name")
162
- artist_input = gr.Textbox(label="Artist Name (optional)")
163
- submit_button = gr.Button("Add Song to Playlist")
164
- output = gr.Textbox(label="Result")
165
- submit_button.click(
166
- fn=gradio_interface,
167
- inputs=[song_input, artist_input],
168
- outputs=output
 
 
 
 
 
 
 
 
 
 
169
  )
 
 
 
 
 
 
 
170
 
171
  if __name__ == "__main__":
172
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import requests
3
+ from flask import Flask, redirect, request, jsonify
4
  import gradio as gr
5
+ import threading
6
+ import time
7
 
8
+ # Spotify API credentials (set these in your HF Space's Environment Variables)
9
+ CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID") # Add to HF Space secrets
10
+ CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET") # Add to HF Space secrets
11
+ REDIRECT_URI = os.getenv("REDIRECT_URI", "https://lenpanda-spotify-mcp.hf.space/callback") # Update with your Space URL
12
  SPOTIFY_AUTH_URL = "https://accounts.spotify.com/authorize"
13
  SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
14
+ SCOPE = "playlist-modify-public playlist-modify-private user-read-private"
15
 
16
+ # Initialize Flask app
17
+ app = Flask(__name__)
18
+
19
+ # Store access token and user info (for demo purposes; use proper session store in production)
20
  access_token = None
21
+ user_info = None
22
 
23
+ @app.route('/')
24
  def home():
25
+ return '<a href="/login">Login with Spotify</a>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ @app.route('/login')
28
  def login():
29
  auth_url = (
30
  f"{SPOTIFY_AUTH_URL}?client_id={CLIENT_ID}&response_type=code"
 
32
  )
33
  return redirect(auth_url)
34
 
35
+ @app.route('/callback')
36
+ def callback():
37
+ global access_token, user_info
38
+ code = request.args.get('code')
39
  if not code:
40
  return "Error: No authorization code provided", 400
41
+
42
+ # Exchange code for access token
43
  token_data = {
44
  "grant_type": "authorization_code",
45
  "code": code,
 
47
  "client_id": CLIENT_ID,
48
  "client_secret": CLIENT_SECRET
49
  }
50
+
51
  response = requests.post(SPOTIFY_TOKEN_URL, data=token_data)
52
  token_json = response.json()
53
+
54
  if response.status_code == 200:
55
  access_token = token_json.get("access_token")
56
+
57
+ # Get user info
58
+ headers = {"Authorization": f"Bearer {access_token}"}
59
+ user_response = requests.get("https://api.spotify.com/v1/me", headers=headers)
60
+ if user_response.status_code == 200:
61
+ user_info = user_response.json()
62
+
63
+ return f"Authorization successful! You can now use the Gradio interface. <br><a href='http://localhost:7860'>Go to Gradio Interface</a>"
64
  else:
65
  return f"Error: {token_json.get('error_description', 'Failed to get access token')}", 400
66
 
67
+ @app.route('/status')
68
+ def status():
69
+ return jsonify({
70
+ "authenticated": access_token is not None,
71
+ "user": user_info.get("display_name") if user_info else None
72
+ })
73
+
74
+ # Gradio functions
75
+ def get_auth_status():
76
+ if access_token and user_info:
77
+ return f"βœ… Authenticated as: {user_info.get('display_name', 'Unknown')}"
78
+ else:
79
+ return "❌ Not authenticated. Please visit the Flask login page first."
80
+
81
+ def search_songs(query):
82
  if not access_token:
83
+ return "Please authenticate first!"
84
+
85
+ if not query.strip():
86
+ return "Please enter a search query."
87
+
88
+ headers = {"Authorization": f"Bearer {access_token}"}
89
+ params = {"q": query, "type": "track", "limit": 10}
90
+
91
+ response = requests.get("https://api.spotify.com/v1/search", headers=headers, params=params)
92
+
93
+ if response.status_code == 200:
94
  data = response.json()
95
+ tracks = data.get("tracks", {}).get("items", [])
96
+
97
+ if not tracks:
98
+ return "No tracks found."
99
+
100
+ result = "🎡 Search Results:\n\n"
101
+ for i, track in enumerate(tracks, 1):
102
+ artists = ", ".join([artist["name"] for artist in track["artists"]])
103
+ result += f"{i}. **{track['name']}** by {artists}\n"
104
+ result += f" Album: {track['album']['name']}\n"
105
+ result += f" URI: `{track['uri']}`\n\n"
106
+
107
+ return result
108
+ else:
109
+ return f"Error searching: {response.json().get('error', {}).get('message', 'Unknown error')}"
 
 
110
 
111
+ def get_user_playlists():
112
+ if not access_token:
113
+ return "Please authenticate first!"
114
+
115
+ headers = {"Authorization": f"Bearer {access_token}"}
116
+ response = requests.get("https://api.spotify.com/v1/me/playlists", headers=headers)
117
+
118
+ if response.status_code == 200:
119
+ data = response.json()
120
+ playlists = data.get("items", [])
121
+
122
+ if not playlists:
123
+ return "No playlists found."
124
+
125
+ result = "πŸ“‹ Your Playlists:\n\n"
126
+ for playlist in playlists:
127
+ result += f"**{playlist['name']}**\n"
128
+ result += f"ID: `{playlist['id']}`\n"
129
+ result += f"Tracks: {playlist['tracks']['total']}\n\n"
130
+
131
+ return result
132
+ else:
133
+ return f"Error getting playlists: {response.json().get('error', {}).get('message', 'Unknown error')}"
134
 
135
+ def add_song_to_playlist(playlist_id, track_uri):
136
+ if not access_token:
137
+ return "Please authenticate first!"
138
+
139
+ if not playlist_id.strip() or not track_uri.strip():
140
+ return "Please provide both playlist ID and track URI."
141
+
142
+ # Ensure track_uri is in correct format
143
+ if not track_uri.startswith("spotify:track:"):
144
+ if track_uri.startswith("https://open.spotify.com/track/"):
145
+ track_uri = f"spotify:track:{track_uri.split('/')[-1].split('?')[0]}"
146
+ else:
147
+ track_uri = f"spotify:track:{track_uri}"
148
+
149
+ url = f"https://api.spotify.com/v1/playlists/{playlist_id}/tracks"
150
+ headers = {
151
+ "Authorization": f"Bearer {access_token}",
152
+ "Content-Type": "application/json"
153
+ }
154
+ data = {"uris": [track_uri]}
155
+
156
+ response = requests.post(url, headers=headers, json=data)
157
+
158
+ if response.status_code == 201:
159
+ return "βœ… Song added to playlist successfully!"
160
+ else:
161
+ error_msg = response.json().get('error', {}).get('message', 'Unknown error')
162
+ return f"❌ Error adding song: {error_msg}"
163
 
164
+ # Create Gradio interface
165
+ with gr.Blocks(title="Spotify Playlist Manager") as demo:
166
+ gr.Markdown("# 🎡 Spotify Playlist Manager")
167
+ gr.Markdown("First, authenticate with Spotify using the Flask login page, then use the tools below.")
168
+
169
+ # Authentication status
170
+ with gr.Row():
171
+ auth_status = gr.Textbox(label="Authentication Status", interactive=False)
172
+ refresh_btn = gr.Button("πŸ”„ Refresh Status")
173
+
174
+ refresh_btn.click(get_auth_status, outputs=auth_status)
175
+
176
+ # Search songs
177
+ gr.Markdown("## πŸ” Search Songs")
178
+ with gr.Row():
179
+ search_input = gr.Textbox(label="Search for songs", placeholder="Enter song name, artist, or album")
180
+ search_btn = gr.Button("Search")
181
+ search_output = gr.Textbox(label="Search Results", lines=10)
182
+
183
+ search_btn.click(search_songs, inputs=search_input, outputs=search_output)
184
+
185
+ # Get playlists
186
+ gr.Markdown("## πŸ“‹ Your Playlists")
187
  with gr.Row():
188
+ get_playlists_btn = gr.Button("Get My Playlists")
189
+ playlists_output = gr.Textbox(label="Your Playlists", lines=8)
190
+
191
+ get_playlists_btn.click(get_user_playlists, outputs=playlists_output)
192
+
193
+ # Add song to playlist
194
+ gr.Markdown("## βž• Add Song to Playlist")
195
+ with gr.Row():
196
+ playlist_id_input = gr.Textbox(label="Playlist ID", placeholder="Enter playlist ID from above")
197
+ track_uri_input = gr.Textbox(label="Track URI", placeholder="spotify:track:... or Spotify URL")
198
+ with gr.Row():
199
+ add_song_btn = gr.Button("Add Song to Playlist")
200
+ add_song_output = gr.Textbox(label="Result")
201
+
202
+ add_song_btn.click(
203
+ add_song_to_playlist,
204
+ inputs=[playlist_id_input, track_uri_input],
205
+ outputs=add_song_output
206
  )
207
+
208
+ # Load initial status
209
+ demo.load(get_auth_status, outputs=auth_status)
210
+
211
+ # Run Flask and Gradio
212
+ def run_flask():
213
+ app.run(host="0.0.0.0", port=5000, debug=False)
214
 
215
  if __name__ == "__main__":
216
+ # Start Flask in a separate thread
217
+ flask_thread = threading.Thread(target=run_flask, daemon=True)
218
+ flask_thread.start()
219
+
220
+ # Give Flask time to start
221
+ time.sleep(2)
222
+
223
+ # Launch Gradio on port 7860 (HF Spaces default)
224
+ demo.launch(
225
+ server_name="0.0.0.0",
226
+ server_port=7860,
227
+ share=False,
228
+ show_error=True
229
+ )