lenpanda commited on
Commit
53d7f84
Β·
verified Β·
1 Parent(s): 2d033ff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +307 -169
app.py CHANGED
@@ -1,86 +1,125 @@
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"
31
- f"&redirect_uri={REDIRECT_URI}&scope={SCOPE}"
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,
46
- "redirect_uri": REDIRECT_URI,
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."
@@ -88,142 +127,241 @@ def search_songs(query):
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
- )
 
1
  import os
2
  import requests
 
3
  import gradio as gr
4
+ import urllib.parse
5
+ import base64
6
+ import secrets
7
+ import hashlib
8
 
9
+ # Spotify API credentials
10
+ CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
11
+ CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
12
+ REDIRECT_URI = os.getenv("REDIRECT_URI", "https://lenpanda-spotify-mcp.hf.space")
13
  SPOTIFY_AUTH_URL = "https://accounts.spotify.com/authorize"
14
  SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
15
  SCOPE = "playlist-modify-public playlist-modify-private user-read-private"
16
 
17
+ # Global variables to store auth state
 
 
 
18
  access_token = None
19
  user_info = None
20
+ auth_state = None
21
 
22
+ def generate_auth_url():
23
+ """Generate Spotify authorization URL"""
24
+ global auth_state
25
+ auth_state = secrets.token_urlsafe(32)
26
+
27
+ params = {
28
+ 'client_id': CLIENT_ID,
29
+ 'response_type': 'code',
30
+ 'redirect_uri': REDIRECT_URI,
31
+ 'scope': SCOPE,
32
+ 'state': auth_state,
33
+ 'show_dialog': 'true'
34
+ }
35
+
36
+ auth_url = f"{SPOTIFY_AUTH_URL}?{urllib.parse.urlencode(params)}"
37
+
38
+ return f"""
39
+ ## 🎡 Spotify Authentication Required
40
 
41
+ To use this app, you need to authenticate with Spotify:
 
 
 
 
 
 
42
 
43
+ 1. **Click the link below** to authorize this app:
44
+
45
+ **[πŸ”— Login with Spotify]({auth_url})**
46
+
47
+ 2. **After authorization**, you'll be redirected back with a code in the URL
48
+ 3. **Copy the entire URL** from your browser's address bar
49
+ 4. **Paste it** in the "Authorization URL" field below
50
+ 5. **Click "Complete Authentication"**
51
+
52
+ ### Current Status: ❌ Not Authenticated
53
+ """
54
+
55
+ def complete_auth(auth_url_input):
56
+ """Complete authentication using the callback URL"""
57
+ global access_token, user_info, auth_state
58
 
59
+ if not auth_url_input or not auth_url_input.strip():
60
+ return "❌ Please paste the callback URL from Spotify", ""
61
 
62
+ try:
63
+ # Parse the URL to extract code and state
64
+ parsed_url = urllib.parse.urlparse(auth_url_input)
65
+ params = urllib.parse.parse_qs(parsed_url.query)
66
 
67
+ code = params.get('code', [None])[0]
68
+ state = params.get('state', [None])[0]
69
+ error = params.get('error', [None])[0]
 
 
70
 
71
+ if error:
72
+ return f"❌ Spotify authentication error: {error}", ""
73
+
74
+ if not code:
75
+ return "❌ No authorization code found in URL. Please make sure you copied the complete callback URL.", ""
76
+
77
+ if state != auth_state:
78
+ return "❌ Invalid state parameter. Please try the authentication process again.", ""
79
+
80
+ # Exchange code for access token
81
+ token_data = {
82
+ "grant_type": "authorization_code",
83
+ "code": code,
84
+ "redirect_uri": REDIRECT_URI,
85
+ "client_id": CLIENT_ID,
86
+ "client_secret": CLIENT_SECRET
87
+ }
88
+
89
+ response = requests.post(SPOTIFY_TOKEN_URL, data=token_data)
90
+ token_json = response.json()
91
+
92
+ if response.status_code == 200:
93
+ access_token = token_json.get("access_token")
94
+
95
+ # Get user info
96
+ headers = {"Authorization": f"Bearer {access_token}"}
97
+ user_response = requests.get("https://api.spotify.com/v1/me", headers=headers)
98
+
99
+ if user_response.status_code == 200:
100
+ user_info = user_response.json()
101
+ success_msg = f"βœ… Successfully authenticated as: **{user_info.get('display_name', 'Unknown User')}**"
102
+ return success_msg, success_msg
103
+ else:
104
+ return "βœ… Authentication successful, but couldn't get user info.", "βœ… Authenticated"
105
+ else:
106
+ error_msg = token_json.get('error_description', 'Failed to get access token')
107
+ return f"❌ Token exchange failed: {error_msg}", ""
108
+
109
+ except Exception as e:
110
+ return f"❌ Error processing authentication: {str(e)}", ""
111
 
 
112
  def get_auth_status():
113
+ """Get current authentication status"""
114
  if access_token and user_info:
115
+ return f"βœ… Authenticated as: **{user_info.get('display_name', 'Unknown User')}**"
116
  else:
117
+ return "❌ Not authenticated"
118
 
119
  def search_songs(query):
120
+ """Search for songs on Spotify"""
121
  if not access_token:
122
+ return "❌ Please authenticate first!"
123
 
124
  if not query.strip():
125
  return "Please enter a search query."
 
127
  headers = {"Authorization": f"Bearer {access_token}"}
128
  params = {"q": query, "type": "track", "limit": 10}
129
 
130
+ try:
131
+ response = requests.get("https://api.spotify.com/v1/search", headers=headers, params=params)
 
 
 
 
 
 
132
 
133
+ if response.status_code == 200:
134
+ data = response.json()
135
+ tracks = data.get("tracks", {}).get("items", [])
136
+
137
+ if not tracks:
138
+ return "No tracks found."
139
+
140
+ result = "🎡 **Search Results:**\n\n"
141
+ for i, track in enumerate(tracks, 1):
142
+ artists = ", ".join([artist["name"] for artist in track["artists"]])
143
+ result += f"**{i}. {track['name']}** by {artists}\n"
144
+ result += f" Album: {track['album']['name']}\n"
145
+ result += f" URI: `{track['uri']}`\n"
146
+ result += f" Preview: {track.get('preview_url', 'Not available')}\n\n"
147
+
148
+ return result
149
+ else:
150
+ error_msg = response.json().get('error', {}).get('message', 'Unknown error')
151
+ return f"❌ Error searching: {error_msg}"
152
+ except Exception as e:
153
+ return f"❌ Search error: {str(e)}"
154
 
155
  def get_user_playlists():
156
+ """Get user's playlists"""
157
  if not access_token:
158
+ return "❌ Please authenticate first!"
159
 
160
  headers = {"Authorization": f"Bearer {access_token}"}
 
161
 
162
+ try:
163
+ response = requests.get("https://api.spotify.com/v1/me/playlists", headers=headers)
 
164
 
165
+ if response.status_code == 200:
166
+ data = response.json()
167
+ playlists = data.get("items", [])
168
+
169
+ if not playlists:
170
+ return "No playlists found."
171
+
172
+ result = "πŸ“‹ **Your Playlists:**\n\n"
173
+ for playlist in playlists:
174
+ result += f"**{playlist['name']}**\n"
175
+ result += f" ID: `{playlist['id']}`\n"
176
+ result += f" Tracks: {playlist['tracks']['total']}\n"
177
+ result += f" Public: {'Yes' if playlist['public'] else 'No'}\n\n"
178
+
179
+ return result
180
+ else:
181
+ error_msg = response.json().get('error', {}).get('message', 'Unknown error')
182
+ return f"❌ Error getting playlists: {error_msg}"
183
+ except Exception as e:
184
+ return f"❌ Playlist error: {str(e)}"
185
 
186
  def add_song_to_playlist(playlist_id, track_uri):
187
+ """Add a song to a playlist"""
188
  if not access_token:
189
+ return "❌ Please authenticate first!"
190
 
191
  if not playlist_id.strip() or not track_uri.strip():
192
+ return "❌ Please provide both playlist ID and track URI."
193
 
194
+ # Clean and format track URI
195
+ track_uri = track_uri.strip()
196
  if not track_uri.startswith("spotify:track:"):
197
+ if "open.spotify.com/track/" in track_uri:
198
+ track_id = track_uri.split("/track/")[1].split("?")[0]
199
+ track_uri = f"spotify:track:{track_id}"
200
+ elif len(track_uri) == 22: # Spotify track ID length
201
  track_uri = f"spotify:track:{track_uri}"
202
+ else:
203
+ return "❌ Invalid track URI format. Use spotify:track:... or Spotify URL"
204
 
205
+ url = f"https://api.spotify.com/v1/playlists/{playlist_id.strip()}/tracks"
206
  headers = {
207
  "Authorization": f"Bearer {access_token}",
208
  "Content-Type": "application/json"
209
  }
210
  data = {"uris": [track_uri]}
211
 
212
+ try:
213
+ response = requests.post(url, headers=headers, json=data)
214
+
215
+ if response.status_code == 201:
216
+ return "βœ… Song added to playlist successfully!"
217
+ else:
218
+ error_data = response.json().get('error', {})
219
+ error_msg = error_data.get('message', 'Unknown error')
220
+ return f"❌ Error adding song: {error_msg}"
221
+ except Exception as e:
222
+ return f"❌ Add song error: {str(e)}"
223
+
224
+ def create_playlist(playlist_name, description="", public=True):
225
+ """Create a new playlist"""
226
+ if not access_token:
227
+ return "❌ Please authenticate first!"
228
+
229
+ if not playlist_name.strip():
230
+ return "❌ Please provide a playlist name."
231
 
232
+ if not user_info:
233
+ return "❌ User information not available."
234
+
235
+ url = f"https://api.spotify.com/v1/users/{user_info['id']}/playlists"
236
+ headers = {
237
+ "Authorization": f"Bearer {access_token}",
238
+ "Content-Type": "application/json"
239
+ }
240
+ data = {
241
+ "name": playlist_name.strip(),
242
+ "description": description.strip(),
243
+ "public": public
244
+ }
245
+
246
+ try:
247
+ response = requests.post(url, headers=headers, json=data)
248
+
249
+ if response.status_code == 201:
250
+ playlist = response.json()
251
+ return f"βœ… Playlist created successfully!\n\n**{playlist['name']}**\nID: `{playlist['id']}`\nURL: {playlist['external_urls']['spotify']}"
252
+ else:
253
+ error_data = response.json().get('error', {})
254
+ error_msg = error_data.get('message', 'Unknown error')
255
+ return f"❌ Error creating playlist: {error_msg}"
256
+ except Exception as e:
257
+ return f"❌ Create playlist error: {str(e)}"
258
 
259
  # Create Gradio interface
260
+ with gr.Blocks(title="🎡 Spotify Playlist Manager", theme=gr.themes.Soft()) as demo:
261
  gr.Markdown("# 🎡 Spotify Playlist Manager")
262
+
263
+ with gr.Tabs():
264
+ # Authentication Tab
265
+ with gr.TabItem("πŸ” Authentication"):
266
+ auth_instructions = gr.Markdown(generate_auth_url())
267
+
268
+ with gr.Row():
269
+ auth_url_input = gr.Textbox(
270
+ label="Paste the callback URL here after authorizing",
271
+ placeholder="https://lenpanda-spotify-mcp.hf.space/?code=...",
272
+ lines=2
273
+ )
274
+
275
+ with gr.Row():
276
+ complete_auth_btn = gr.Button("Complete Authentication", variant="primary")
277
+ refresh_auth_btn = gr.Button("Refresh Auth Status")
278
+
279
+ auth_status = gr.Markdown("❌ Not authenticated")
280
+
281
+ complete_auth_btn.click(
282
+ complete_auth,
283
+ inputs=auth_url_input,
284
+ outputs=[gr.Markdown(), auth_status]
285
+ )
286
+
287
+ refresh_auth_btn.click(
288
+ get_auth_status,
289
+ outputs=auth_status
290
+ )
291
+
292
+ # Search Tab
293
+ with gr.TabItem("πŸ” Search Songs"):
294
+ gr.Markdown("Search for songs on Spotify")
295
+
296
+ with gr.Row():
297
+ search_input = gr.Textbox(
298
+ label="Search for songs",
299
+ placeholder="Enter song name, artist, or album"
300
+ )
301
+ search_btn = gr.Button("Search", variant="primary")
302
+
303
+ search_output = gr.Markdown("Enter a search query above")
304
+
305
+ search_btn.click(search_songs, inputs=search_input, outputs=search_output)
306
+
307
+ # Playlists Tab
308
+ with gr.TabItem("πŸ“‹ Playlists"):
309
+ gr.Markdown("View and manage your playlists")
310
+
311
+ with gr.Row():
312
+ get_playlists_btn = gr.Button("Get My Playlists", variant="primary")
313
+
314
+ playlists_output = gr.Markdown("Click the button above to load your playlists")
315
+
316
+ get_playlists_btn.click(get_user_playlists, outputs=playlists_output)
317
+
318
+ # Add Song Tab
319
+ with gr.TabItem("βž• Add Song"):
320
+ gr.Markdown("Add a song to one of your playlists")
321
+
322
+ playlist_id_input = gr.Textbox(
323
+ label="Playlist ID",
324
+ placeholder="Get this from the Playlists tab"
325
+ )
326
+
327
+ track_uri_input = gr.Textbox(
328
+ label="Track URI or Spotify URL",
329
+ placeholder="spotify:track:... or https://open.spotify.com/track/..."
330
+ )
331
+
332
+ add_song_btn = gr.Button("Add Song to Playlist", variant="primary")
333
+ add_song_output = gr.Markdown("")
334
+
335
+ add_song_btn.click(
336
+ add_song_to_playlist,
337
+ inputs=[playlist_id_input, track_uri_input],
338
+ outputs=add_song_output
339
+ )
340
+
341
+ # Create Playlist Tab
342
+ with gr.TabItem("βž• Create Playlist"):
343
+ gr.Markdown("Create a new playlist")
344
+
345
+ playlist_name_input = gr.Textbox(
346
+ label="Playlist Name",
347
+ placeholder="My Awesome Playlist"
348
+ )
349
+
350
+ playlist_desc_input = gr.Textbox(
351
+ label="Description (optional)",
352
+ placeholder="A description for your playlist"
353
+ )
354
+
355
+ playlist_public = gr.Checkbox(label="Make playlist public", value=True)
356
+
357
+ create_playlist_btn = gr.Button("Create Playlist", variant="primary")
358
+ create_playlist_output = gr.Markdown("")
359
+
360
+ create_playlist_btn.click(
361
+ create_playlist,
362
+ inputs=[playlist_name_input, playlist_desc_input, playlist_public],
363
+ outputs=create_playlist_output
364
+ )
365
 
366
  if __name__ == "__main__":
367
+ demo.launch()