Skip to main content

tongbal_api/
client.rs

1use std::sync::LazyLock;
2
3use asknothingx2_util::{
4    api::{HeaderMut, preset},
5    oauth::{AccessToken, ClientId, ClientSecret},
6};
7use serde::de::DeserializeOwned;
8use url::Url;
9
10use crate::Error;
11
12const CHZZK_API_URL: &str = "https://openapi.chzzk.naver.com/open/v1";
13
14static BASE_URL: LazyLock<Url> = LazyLock::new(|| url::Url::parse(CHZZK_API_URL).unwrap());
15
16pub trait BaseClient {
17    fn http_client(&self) -> &reqwest::Client;
18    fn base_url(&self) -> Url;
19}
20
21#[derive(Debug, Clone)]
22pub struct UserClient {
23    url: Url,
24    client: reqwest::Client,
25}
26
27#[derive(Debug, Clone)]
28pub struct AppClient {
29    url: Url,
30    client: reqwest::Client,
31}
32
33impl UserClient {
34    pub fn new(access_token: AccessToken) -> Self {
35        let mut client = preset::rest_api(concat!(
36            env!("CARGO_PKG_NAME"),
37            "/",
38            env!("CARGO_PKG_VERSION")
39        ));
40
41        client
42            .default_headers_mut()
43            .bearer_token(access_token.secret())
44            .content_type_json();
45
46        Self {
47            url: BASE_URL.clone(),
48            client: client.build().unwrap(),
49        }
50    }
51
52    pub fn with_client_builder(access_token: AccessToken, builder: reqwest::ClientBuilder) -> Self {
53        use reqwest::header;
54        let mut headers = header::HeaderMap::new();
55        HeaderMut::new(&mut headers).bearer_token(access_token.secret());
56
57        let client = builder.default_headers(headers);
58
59        Self {
60            url: BASE_URL.clone(),
61            client: client.build().unwrap(),
62        }
63    }
64
65    pub fn with_url(mut self, url: Url) -> Self {
66        self.url = url;
67        self
68    }
69}
70
71impl AppClient {
72    pub fn new(client_id: ClientId, client_secret: ClientSecret) -> Self {
73        let mut client = preset::rest_api(concat!(
74            env!("CARGO_PKG_NAME"),
75            "/",
76            env!("CARGO_PKG_VERSION")
77        ));
78
79        client
80            .default_headers_mut()
81            .client_id(&client_id)
82            .expect("client_id must be valid ASCII")
83            .client_secret(&client_secret)
84            .expect("client_secret must be valid ASCII")
85            .content_type_json();
86
87        Self {
88            url: BASE_URL.clone(),
89            client: client.build().unwrap(),
90        }
91    }
92
93    pub fn with_client_builder(
94        client_id: ClientId,
95        client_secret: ClientSecret,
96        builder: reqwest::ClientBuilder,
97    ) -> Self {
98        use reqwest::header;
99        let mut headers = header::HeaderMap::new();
100        HeaderMut::new(&mut headers)
101            .client_id(&client_id)
102            .expect("client_id must be valid ASCII")
103            .client_secret(&client_secret)
104            .expect("client_secret must be valid ASCII")
105            .content_type_json();
106
107        let client = builder.default_headers(headers);
108
109        Self {
110            url: BASE_URL.clone(),
111            client: client.build().unwrap(),
112        }
113    }
114
115    pub fn with_url(mut self, url: Url) -> Self {
116        self.url = url;
117        self
118    }
119}
120
121impl BaseClient for UserClient {
122    fn http_client(&self) -> &reqwest::Client {
123        &self.client
124    }
125
126    fn base_url(&self) -> Url {
127        self.url.clone()
128    }
129}
130
131impl BaseClient for AppClient {
132    fn http_client(&self) -> &reqwest::Client {
133        &self.client
134    }
135
136    fn base_url(&self) -> Url {
137        self.url.clone()
138    }
139}
140
141pub async fn json<T: DeserializeOwned>(req: reqwest::RequestBuilder) -> Result<T, Error> {
142    execute(req).await?.json().await.map_err(Error::from)
143}
144
145pub async fn no_content(req: reqwest::RequestBuilder) -> Result<(), Error> {
146    execute(req).await?;
147    Ok(())
148}
149
150pub async fn execute(req: reqwest::RequestBuilder) -> Result<reqwest::Response, Error> {
151    let resp = req.send().await.map_err(Error::from)?;
152    if resp.status().is_success() {
153        Ok(resp)
154    } else {
155        let code = resp.status().as_u16();
156        let v = resp.bytes().await?;
157        let message = String::from_utf8_lossy(&v).into_owned();
158        Err(Error::APIError { code, message })
159    }
160}