use reqwest::Method;
use super::eapi;
use super::response::{
EapiSearchResponse, LyricContent, LyricsResponse, SearchResponse, SyllableLyricsResponse,
};
use crate::error::SearchError;
use crate::providers::web::base_api;
const REFERER: &str = "https://music.163.com/";
const SEARCH_URL: &str = "http://music.163.com/api/search/get/web";
const SEARCH_NEW_URL: &str = "https://interface.music.163.com/eapi/cloudsearch/pc";
const SEARCH_TYPE_SONG: &str = "1";
const SYLLABLE_LYRICS_URL: &str = "https://interface3.music.163.com/eapi/song/lyric/v1";
fn standard_headers() -> Vec<(&'static str, &'static str)> {
vec![
("Referer", REFERER),
("Cookie", base_api::COOKIE),
("User-Agent", base_api::USER_AGENT),
]
}
pub(crate) async fn search(keyword: &str) -> Result<SearchResponse, SearchError> {
let url = format!(
"{SEARCH_URL}?csrf_token=hlpretag=&hlposttag=&s={}&type={SEARCH_TYPE_SONG}&offset=0&total=true&limit=20",
urlencoding::encode(keyword)
);
let response = base_api::send(Method::GET, &url, &standard_headers()).await?;
base_api::json(response).await
}
pub(crate) async fn search_new(keyword: &str) -> Result<SearchResponse, SearchError> {
let response = eapi::post(
SEARCH_NEW_URL,
serde_json::json!({
"s": keyword,
"type": SEARCH_TYPE_SONG,
"limit": "30",
"offset": "0",
"total": "true",
}),
)
.await?;
let response: EapiSearchResponse = serde_json::from_str(&response)?;
Ok(response.into())
}
pub async fn get_lyrics(song_id: i64) -> Result<(Option<String>, Option<String>), SearchError> {
let url = format!(
"https://music.163.com/api/song/lyric?id={}&lv=1&kv=1&tv=-1",
song_id
);
let response = base_api::send(Method::GET, &url, &standard_headers()).await?;
let resp: LyricsResponse = base_api::json(response).await?;
Ok((
resp.lrc.and_then(|l| l.lyric),
resp.tlyric.and_then(|t| t.lyric),
))
}
#[derive(Debug, Clone, Default)]
pub struct SyllableLyrics {
pub yrc: Option<String>,
pub ytlrc: Option<String>,
pub yromalrc: Option<String>,
}
pub async fn get_syllable_lyrics(song_id: i64) -> Result<Option<SyllableLyrics>, SearchError> {
let response = eapi::post(
SYLLABLE_LYRICS_URL,
serde_json::json!({
"id": song_id.to_string(),
"cp": "false",
"lv": "0",
"kv": "0",
"tv": "0",
"rv": "0",
"yv": "0",
"ytv": "0",
"yrv": "0",
"csrf_token": "",
}),
)
.await?;
let response: SyllableLyricsResponse = serde_json::from_str(&response)?;
let lyrics = SyllableLyrics {
yrc: lyric_text(response.yrc),
ytlrc: lyric_text(response.ytlrc),
yromalrc: lyric_text(response.yromalrc),
};
let has_syllable = lyrics.yrc.is_some() || lyrics.ytlrc.is_some() || lyrics.yromalrc.is_some();
Ok(has_syllable.then_some(lyrics))
}
fn lyric_text(content: Option<LyricContent>) -> Option<String> {
content
.and_then(|content| content.lyric)
.filter(|lyric| !lyric.is_empty())
}