From 5cabceede2b2e6a979f961ed8aa212d2fd3f9675 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 11 Jan 2026 21:09:38 +0100 Subject: [PATCH] Fix download: proxy audio through server to bypass IP lock --- mod.ts | 52 +++++++++++++++++++++++++++++++++------------------- ui.ts | 23 +++++------------------ 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/mod.ts b/mod.ts index 6d6ece6..190e4f2 100644 --- a/mod.ts +++ b/mod.ts @@ -261,32 +261,34 @@ async function handler(req: Request): Promise { const id = searchParams.get("id"); const title = searchParams.get("title") || "audio"; const artist = searchParams.get("artist") || ""; - const redirect = searchParams.get("redirect") !== "0"; if (!id) return error("Missing id"); - // Get stream URL - try piped first, then invidious + // Get stream URL - try invidious first (better proxy support) let audioUrl = null; let contentType = "audio/mp4"; + let instance = ""; - const piped = await fetchFromPiped(id); - if (piped.success && piped.streamingUrls) { - const audio = piped.streamingUrls.find((s: any) => + const invidious = await fetchFromInvidious(id); + if (invidious.success && invidious.streamingUrls) { + const audio = invidious.streamingUrls.find((s: any) => s.type?.includes("audio/mp4") && s.audioQuality === "AUDIO_QUALITY_MEDIUM" - ) || piped.streamingUrls.find((s: any) => s.type?.includes("audio")); + ) || invidious.streamingUrls.find((s: any) => s.type?.includes("audio")); if (audio) { audioUrl = audio.url; + instance = invidious.instance || ""; contentType = audio.type?.split(";")[0] || "audio/mp4"; } } if (!audioUrl) { - const invidious = await fetchFromInvidious(id); - if (invidious.success && invidious.streamingUrls) { - const audio = invidious.streamingUrls.find((s: any) => + const piped = await fetchFromPiped(id); + if (piped.success && piped.streamingUrls) { + const audio = piped.streamingUrls.find((s: any) => s.type?.includes("audio/mp4") && s.audioQuality === "AUDIO_QUALITY_MEDIUM" - ) || invidious.streamingUrls.find((s: any) => s.type?.includes("audio")); + ) || piped.streamingUrls.find((s: any) => s.type?.includes("audio")); if (audio) { audioUrl = audio.url; + instance = piped.instance || ""; contentType = audio.type?.split(";")[0] || "audio/mp4"; } } @@ -297,19 +299,31 @@ async function handler(req: Request): Promise { const ext = contentType.includes("webm") ? ".webm" : ".m4a"; const filename = `${artist ? artist + " - " : ""}${title}`.replace(/[<>:"/\\|?*]/g, "").trim() + ext; - // Redirect to audio URL - browser will handle download - if (redirect) { - return new Response(null, { - status: 302, + // Proxy the download through our server + try { + const response = await fetch(audioUrl, { headers: { - "Location": audioUrl, - ...corsHeaders, + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "Accept": "*/*", + "Referer": instance || "https://www.youtube.com/", }, }); - } - // Return URL for client to handle - return json({ success: true, url: audioUrl, filename, contentType }); + if (!response.ok) { + return json({ success: false, error: `Failed to fetch audio: ${response.status}` }, 502); + } + + return new Response(response.body, { + headers: { + "Content-Type": contentType, + "Content-Disposition": `attachment; filename="${encodeURIComponent(filename)}"`, + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Content-Disposition", + }, + }); + } catch (err) { + return json({ success: false, error: "Download failed: " + String(err) }, 500); + } } // ============ LYRICS & INFO ============ diff --git a/ui.ts b/ui.ts index 82312fa..6ed16b2 100644 --- a/ui.ts +++ b/ui.ts @@ -327,25 +327,12 @@ function render(f,append){ else el.innerHTML=html; } -async function download(i){ +function download(i){ var s=songs[i];if(!s||!s.videoId)return; - var btn=document.querySelectorAll('.dl-btn')[i]; - if(btn){btn.textContent='...';btn.disabled=true;} - try{ - var res=await fetch('/api/download?id='+s.videoId+'&redirect=0'); - var data=await res.json(); - if(!data.success||!data.url){alert('Download failed: '+(data.error||'No URL'));return;} - // Create download link - var a=document.createElement('a'); - a.href=data.url; - a.download=data.filename||'audio.m4a'; - a.target='_blank'; - a.rel='noopener'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - }catch(e){alert('Download error');} - finally{if(btn){btn.textContent='↓';btn.disabled=false;}} + var title=encodeURIComponent(s.title||'audio'); + var artist=encodeURIComponent(s.artists?.map(a=>a.name).join(', ')||''); + // Open download in new tab - server will proxy the audio + window.open('/api/download?id='+s.videoId+'&title='+title+'&artist='+artist,'_blank'); } function play(i){