unm_engine_kuwo/
api.rs

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" /* 1hr */, 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}