downloads-khinsider-com-dl

Download all songs at once from downloads.khinsider.com
git clone https://git.ea.contact/downloads-khinsider-com-dl
Log | Files | Refs | README

commit b43351e5afe73c57a917be308645939439eb14b3
parent 58a3978b0b5f52143a465ebd31d64d9f06e63a86
Author: egor-achkasov <eaachkasov@gmail.com>
Date:   Thu, 12 Mar 2026 21:53:05 +0000

Fix FLAC resolution

Diffstat:
Msrc/lib/lib.rs | 41+++++++++++++++++++++++++++++++++--------
1 file changed, 33 insertions(+), 8 deletions(-)

diff --git a/src/lib/lib.rs b/src/lib/lib.rs @@ -16,21 +16,21 @@ pub async fn run(config: Config, tx: Sender<Event>) -> Result<()> { let main_page = client.get(&config.url).send().await.context("Failed to get main page")?; tx.send(Event::GetPageCompleted)?; - let (name, image_urls, track_urls) = parse_page(main_page, config.images, config.flac).await?; + let (name, image_urls, track_urls) = parse_page(main_page, config.images).await?; let dest_dir = std::path::Path::new(&name).to_path_buf(); std::fs::create_dir_all(&dest_dir)?; let mut joinset = tokio::task::JoinSet::new(); for url in track_urls { - joinset.spawn(download(client.clone(), url, dest_dir.clone(), tx.clone())); + joinset.spawn(download(client.clone(), url, dest_dir.clone(), tx.clone(), config.flac)); } if config.images { let dest_dir = std::path::Path::new(&name).join("images"); std::fs::create_dir_all(&dest_dir)?; for url in image_urls { - joinset.spawn(download(client.clone(), url, dest_dir.clone(), tx.clone())); + joinset.spawn(download(client.clone(), url, dest_dir.clone(), tx.clone(), false)); } } @@ -44,7 +44,6 @@ pub async fn run(config: Config, tx: Sender<Event>) -> Result<()> { async fn parse_page( main_page: reqwest::Response, images: bool, - flac: bool ) -> Result<(String, Vec<Url>, Vec<Url>)> { let base_url = main_page.url().clone(); let html = main_page.text().await.context("Failed to read page body")?; @@ -75,9 +74,7 @@ async fn parse_page( Vec::new() }; - let td = if flac { 6 } else { 5 }; - let track_sel_str = format!("#songlist tbody tr td:nth-child({td}) a"); - let track_sel = scraper::Selector::parse(&track_sel_str).unwrap(); + let track_sel = scraper::Selector::parse("#songlist tbody tr td:nth-child(5) a").unwrap(); let track_urls = document .select(&track_sel) .filter_map(|el| el.value().attr("href")) @@ -108,11 +105,28 @@ fn percent_decode(s: &str) -> String { String::from_utf8_lossy(&bytes).into_owned() } +async fn resolve_flac_url(client: &reqwest::Client, track_page_url: &Url) -> Result<Url> { + let html = client.get(track_page_url.clone()).send().await?.text().await?; + let document = scraper::Html::parse_document(&html); + let sel = scraper::Selector::parse( + "#pageContent > p:nth-child(10) > a" + ).map_err(|e| anyhow::anyhow!("Failed to parse FLAC link selector: {e}"))?; + let href = document + .select(&sel) + .next() + .ok_or_else(|| anyhow::anyhow!("FLAC link not found on page: {}", track_page_url))? + .value() + .attr("href") + .ok_or_else(|| anyhow::anyhow!("FLAC link has no href on page: {}", track_page_url))?; + track_page_url.join(href).context("Failed to parse FLAC URL") +} + async fn download( client: reqwest::Client, url: Url, dest_dir: std::path::PathBuf, tx: Sender<Event>, + flac: bool, ) -> Result<()> { async fn dl(client: reqwest::Client, url: &Url, dest_dir: std::path::PathBuf) -> Result<()> { let filename = url @@ -128,7 +142,18 @@ async fn download( } tx.send(Event::DlStarted { url: url.to_string() })?; - match dl(client, &url, dest_dir).await { + let download_url = if flac { + match resolve_flac_url(&client, &url).await { + Ok(u) => u, + Err(e) => { + tx.send(Event::DlFailed { error: e })?; + return Ok(()); + } + } + } else { + url.clone() + }; + match dl(client, &download_url, dest_dir).await { Err(e) => tx.send(Event::DlFailed { error: e })?, Ok(()) => tx.send(Event::DlCompleted { url: url.to_string() })? };