mod model;
use super::encrypt::Crypto;
use anyhow::{anyhow, bail, Result};
use model::{to_lyric, to_lyric_id_accesskey, to_pic_url, to_song_info, to_song_url};
use std::io::Read;
use std::time::Duration;
use ureq::{Agent, AgentBuilder};
static URL_SEARCH_KUGOU: &str = "http://mobilecdn.kugou.com/api/v3/search/song";
static URL_LYRIC_SEARCH_KUGOU: &str = "http://krcs.kugou.com/search";
static URL_LYRIC_DOWNLOAD_KUGOU: &str = "http://lyrics.kugou.com/download";
static URL_SONG_DOWNLOAD_KUGOU: &str = "http://www.kugou.com/yy/index.php?r=play/getdata";
pub struct Api {
client: Agent,
}
impl Api {
pub fn new() -> Self {
let client = AgentBuilder::new().timeout(Duration::from_secs(10)).build();
Self { client }
}
pub fn search(&self, keywords: &str, types: u32, offset: u16, limit: u16) -> Result<String> {
let result = self
.client
.post(URL_SEARCH_KUGOU)
.set("Referer", "https://m.music.migu.cn")
.query("format", "json")
.query("showtype", &1.to_string())
.query("keyword", keywords)
.query("page", &offset.to_string())
.query("pagesize", &limit.to_string())
.query("showtype", &1.to_string())
.call()?
.into_string()?;
match types {
1 => {
let song_info = to_song_info(&result).ok_or_else(|| anyhow!("Search Error"))?;
let song_info_string = serde_json::to_string(&song_info)?;
Ok(song_info_string)
}
_ => bail!("None Error"),
}
}
pub fn song_lyric(&self, music_id: &str) -> Result<String> {
let result = self
.client
.get(URL_LYRIC_SEARCH_KUGOU)
.query("keyword", "%20-%20")
.query("ver", "1")
.query("hash", music_id)
.query("client", "mobi")
.query("man", "yes")
.call()?
.into_string()?;
let (accesskey, id) =
to_lyric_id_accesskey(&result).ok_or_else(|| anyhow!("Search Error"))?;
let result = self
.client
.get(URL_LYRIC_DOWNLOAD_KUGOU)
.query("charset", "utf8")
.query("accesskey", &accesskey)
.query("id", &id)
.query("client", "mobi")
.query("fmt", "lrc")
.query("ver", "1")
.call()?
.into_string()?;
to_lyric(&result).ok_or_else(|| anyhow!("Search Error"))
}
pub fn song_url(&self, id: &str, album_id: &str) -> Result<String> {
let kg_mid = Crypto::alpha_lowercase_random_bytes(32);
let result = self
.client
.get(URL_SONG_DOWNLOAD_KUGOU)
.set("Cookie", format!("kg_mid={}", kg_mid).as_str())
.query("hash", id)
.query("album_id", album_id)
.call()?
.into_string()?;
to_song_url(&result).ok_or_else(|| anyhow!("Search Error"))
}
pub fn pic(&self, id: &str, album_id: &str) -> Result<Vec<u8>> {
let kg_mid = Crypto::alpha_lowercase_random_bytes(32);
let result = self
.client
.get(URL_SONG_DOWNLOAD_KUGOU)
.set("Cookie", format!("kg_mid={}", kg_mid).as_str())
.query("hash", id)
.query("album_id", album_id)
.call()?
.into_string()?;
let url = to_pic_url(&result).ok_or_else(|| anyhow!("Search Error"))?;
let result = self.client.get(&url).call()?;
let mut bytes: Vec<u8> = Vec::new();
result.into_reader().read_to_end(&mut bytes)?;
Ok(bytes)
}
}