Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Music API - Endpoint Tester</title> | |
| <style> | |
| body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial; margin: 24px; } | |
| h1 { margin: 0 0 4px; } | |
| .subtitle { color: #666; margin: 0 0 16px; } | |
| .card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; margin-bottom: 16px; } | |
| .row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } | |
| .row > * { margin: 4px 0; } | |
| label { font-size: 12px; color: #333; } | |
| input, select, button, textarea { font-size: 14px; padding: 8px 10px; } | |
| input, select, textarea { border: 1px solid #ccc; border-radius: 8px; } | |
| button { border: 1px solid #333; background: #111; color: #fff; border-radius: 8px; cursor: pointer; } | |
| button.secondary { background: #fff; color: #111; } | |
| code.url { display: block; background: #f7f7f7; padding: 8px; border-radius: 8px; overflow: auto; } | |
| pre { background: #0b1020; color: #d7e8ff; padding: 12px; border-radius: 10px; overflow: auto; } | |
| .two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } | |
| @media (max-width: 900px) { .two { grid-template-columns: 1fr; } } | |
| </style> | |
| <script> | |
| const base = 'https://veltrixcode-ytm.hf.space'; | |
| function $(id) { return document.getElementById(id); } | |
| function setJSON(id, value) { $(id).textContent = JSON.stringify(value, null, 2); } | |
| function setText(id, value) { $(id).textContent = value || ''; } | |
| function buildUrl(path, params) { | |
| const url = new URL(path, base); | |
| Object.entries(params || {}).forEach(([k, v]) => { | |
| if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, v); | |
| }); | |
| return url.toString(); | |
| } | |
| async function doFetch({ path, params, outUrlId, outJsonId, onAfter }) { | |
| const url = buildUrl(path, params); | |
| setText(outUrlId, url); | |
| try { | |
| const res = await fetch(url); | |
| const json = await res.json(); | |
| setJSON(outJsonId, json); | |
| if (typeof onAfter === 'function') onAfter(json); | |
| } catch (e) { | |
| setJSON(outJsonId, { error: e.message }); | |
| } | |
| } | |
| // Shared continuation helpers | |
| function applyContinuation(fromJson, toInputIdList = []) { | |
| const token = fromJson?.continuationToken | |
| || fromJson?.results?.continuationToken | |
| || null; | |
| toInputIdList.forEach(id => { $(id).value = token || ''; }); | |
| return token; | |
| } | |
| // Handlers | |
| async function ytmSearch(initial = true) { | |
| const q = $('ytm_q').value.trim(); | |
| const filter = $('ytm_filter').value; | |
| const continuationToken = initial ? '' : $('ytm_ct').value.trim(); | |
| await doFetch({ | |
| path: '/api/search', | |
| params: { q, filter, continuationToken }, | |
| outUrlId: 'ytm_url', | |
| outJsonId: 'ytm_json', | |
| onAfter: (json) => { | |
| // autofill continuation | |
| applyContinuation(json, ['ytm_ct']); | |
| } | |
| }); | |
| } | |
| async function ytSearch(initial = true) { | |
| const q = $('yt_q').value.trim(); | |
| const filter = $('yt_filter').value; | |
| const continuationToken = initial ? '' : $('yt_ct').value.trim(); | |
| await doFetch({ | |
| path: '/api/yt_search', | |
| params: { q, filter, continuationToken }, | |
| outUrlId: 'yt_url', | |
| outJsonId: 'yt_json', | |
| onAfter: (json) => { | |
| applyContinuation(json, ['yt_ct']); | |
| } | |
| }); | |
| } | |
| async function ytPlaylists(initial = true) { | |
| const q = $('ytp_q').value.trim(); | |
| const continuationToken = initial ? '' : $('ytp_ct').value.trim(); | |
| await doFetch({ | |
| path: '/api/yt_playlists', | |
| params: { q, continuationToken }, | |
| outUrlId: 'ytp_url', | |
| outJsonId: 'ytp_json', | |
| onAfter: (json) => { | |
| applyContinuation(json, ['ytp_ct']); | |
| } | |
| }); | |
| } | |
| // Quick presets | |
| function presetPhonk() { | |
| ['ytm_q','yt_q','ytp_q'].forEach(id => { $(id).value = 'phonk'; }); | |
| } | |
| window.addEventListener('DOMContentLoaded', presetPhonk); | |
| </script> | |
| </head> | |
| <body> | |
| <h1>Music API - Endpoint Tester</h1> | |
| <p class="subtitle">Run requests against the local API, inspect JSON, and page with continuation tokens.</p> | |
| <div class="card"> | |
| <h2>YouTube Music: /api/search</h2> | |
| <div class="row"> | |
| <label>Query <input id="ytm_q" placeholder="e.g. phonk"></label> | |
| <label>Filter | |
| <select id="ytm_filter"> | |
| <option value="songs">songs</option> | |
| <option value="videos">videos</option> | |
| <option value="albums">albums</option> | |
| <option value="artists">artists</option> | |
| <option value="playlists">playlists</option> | |
| <option value="community_playlists">community_playlists</option> | |
| <option value="profiles">profiles</option> | |
| <option value="podcasts">podcasts</option> | |
| <option value="episodes">episodes</option> | |
| </select> | |
| </label> | |
| <label>Continuation <input id="ytm_ct" placeholder="continuationToken"></label> | |
| <button onclick="ytmSearch(true)">Search</button> | |
| <button class="secondary" onclick="ytmSearch(false)">Next page ▶</button> | |
| </div> | |
| <code class="url" id="ytm_url"></code> | |
| <pre id="ytm_json">{}</pre> | |
| </div> | |
| <div class="card"> | |
| <h2>YouTube: /api/yt_search</h2> | |
| <div class="row"> | |
| <label>Query <input id="yt_q" placeholder="e.g. phonk"></label> | |
| <label>Filter | |
| <select id="yt_filter"> | |
| <option value="videos">videos</option> | |
| <option value="all">all</option> | |
| <option value="channels">channels</option> | |
| <option value="playlists">playlists</option> | |
| </select> | |
| </label> | |
| <label>Continuation <input id="yt_ct" placeholder="continuationToken"></label> | |
| <button onclick="ytSearch(true)">Search</button> | |
| <button class="secondary" onclick="ytSearch(false)">Next page ▶</button> | |
| </div> | |
| <code class="url" id="yt_url"></code> | |
| <pre id="yt_json">{}</pre> | |
| </div> | |
| <div class="card"> | |
| <h2>YouTube: /api/yt_playlists</h2> | |
| <div class="row"> | |
| <label>Query <input id="ytp_q" placeholder="e.g. phonk"></label> | |
| <label>Continuation <input id="ytp_ct" placeholder="continuationToken"></label> | |
| <button onclick="ytPlaylists(true)">Search</button> | |
| <button class="secondary" onclick="ytPlaylists(false)">Next page ▶</button> | |
| </div> | |
| <code class="url" id="ytp_url"></code> | |
| <pre id="ytp_json">{}</pre> | |
| </div> | |
| <div class="card"> | |
| <h2>Notes</h2> | |
| <ul> | |
| <li>Continuation tokens are auto-populated into the input after a response. Click "Next page" to fetch the next page.</li> | |
| <li>This page targets the API served by the current origin (location.origin).</li> | |
| <li>Open DevTools (Network tab) if you need to inspect raw requests.</li> | |
| </ul> | |
| </div> | |
| </body> | |
| </html> | |