From bab630224772414663db41e796e5e2d9215bad1d Mon Sep 17 00:00:00 2001 From: Jorge Vargas Date: Mon, 10 Mar 2025 19:35:55 -0600 Subject: [PATCH] Implement individual song download --- src/components/Album.tsx | 30 ++++++++++++++++++++++++++---- src/components/Downloader.tsx | 4 ++-- src/pages/index.astro | 9 +++++++++ src/utils/search.ts | 28 ++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/components/Album.tsx b/src/components/Album.tsx index d943d7a..a5ada66 100644 --- a/src/components/Album.tsx +++ b/src/components/Album.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { getTracks } from 'utils/search' +import { getDownloadUrl, getTracks } from 'utils/search' import type { AlbumRow, TrackRow } from 'utils/types' import Loader from './Loader' @@ -49,13 +49,13 @@ function TrackList(props: { url: string; open: boolean; lastReqRef: React.RefObj {tracks && open ? (
{tracks.map((t, i) => ( -
+
{(i + 1).toString().padStart(2, '0').slice(-2)}. {t.name} FLAC/MP3 - +
))}
@@ -65,5 +65,27 @@ function TrackList(props: { url: string; open: boolean; lastReqRef: React.RefObj } function TrackDownload(props: { url: string; lastReqRef: React.RefObject }) { - return + const { url, lastReqRef } = props + const [downloadUrl, setDownloadUrl] = useState() + const [loading, setLoading] = useState(false) + + async function fetchUrl() { + try { + setLoading(true) + const dlUrl = await getDownloadUrl(url, lastReqRef) + setDownloadUrl(dlUrl) + } finally { + setLoading(false) + } + } + + return downloadUrl ? ( + + Download + + ) : ( + + ) } diff --git a/src/components/Downloader.tsx b/src/components/Downloader.tsx index 13feb1c..9f2aaaa 100644 --- a/src/components/Downloader.tsx +++ b/src/components/Downloader.tsx @@ -53,8 +53,8 @@ export default function Downloader() {
) : null}
- {results.map((r) => ( - + {results.map((r, i) => ( + ))}
diff --git a/src/pages/index.astro b/src/pages/index.astro index c7ad26f..a4a7a43 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -117,6 +117,7 @@ import Downloader from '../components/Downloader' border-radius: 4px; cursor: pointer; transition: background 0.2s; + font-size: 13px; } .download-btn:hover { background-color: #218838; @@ -153,12 +154,20 @@ import Downloader from '../components/Downloader' display: flex; gap: 8px; } + a.download-btn { + text-decoration: none; + } .download-btn.flac { background-color: #6f42c1; } .download-btn.flac:hover { background-color: #563d7c; } + .download-btn .loader { + height: 8px; + width: 8px; + margin: 2px 8px; + } .results h3 { margin: 0; font-size: 1.1em; diff --git a/src/utils/search.ts b/src/utils/search.ts index 4679e0a..0c87b44 100644 --- a/src/utils/search.ts +++ b/src/utils/search.ts @@ -75,3 +75,31 @@ export async function getTracks(url: string, lastReqRef: React.RefObject return tracks } + +export async function getDownloadUrl(trackPageUrl: string, lastReqRef: React.RefObject) { + const trackPageHtml = await (await politeFetch(trackPageUrl, lastReqRef)).text() + const $ = cheerio.load(trackPageHtml) + + const audioLinks: string[] = [] + + $('a[href$=".mp3"], a[href$=".flac"]').each((index, link) => { + let href = $(link).attr('href') + if (!href) return + + href = decodeURIComponent(href) + if (!href.startsWith('http')) { + href = `https://downloads.khinsider.com${href}` + } + audioLinks.push(href) + }) + + const flacLinks = audioLinks.filter((link) => link.endsWith('.flac')) + if (flacLinks.length > 0) { + return flacLinks[0] + } + + const mp3Links = audioLinks.filter((link) => link.endsWith('.mp3')) + if (mp3Links.length > 0) { + return mp3Links[0] + } +}