1pub mod typing;
2
3use cached::proc_macro::once;
4use concat_string::concat_string;
5use http::header::{COOKIE, REFERER};
6use http::HeaderValue;
7use reqwest::header::HeaderMap;
8use reqwest::Url;
9use unm_request::build_client;
10use unm_types::Context;
11
12use self::typing::{GetPlayUrlResponse, MusicID, SearchResponse};
13
14pub fn genenate_kw_token() -> String {
15 log::debug!("Generating kw_token…");
16 let charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
17
18 random_string::generate(11, charset)
19}
20
21#[once(time = "3600" , result = true)]
22pub fn construct_header() -> anyhow::Result<HeaderMap> {
23 log::debug!("Constructing header to pass to Kuwo Music…");
24 let token = genenate_kw_token();
25 let mut hm = HeaderMap::with_capacity(3);
26
27 hm.insert(
28 COOKIE,
29 HeaderValue::from_str(&concat_string!("kw_token=", token))?,
30 );
31 hm.insert(REFERER, HeaderValue::from_static("http://www.kuwo.cn/"));
32 hm.insert("csrf", HeaderValue::from_str(&token)?);
33
34 Ok(hm)
35}
36
37pub async fn search_music_by_keyword(
38 keyword: &str,
39 page_number: i32,
40 entries_per_page: i32,
41 ctx: &Context,
42) -> anyhow::Result<SearchResponse> {
43 log::debug!("Searching music in Kuwo by keyword “{keyword}”… [Page {page_number}, {entries_per_page} entries]");
44
45 let client = build_client(ctx.proxy_uri.as_deref())?;
46 let url = Url::parse_with_params(
47 "http://www.kuwo.cn/api/www/search/searchMusicBykeyWord",
48 &[
49 ("key", keyword),
50 ("pn", &page_number.to_string()),
51 ("rn", &entries_per_page.to_string()),
52 ("httpsStatus", "1"),
53 ],
54 )?;
55
56 let response = client.get(url).headers(construct_header()?).send().await?;
57 let json = response.json::<SearchResponse>().await?;
58
59 Ok(json)
60}
61
62pub async fn get_music(mid: MusicID, ctx: &Context) -> anyhow::Result<GetPlayUrlResponse> {
63 log::debug!("Fetch the music with MID “{mid}” from Kuwo Music…");
64
65 let client = build_client(ctx.proxy_uri.as_deref())?;
66 let url = Url::parse_with_params(
67 "http://www.kuwo.cn/api/v1/www/music/playUrl",
68 &[
69 ("mid", mid.to_string().as_str()),
70 ("type", "music"),
71 ("httpsStatus", "1"),
72 ],
73 )?;
74
75 let response = client.get(url).headers(construct_header()?).send().await?;
76 let json = response.json::<GetPlayUrlResponse>().await?;
77
78 Ok(json)
79}
80
81#[cfg(test)]
82mod tests {
83 use concat_string::concat_string;
84 use http::header::{COOKIE, REFERER};
85
86 use super::construct_header;
87
88 #[test]
89 fn construct_header_test() {
90 let h = construct_header().expect("should be able to construct header");
91 let getstr = |k: &str| {
92 h.get(k)
93 .expect("should has cookie")
94 .to_str()
95 .expect("should able to convert to string")
96 };
97
98 let token = getstr(COOKIE.as_str()).replace("kw_token=", "");
99
100 assert_eq!(getstr(COOKIE.as_str()), concat_string!("kw_token=", token));
101 assert_eq!(getstr(REFERER.as_str()), "http://www.kuwo.cn/");
102 assert_eq!(getstr("csrf"), token);
103 }
104}