themehmi commited on
Commit
bc2473c
·
verified ·
1 Parent(s): d403c94

Upload 11 files

Browse files
Files changed (3) hide show
  1. app.py +3 -129
  2. templates/index.html +0 -121
  3. templates/settings.html +2 -114
app.py CHANGED
@@ -227,80 +227,12 @@ def logout():
227
  @app.route('/settings')
228
  @login_required
229
  def settings():
230
- from bson.objectid import ObjectId
231
- user_id = session.get('user_id')
232
- db = get_db()
233
- users = db.users
234
- user = users.find_one({'_id': ObjectId(user_id)})
235
- has_oauth = bool(user and user.get('oauth_token'))
236
-
237
- return render_template('settings.html', has_oauth=has_oauth)
238
 
239
  @app.route('/')
240
  @login_required
241
  def index():
242
- from bson.objectid import ObjectId
243
- user_id = session.get('user_id')
244
- user = get_db().users.find_one({'_id': ObjectId(user_id)})
245
- has_oauth = bool(user and user.get('oauth_token'))
246
- return render_template('index.html', has_oauth=has_oauth)
247
-
248
- @app.route('/api/oauth/start', methods=['POST'])
249
- @login_required
250
- def api_oauth_start():
251
- client_id = os.getenv('YOUTUBE_CLIENT_ID')
252
- if not client_id:
253
- return jsonify({"error": "YOUTUBE_CLIENT_ID not configured"}), 500
254
-
255
- data = {
256
- "client_id": client_id,
257
- "scope": "https://www.googleapis.com/auth/youtube"
258
- }
259
- resp = requests.post("https://oauth2.googleapis.com/device/code", data=data)
260
- if resp.status_code != 200:
261
- return jsonify({"error": "Failed to contact Google OAuth"}), 500
262
-
263
- return jsonify(resp.json())
264
-
265
- @app.route('/api/oauth/poll', methods=['POST'])
266
- @login_required
267
- def api_oauth_poll():
268
- client_id = os.getenv('YOUTUBE_CLIENT_ID')
269
- client_secret = os.getenv('YOUTUBE_CLIENT_SECRET')
270
- device_code = request.json.get('device_code')
271
-
272
- if not client_id or not client_secret or not device_code:
273
- return jsonify({"error": "Missing credentials"}), 400
274
-
275
- data = {
276
- "client_id": client_id,
277
- "client_secret": client_secret,
278
- "device_code": device_code,
279
- "grant_type": "urn:ietf:params:oauth:grant-type:device_code"
280
- }
281
-
282
- resp = requests.post("https://oauth2.googleapis.com/token", data=data)
283
-
284
- if resp.status_code == 200:
285
- token_data = resp.json()
286
- token_data["client_id"] = client_id
287
- token_data["client_secret"] = client_secret
288
- from bson.objectid import ObjectId
289
- db = get_db()
290
- db.users.update_one({'_id': ObjectId(session['user_id'])}, {'$set': {'oauth_token': json.dumps(token_data)}})
291
- return jsonify({"status": "success", "token": token_data})
292
- else:
293
- try:
294
- err_data = resp.json()
295
- err = err_data.get("error")
296
- if err in ["authorization_pending", "slow_down"]:
297
- return jsonify({"status": "pending"})
298
- elif err == "expired_token":
299
- return jsonify({"status": "expired"}), 400
300
- except Exception:
301
- pass
302
-
303
- return jsonify({"error": resp.text}), 500
304
 
305
  @app.route('/api/status')
306
  def api_status():
@@ -554,68 +486,10 @@ def api_voice():
554
  return jsonify({"speak": "Skipping.", "action": "next_native"})
555
  if song:
556
  try:
557
- user_id = session.get('user_id')
558
- user = None
559
- if user_id:
560
- from bson.objectid import ObjectId
561
- user = get_db().users.find_one({'_id': ObjectId(user_id)})
562
-
563
- has_oauth = False
564
  # Inject a Chrome-impersonated session to bypass YouTube bot blocking on Hugging Face Spaces
565
  custom_session = cffi_requests.Session(impersonate="chrome")
566
- try:
567
- if user and user.get('oauth_token'):
568
- filepath = f"oauth_{user_id}.json"
569
- with open(filepath, 'w') as f:
570
- f.write(user['oauth_token'])
571
- ytmusic = YTMusic(filepath, requests_session=custom_session)
572
- has_oauth = True
573
- # Read back in case of refresh
574
- with open(filepath, 'r') as f:
575
- refreshed = f.read()
576
- if refreshed != user['oauth_token']:
577
- get_db().users.update_one({'_id': ObjectId(user_id)}, {'$set': {'oauth_token': refreshed}})
578
- else:
579
- ytmusic = YTMusic(requests_session=custom_session)
580
- except Exception:
581
- ytmusic = YTMusic(requests_session=custom_session)
582
-
583
- is_personal = any(phrase in song.lower() for phrase in ["my liked", "my playlist", "my mix", "my supermix"])
584
 
585
- if is_personal:
586
- if not has_oauth:
587
- return jsonify({"speak": "Please link your Google Account in the settings first to access your personal library."})
588
-
589
- if "liked" in song.lower():
590
- playlist = ytmusic.get_liked_songs(limit=50)
591
- video_ids = [track['videoId'] for track in playlist['tracks'] if track.get('videoId')]
592
- return jsonify({"speak": "Playing your liked songs.", "action": "play_native", "video_ids": video_ids, "title": "Liked Songs"})
593
-
594
- elif "playlist" in song.lower() or "mix" in song.lower():
595
- # Extract requested playlist name
596
- pl_name = song.lower().replace("my playlist", "").replace("my mix", "").replace("my supermix", "my supermix").strip()
597
- if not pl_name:
598
- pl_name = "my supermix" if "supermix" in song.lower() else "mix"
599
-
600
- playlists = ytmusic.get_library_playlists(limit=50)
601
- best_match = None
602
-
603
- # Find closest match
604
- for pl in playlists:
605
- if pl_name in pl.get('title', '').lower() or pl.get('title', '').lower() in pl_name:
606
- best_match = pl
607
- break
608
-
609
- if not best_match and playlists:
610
- best_match = playlists[0] # fallback to first
611
-
612
- if best_match:
613
- tracks = ytmusic.get_playlist(best_match['playlistId'], limit=50)
614
- video_ids = [track['videoId'] for track in tracks.get('tracks', []) if track.get('videoId')]
615
- return jsonify({"speak": f"Playing {best_match.get('title')}.", "action": "play_native", "video_ids": video_ids, "title": best_match.get('title')})
616
- else:
617
- return jsonify({"speak": "I couldn't find that playlist in your library."})
618
-
619
  # Generic search
620
  results = ytmusic.search(f"{song} lyrics", filter="videos", limit=10)
621
  video_ids = [res['videoId'] for res in results if 'videoId' in res]
 
227
  @app.route('/settings')
228
  @login_required
229
  def settings():
230
+ return render_template('settings.html')
 
 
 
 
 
 
 
231
 
232
  @app.route('/')
233
  @login_required
234
  def index():
235
+ return render_template('index.html')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  @app.route('/api/status')
238
  def api_status():
 
486
  return jsonify({"speak": "Skipping.", "action": "next_native"})
487
  if song:
488
  try:
 
 
 
 
 
 
 
489
  # Inject a Chrome-impersonated session to bypass YouTube bot blocking on Hugging Face Spaces
490
  custom_session = cffi_requests.Session(impersonate="chrome")
491
+ ytmusic = YTMusic(requests_session=custom_session)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  # Generic search
494
  results = ytmusic.search(f"{song} lyrics", filter="videos", limit=10)
495
  video_ids = [res['videoId'] for res in results if 'videoId' in res]
templates/index.html CHANGED
@@ -929,33 +929,7 @@
929
  </aside>
930
  </div>
931
 
932
- <!-- OAuth Modal -->
933
- <div id="cookieModal" class="modal-overlay">
934
- <div class="modal-content">
935
- <h2 style="font-size: 1.8rem; margin-bottom: 0.5rem; color: var(--primary);"><i class="fa-brands fa-youtube" style="color: #ff0000;"></i> YouTube Music Setup</h2>
936
- <p style="color: var(--text-muted); margin-bottom: 2rem; font-size: 0.9rem; line-height: 1.5;">Welcome! To play music flawlessly, please link your YouTube Music account.</p>
937
-
938
- <div id="oauthInitState">
939
- <button type="button" class="btn-glow" id="startOauthBtn" style="width: 100%; justify-content: center; padding: 1rem 2rem; font-size: 1.1rem; margin-bottom: 1rem;">Link Google Account</button>
940
- <button type="button" class="btn-glow" id="closeModalBtn" style="width: 100%; justify-content: center; padding: 1rem 2rem; font-size: 1.1rem; background: #333333; color: white; border: 1px solid var(--glass-border);">Skip</button>
941
- </div>
942
 
943
- <div id="oauthCodeState" style="display: none; text-align: center;">
944
- <p style="color: var(--text-main); margin-bottom: 1rem;">Go to the link below and enter this code:</p>
945
- <div style="background: rgba(0,0,0,0.5); padding: 1rem; border-radius: 8px; margin-bottom: 1rem;">
946
- <a id="oauthLink" href="#" target="_blank" style="color: var(--primary); font-size: 1.2rem; display: block; margin-bottom: 0.5rem;">google.com/device</a>
947
- <div style="display: flex; align-items: center; justify-content: center; gap: 15px;">
948
- <h1 id="oauthCode" style="color: var(--warning); letter-spacing: 5px; font-size: 2rem; margin: 0;"></h1>
949
- <button type="button" id="copyOauthCodeBtn" style="background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 1.5rem; transition: color 0.2s; padding: 0;" title="Copy code">
950
- <i class="fa-regular fa-copy"></i>
951
- </button>
952
- </div>
953
- </div>
954
- <p style="color: var(--text-muted); font-size: 0.85rem; margin-bottom: 1.5rem;"><i class="fa-solid fa-circle-notch fa-spin"></i> Waiting for authorization...</p>
955
- <button type="button" class="btn-glow" id="cancelOauthBtn" style="width: 100%; justify-content: center; background: #333333; color: white; border: 1px solid var(--glass-border);">Cancel</button>
956
- </div>
957
- </div>
958
- </div>
959
 
960
  <!-- Chat FAB for Mobile -->
961
  <button id="chatFab" class="chat-fab" title="Open Chat">
@@ -1502,102 +1476,7 @@
1502
  if (e.key === 'Enter') sendTextMessage();
1503
  });
1504
 
1505
- // OAuth Modal Logic
1506
- {% if not has_oauth %}
1507
- document.getElementById('cookieModal').style.display = 'flex';
1508
- {% endif %}
1509
-
1510
- let oauthPollInterval = null;
1511
-
1512
- document.getElementById('closeModalBtn')?.addEventListener('click', () => {
1513
- document.getElementById('cookieModal').style.display = 'none';
1514
- });
1515
-
1516
- document.getElementById('copyOauthCodeBtn').addEventListener('click', function() {
1517
- const code = document.getElementById('oauthCode').innerText;
1518
- if (code) {
1519
- navigator.clipboard.writeText(code).then(() => {
1520
- const icon = this.querySelector('i');
1521
- icon.classList.remove('fa-copy', 'fa-regular');
1522
- icon.classList.add('fa-check', 'fa-solid');
1523
- icon.style.color = 'var(--success)';
1524
- setTimeout(() => {
1525
- icon.classList.remove('fa-check', 'fa-solid');
1526
- icon.classList.add('fa-copy', 'fa-regular');
1527
- icon.style.color = '';
1528
- }, 2000);
1529
- });
1530
- }
1531
- });
1532
-
1533
- document.getElementById('cancelOauthBtn')?.addEventListener('click', () => {
1534
- if(oauthPollInterval) clearInterval(oauthPollInterval);
1535
- document.getElementById('oauthInitState').style.display = 'block';
1536
- document.getElementById('oauthCodeState').style.display = 'none';
1537
- });
1538
-
1539
- document.getElementById('startOauthBtn')?.addEventListener('click', async () => {
1540
- const btn = document.getElementById('startOauthBtn');
1541
- const originalText = btn.innerHTML;
1542
- btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Connecting...';
1543
- btn.disabled = true;
1544
-
1545
- try {
1546
- const response = await fetch('/api/oauth/start', { method: 'POST' });
1547
- const data = await response.json();
1548
-
1549
- btn.innerHTML = originalText;
1550
- btn.disabled = false;
1551
-
1552
- if (data.error) {
1553
- alert('Error starting OAuth: ' + data.error);
1554
- return;
1555
- }
1556
-
1557
- document.getElementById('oauthLink').href = data.verification_url;
1558
- document.getElementById('oauthLink').innerText = data.verification_url;
1559
- document.getElementById('oauthCode').innerText = data.user_code;
1560
-
1561
- document.getElementById('oauthInitState').style.display = 'none';
1562
- document.getElementById('oauthCodeState').style.display = 'block';
1563
-
1564
- // Start polling
1565
- const deviceCode = data.device_code;
1566
- const interval = data.interval || 5;
1567
-
1568
- oauthPollInterval = setInterval(async () => {
1569
- try {
1570
- const pollRes = await fetch('/api/oauth/poll', {
1571
- method: 'POST',
1572
- headers: { 'Content-Type': 'application/json' },
1573
- body: JSON.stringify({ device_code: deviceCode })
1574
- });
1575
- const pollData = await pollRes.json();
1576
-
1577
- if (pollData.status === 'success') {
1578
- clearInterval(oauthPollInterval);
1579
- alert('Successfully linked YouTube Music!');
1580
- window.location.reload();
1581
- } else if (pollData.status === 'expired') {
1582
- clearInterval(oauthPollInterval);
1583
- alert('Code expired. Please try again.');
1584
- document.getElementById('cancelOauthBtn').click();
1585
- } else if (pollData.error) {
1586
- clearInterval(oauthPollInterval);
1587
- alert('Error: ' + pollData.error);
1588
- document.getElementById('cancelOauthBtn').click();
1589
- }
1590
- } catch(err) {
1591
- console.error('Polling error', err);
1592
- }
1593
- }, interval * 1000);
1594
 
1595
- } catch(e) {
1596
- btn.innerHTML = originalText;
1597
- btn.disabled = false;
1598
- alert('Connection error');
1599
- }
1600
- });
1601
 
1602
  </script>
1603
  </body>
 
929
  </aside>
930
  </div>
931
 
 
 
 
 
 
 
 
 
 
 
932
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
933
 
934
  <!-- Chat FAB for Mobile -->
935
  <button id="chatFab" class="chat-fab" title="Open Chat">
 
1476
  if (e.key === 'Enter') sendTextMessage();
1477
  });
1478
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1479
 
 
 
 
 
 
 
1480
 
1481
  </script>
1482
  </body>
templates/settings.html CHANGED
@@ -175,125 +175,13 @@
175
  </div>
176
 
177
  <div class="instructions" style="text-align: center;">
178
- {% if has_oauth %}
179
- <h3 style="color: var(--success);"><i class="fa-solid fa-circle-check"></i> Account Linked</h3>
180
- <p style="margin-top: 10px;">Your YouTube Music account is successfully linked!</p>
181
- {% else %}
182
- <h3 style="color: var(--warning);"><i class="fa-solid fa-triangle-exclamation"></i> Account Not Linked</h3>
183
- <p style="margin-top: 10px;">Link your account to ensure high-quality music streaming.</p>
184
- {% endif %}
185
  </div>
186
 
187
  <div id="oauthInitState">
188
- <button type="button" class="btn-glow" id="startOauthBtn" style="margin-bottom: 1rem;">
189
- {% if has_oauth %}Relink Account{% else %}Link Google Account{% endif %}
190
- </button>
191
  <a href="{{ url_for('index') }}" class="btn-glow btn-secondary">Back to Dashboard</a>
192
  </div>
193
-
194
- <div id="oauthCodeState" style="display: none; text-align: center;">
195
- <p style="color: var(--text-main); margin-bottom: 1rem;">Go to the link below and enter this code:</p>
196
- <div style="background: rgba(0,0,0,0.5); padding: 1rem; border-radius: 8px; margin-bottom: 1rem;">
197
- <a id="oauthLink" href="#" target="_blank" style="color: var(--primary); font-size: 1.2rem; display: block; margin-bottom: 0.5rem;">google.com/device</a>
198
- <div style="display: flex; align-items: center; justify-content: center; gap: 15px;">
199
- <h1 id="oauthCode" style="color: var(--warning); letter-spacing: 5px; font-size: 2rem; margin: 0;"></h1>
200
- <button type="button" id="copyOauthCodeBtn" style="background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 1.5rem; transition: color 0.2s; padding: 0;" title="Copy code">
201
- <i class="fa-regular fa-copy"></i>
202
- </button>
203
- </div>
204
- </div>
205
- <p style="color: var(--text-muted); font-size: 0.85rem; margin-bottom: 1.5rem;"><i class="fa-solid fa-circle-notch fa-spin"></i> Waiting for authorization...</p>
206
- <button type="button" class="btn-glow btn-secondary" id="cancelOauthBtn">Cancel</button>
207
- </div>
208
  </div>
209
-
210
- <script>
211
- let oauthPollInterval = null;
212
-
213
- document.getElementById('copyOauthCodeBtn').addEventListener('click', function() {
214
- const code = document.getElementById('oauthCode').innerText;
215
- if (code) {
216
- navigator.clipboard.writeText(code).then(() => {
217
- const icon = this.querySelector('i');
218
- icon.classList.remove('fa-copy', 'fa-regular');
219
- icon.classList.add('fa-check', 'fa-solid');
220
- icon.style.color = 'var(--success)';
221
- setTimeout(() => {
222
- icon.classList.remove('fa-check', 'fa-solid');
223
- icon.classList.add('fa-copy', 'fa-regular');
224
- icon.style.color = '';
225
- }, 2000);
226
- });
227
- }
228
- });
229
-
230
- document.getElementById('cancelOauthBtn')?.addEventListener('click', () => {
231
- if(oauthPollInterval) clearInterval(oauthPollInterval);
232
- document.getElementById('oauthInitState').style.display = 'block';
233
- document.getElementById('oauthCodeState').style.display = 'none';
234
- });
235
-
236
- document.getElementById('startOauthBtn')?.addEventListener('click', async () => {
237
- const btn = document.getElementById('startOauthBtn');
238
- const originalText = btn.innerHTML;
239
- btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Connecting...';
240
- btn.disabled = true;
241
-
242
- try {
243
- const response = await fetch('/api/oauth/start', { method: 'POST' });
244
- const data = await response.json();
245
-
246
- btn.innerHTML = originalText;
247
- btn.disabled = false;
248
-
249
- if (data.error) {
250
- alert('Error starting OAuth: ' + data.error);
251
- return;
252
- }
253
-
254
- document.getElementById('oauthLink').href = data.verification_url;
255
- document.getElementById('oauthLink').innerText = data.verification_url;
256
- document.getElementById('oauthCode').innerText = data.user_code;
257
-
258
- document.getElementById('oauthInitState').style.display = 'none';
259
- document.getElementById('oauthCodeState').style.display = 'block';
260
-
261
- const deviceCode = data.device_code;
262
- const interval = data.interval || 5;
263
-
264
- oauthPollInterval = setInterval(async () => {
265
- try {
266
- const pollRes = await fetch('/api/oauth/poll', {
267
- method: 'POST',
268
- headers: { 'Content-Type': 'application/json' },
269
- body: JSON.stringify({ device_code: deviceCode })
270
- });
271
- const pollData = await pollRes.json();
272
-
273
- if (pollData.status === 'success') {
274
- clearInterval(oauthPollInterval);
275
- alert('Successfully linked YouTube Music!');
276
- window.location.reload();
277
- } else if (pollData.status === 'expired') {
278
- clearInterval(oauthPollInterval);
279
- alert('Code expired. Please try again.');
280
- document.getElementById('cancelOauthBtn').click();
281
- } else if (pollData.error) {
282
- clearInterval(oauthPollInterval);
283
- alert('Error: ' + pollData.error);
284
- document.getElementById('cancelOauthBtn').click();
285
- }
286
- } catch(err) {
287
- console.error('Polling error', err);
288
- }
289
- }, interval * 1000);
290
-
291
- } catch(e) {
292
- btn.innerHTML = originalText;
293
- btn.disabled = false;
294
- alert('Connection error');
295
- }
296
- });
297
- </script>
298
  </body>
299
  </html>
 
175
  </div>
176
 
177
  <div class="instructions" style="text-align: center;">
178
+ <h3 style="color: var(--text-main);"><i class="fa-solid fa-info-circle"></i> Settings</h3>
179
+ <p style="margin-top: 10px;">OAuth has been completely removed from the system. No configuration is required.</p>
 
 
 
 
 
180
  </div>
181
 
182
  <div id="oauthInitState">
 
 
 
183
  <a href="{{ url_for('index') }}" class="btn-glow btn-secondary">Back to Dashboard</a>
184
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  </body>
187
  </html>