Fix download: proxy audio through server to bypass IP lock

This commit is contained in:
Your Name
2026-01-11 21:09:38 +01:00
parent 3afb843316
commit 5cabceede2
2 changed files with 38 additions and 37 deletions

58
mod.ts
View File

@@ -261,25 +261,13 @@ async function handler(req: Request): Promise<Response> {
const id = searchParams.get("id"); const id = searchParams.get("id");
const title = searchParams.get("title") || "audio"; const title = searchParams.get("title") || "audio";
const artist = searchParams.get("artist") || ""; const artist = searchParams.get("artist") || "";
const redirect = searchParams.get("redirect") !== "0";
if (!id) return error("Missing id"); 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 audioUrl = null;
let contentType = "audio/mp4"; let contentType = "audio/mp4";
let instance = "";
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"
) || piped.streamingUrls.find((s: any) => s.type?.includes("audio"));
if (audio) {
audioUrl = audio.url;
contentType = audio.type?.split(";")[0] || "audio/mp4";
}
}
if (!audioUrl) {
const invidious = await fetchFromInvidious(id); const invidious = await fetchFromInvidious(id);
if (invidious.success && invidious.streamingUrls) { if (invidious.success && invidious.streamingUrls) {
const audio = invidious.streamingUrls.find((s: any) => const audio = invidious.streamingUrls.find((s: any) =>
@@ -287,6 +275,20 @@ async function handler(req: Request): Promise<Response> {
) || invidious.streamingUrls.find((s: any) => s.type?.includes("audio")); ) || invidious.streamingUrls.find((s: any) => s.type?.includes("audio"));
if (audio) { if (audio) {
audioUrl = audio.url; audioUrl = audio.url;
instance = invidious.instance || "";
contentType = audio.type?.split(";")[0] || "audio/mp4";
}
}
if (!audioUrl) {
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"
) || piped.streamingUrls.find((s: any) => s.type?.includes("audio"));
if (audio) {
audioUrl = audio.url;
instance = piped.instance || "";
contentType = audio.type?.split(";")[0] || "audio/mp4"; contentType = audio.type?.split(";")[0] || "audio/mp4";
} }
} }
@@ -297,19 +299,31 @@ async function handler(req: Request): Promise<Response> {
const ext = contentType.includes("webm") ? ".webm" : ".m4a"; const ext = contentType.includes("webm") ? ".webm" : ".m4a";
const filename = `${artist ? artist + " - " : ""}${title}`.replace(/[<>:"/\\|?*]/g, "").trim() + ext; const filename = `${artist ? artist + " - " : ""}${title}`.replace(/[<>:"/\\|?*]/g, "").trim() + ext;
// Redirect to audio URL - browser will handle download // Proxy the download through our server
if (redirect) { try {
return new Response(null, { const response = await fetch(audioUrl, {
status: 302,
headers: { headers: {
"Location": audioUrl, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
...corsHeaders, "Accept": "*/*",
"Referer": instance || "https://www.youtube.com/",
}, },
}); });
if (!response.ok) {
return json({ success: false, error: `Failed to fetch audio: ${response.status}` }, 502);
} }
// Return URL for client to handle return new Response(response.body, {
return json({ success: true, url: audioUrl, filename, contentType }); 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 ============ // ============ LYRICS & INFO ============

23
ui.ts
View File

@@ -327,25 +327,12 @@ function render(f,append){
else el.innerHTML=html; else el.innerHTML=html;
} }
async function download(i){ function download(i){
var s=songs[i];if(!s||!s.videoId)return; var s=songs[i];if(!s||!s.videoId)return;
var btn=document.querySelectorAll('.dl-btn')[i]; var title=encodeURIComponent(s.title||'audio');
if(btn){btn.textContent='...';btn.disabled=true;} var artist=encodeURIComponent(s.artists?.map(a=>a.name).join(', ')||'');
try{ // Open download in new tab - server will proxy the audio
var res=await fetch('/api/download?id='+s.videoId+'&redirect=0'); window.open('/api/download?id='+s.videoId+'&title='+title+'&artist='+artist,'_blank');
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;}}
} }
function play(i){ function play(i){