1use std::future::Future;
4use std::marker::PhantomData;
5use std::pin::Pin;
6
7use serde::de::DeserializeOwned;
19
20use crate::builder::types::{ConfQueryInner, Resp, RoomQueryInner};
21use crate::config::StreamConfig;
22use crate::errors::{BoxedError, BuildError};
23
24#[cfg(test)]
25mod tests;
26mod types;
27
28#[cfg(feature = "not-send")]
32pub trait Requester {
33 fn get_json<T: DeserializeOwned>(
35 &self,
36 url: &str,
37 ) -> Pin<Box<dyn Future<Output = Result<T, BoxedError>> + '_>>;
38}
39
40#[cfg(not(feature = "not-send"))]
44pub trait Requester: Send + Sync {
45 fn get_json<T: DeserializeOwned>(
47 &self,
48 url: &str,
49 ) -> Pin<Box<dyn Future<Output = Result<T, BoxedError>> + Send + '_>>;
50}
51
52#[doc(hidden)]
53pub enum BF {}
54
55#[doc(hidden)]
56pub enum BN {}
57
58#[derive(Debug)]
68pub struct ConfigBuilder<H, R, U, T, S> {
69 http: H,
70 room_id: Option<u64>,
71 uid: Option<u64>,
72 token: Option<String>,
73 servers: Option<Vec<String>>,
74 __marker: PhantomData<(R, U, T, S)>,
75}
76
77impl<H: Default> ConfigBuilder<H, BN, BN, BN, BN> {
78 #[allow(clippy::new_without_default)]
80 #[must_use]
81 pub fn new() -> Self {
82 Self::new_with_client(H::default())
83 }
84}
85
86impl<H> ConfigBuilder<H, BN, BN, BN, BN> {
87 #[must_use]
89 pub const fn new_with_client(client: H) -> Self {
90 Self {
91 http: client,
92 room_id: None,
93 uid: None,
94 token: None,
95 servers: None,
96 __marker: PhantomData,
97 }
98 }
99}
100
101impl<H, R, U, T, S> ConfigBuilder<H, R, U, T, S> {
102 #[allow(clippy::missing_const_for_fn)] fn cast<R2, U2, T2, S2>(self) -> ConfigBuilder<H, R2, U2, T2, S2> {
104 ConfigBuilder {
105 http: self.http,
106 room_id: self.room_id,
107 uid: self.uid,
108 token: self.token,
109 servers: self.servers,
110 __marker: PhantomData,
111 }
112 }
113}
114
115impl<H, R, U, T, S> ConfigBuilder<H, R, U, T, S> {
116 #[must_use]
117 pub fn room_id(mut self, room_id: u64) -> ConfigBuilder<H, BF, U, T, S> {
118 self.room_id = Some(room_id);
119 self.cast()
120 }
121 #[must_use]
122 pub fn uid(mut self, uid: u64) -> ConfigBuilder<H, R, BF, T, S> {
123 self.uid = Some(uid);
124 self.cast()
125 }
126 #[must_use]
127 pub fn token(mut self, token: &str) -> ConfigBuilder<H, R, U, BF, S> {
128 self.token = Some(token.to_string());
129 self.cast()
130 }
131
132 #[must_use]
133 pub fn servers(mut self, servers: &[String]) -> ConfigBuilder<H, R, U, T, BF> {
134 self.servers = Some(servers.to_vec());
135 self.cast()
136 }
137}
138
139impl<H, R, U, T, S> ConfigBuilder<H, R, U, T, S>
140where
141 H: Requester,
142 R: Send + Sync,
143 U: Send + Sync,
144 T: Send + Sync,
145 S: Send + Sync,
146{
147 pub async fn by_uid(mut self, uid: u64) -> Result<ConfigBuilder<H, BF, BF, T, S>, BuildError> {
152 let resp: Resp<RoomQueryInner> = self
153 .http
154 .get_json(&*format!(
155 "https://api.live.bilibili.com/bili/living_v2/{}",
156 uid
157 ))
158 .await
159 .map_err(BuildError)?;
160 let room_id = resp.room_id();
161
162 self.room_id = Some(room_id);
163 self.uid = Some(uid);
164 Ok(self.cast())
165 }
166
167 pub async fn fetch_conf(mut self) -> Result<ConfigBuilder<H, R, U, BF, BF>, BuildError> {
172 let resp: Resp<ConfQueryInner> = self
173 .http
174 .get_json("https://api.live.bilibili.com/room/v1/Danmu/getConf")
175 .await
176 .map_err(BuildError)?;
177
178 self.token = Some(resp.token().to_string());
179 self.servers = Some(resp.servers());
180 Ok(self.cast())
181 }
182}
183
184impl<H> ConfigBuilder<H, BF, BF, BF, BF> {
185 #[allow(clippy::missing_panics_doc)]
187 pub fn build(self) -> StreamConfig {
188 StreamConfig::new(
190 self.room_id.unwrap(),
191 self.uid.unwrap(),
192 self.token.unwrap(),
193 self.servers.unwrap(),
194 )
195 }
196}