feat: 添加单曲信息获取和下载功能

- 新增 aria2 模块,实现下载功能- 新增 single 模块,实现单曲信息获取
- 新增 utils 模块,用于创建请求客户端
This commit is contained in:
2025-08-18 22:02:07 +08:00
commit b5aee397f8
7 changed files with 2071 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/.idea

1959
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "netease-downloader"
version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1.47.1", default-features = false, features = ["rt-multi-thread", "macros", "time"] }
reqwest = { version = "0.12.23", default-features = false, features = ["rustls-tls", "json"] }
serde = { version = "1.0.219", default-features = false, features = ["std", "derive"] }
serde_json = { version = "1.0.142", default-features = false, features = ["std"] }
aria2-ws = "0.5.1"

18
src/aria2.rs Normal file
View File

@@ -0,0 +1,18 @@
use crate::ErrorString;
use aria2_ws::{Client, TaskOptions};
pub async fn download(url: String, path: String) -> Result<(), ErrorString> {
let client = Client::connect("ws://127.0.0.1:16800/jsonrpc", Some("GenshinMinecraft"))
.await
.map_err(|e| format!("无法创建 Client: {e}"))?;
let options = TaskOptions {
out: Some(path),
..Default::default()
};
client
.add_uri(vec![url], Some(options.clone()), None, None)
.await
.map_err(|e| format!("无法添加任务: {e}"))?;
Ok(())
}

12
src/main.rs Normal file
View File

@@ -0,0 +1,12 @@
mod aria2;
mod single;
pub mod utils;
type ErrorString = String;
#[tokio::main]
async fn main() {
let single_songs_info = single::single(448317056).await.unwrap();
println!("{:?}", single_songs_info);
aria2::download(single_songs_info.download_url, format!("{}.flac", single_songs_info.name)).await.unwrap();
}

53
src/single.rs Normal file
View File

@@ -0,0 +1,53 @@
use crate::ErrorString;
use crate::utils::create_reqwest_client;
use serde::{Deserialize, Serialize};
use tokio::task::JoinHandle;
#[derive(Debug)]
pub struct SingleSongsInfo {
pub id: u64,
pub name: String,
pub artist: String,
pub album: String,
pub pic_url: String,
pub download_url: String,
}
#[derive(Serialize, Deserialize)]
struct DetailJson {
name: String,
ar_name: String,
al_name: String,
pic: String,
url: String,
}
pub async fn single(id: u64) -> Result<SingleSongsInfo, ErrorString> {
let client = create_reqwest_client().await?;
let info_handle: JoinHandle<Result<DetailJson, ErrorString>> = tokio::spawn(async move {
let info_request_url =
format!("https://api.kxzjoker.cn/api/163_music?url={id}&level=jymaster&type=json");
client
.post(info_request_url)
.send()
.await
.map_err(|e| format!("无法获取歌曲信息: {e}"))?
.json::<DetailJson>()
.await
.map_err(|e| format!("无法解析歌曲信息: {e}"))
});
let info_result = info_handle
.await
.map_err(|e| format!("歌曲信息获取失败: {e}"))??;
Ok(SingleSongsInfo {
id,
name: info_result.name,
artist: info_result.ar_name,
album: info_result.al_name,
pic_url: info_result.pic,
download_url: info_result.url,
})
}

16
src/utils.rs Normal file
View File

@@ -0,0 +1,16 @@
use crate::ErrorString;
use reqwest::Client;
use std::time::Duration;
use tokio::sync::OnceCell;
static CLIENT: OnceCell<Client> = OnceCell::const_new();
pub async fn create_reqwest_client() -> Result<&'static Client, ErrorString> {
CLIENT
.get_or_try_init(|| async {
Client::builder()
.timeout(Duration::from_secs(5))
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36")
.build()
})
.await.map_err(|e| format!("无法创建 Client: {e}"))
}