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}