lavalink_rs/
http.rs

1use crate::error::LavalinkResult;
2use crate::model::*;
3
4use std::sync::Arc;
5
6use ::http::{uri::InvalidUri, Method, Uri};
7use http_body_util::BodyExt;
8use hyper::{body::Buf, Request};
9use std::io::Read;
10
11#[derive(Debug, Clone)]
12#[cfg_attr(feature = "python", pyo3::pyclass)]
13pub struct Http {
14    pub authority: String,
15    pub rest_address: String,
16    pub rest_address_versionless: String,
17    pub headers: ::http::header::HeaderMap,
18    pub request_client: Arc<
19        hyper_util::client::legacy::Client<
20            crate::HttpsConnector,
21            http_body_util::Full<bytes::Bytes>,
22        >,
23    >,
24}
25
26impl Http {
27    /// Makes an HTTP/1.1 request using Hyper to endpoints that return deserializable data.
28    pub async fn request<R: serde::de::DeserializeOwned, T: serde::Serialize + ?Sized, U>(
29        &self,
30        method: Method,
31        uri: U,
32        data: Option<&T>,
33    ) -> LavalinkResult<R>
34    where
35        Uri: TryFrom<U>,
36        <Uri as TryFrom<U>>::Error: Into<::http::Error>,
37    {
38        let mut request_builder = Request::builder().method(method).uri(uri);
39
40        {
41            *request_builder.headers_mut().unwrap() = self.headers.clone()
42        }
43
44        request_builder = request_builder.header(::http::header::HOST, &self.authority);
45
46        let request = if let Some(data) = data {
47            request_builder =
48                request_builder.header(::http::header::CONTENT_TYPE, "application/json");
49            request_builder.body(http_body_util::Full::from(serde_json::to_vec(data)?))?
50        } else {
51            request_builder.body(http_body_util::Full::default())?
52        };
53
54        let response = self.request_client.request(request).await?;
55
56        let raw_body = response.collect().await?.aggregate();
57        let body = serde_json::from_reader(raw_body.reader())?;
58
59        Ok(body)
60    }
61
62    /// Makes an HTTP/1.1 request using Hyper to endpoints that return text data.
63    pub async fn raw_request<T: serde::Serialize + ?Sized, U>(
64        &self,
65        method: Method,
66        uri: U,
67        data: Option<&T>,
68    ) -> LavalinkResult<String>
69    where
70        Uri: TryFrom<U>,
71        <Uri as TryFrom<U>>::Error: Into<::http::Error>,
72    {
73        let mut request_builder = Request::builder().method(method).uri(uri);
74
75        {
76            *request_builder.headers_mut().unwrap() = self.headers.clone()
77        }
78
79        request_builder = request_builder.header(::http::header::HOST, &self.authority);
80
81        let request = if let Some(data) = data {
82            request_builder =
83                request_builder.header(::http::header::CONTENT_TYPE, "application/json");
84            request_builder.body(http_body_util::Full::from(serde_json::to_vec(data)?))?
85        } else {
86            request_builder.body(http_body_util::Full::default())?
87        };
88
89        let response = self.request_client.request(request).await?;
90
91        let mut body = "".to_string();
92        let raw_body = response.collect().await?.aggregate();
93        raw_body.reader().read_to_string(&mut body)?;
94
95        Ok(body)
96    }
97
98    /// Convert a path and query to a uri that points to the lavalink server.
99    pub fn path_to_uri(&self, path: &str, with_version: bool) -> Result<Uri, InvalidUri> {
100        if with_version {
101            format!("{}{}", self.rest_address, path).try_into()
102        } else {
103            format!("{}{}", self.rest_address_versionless, path).try_into()
104        }
105    }
106
107    /// Destroys the player for this guild in this session.
108    pub async fn delete_player(
109        &self,
110        guild_id: impl Into<GuildId>,
111        session_id: &str,
112    ) -> LavalinkResult<()> {
113        let guild_id = guild_id.into();
114
115        self.raw_request(
116            Method::DELETE,
117            self.path_to_uri(
118                &format!("/sessions/{}/players/{}", session_id, guild_id.0),
119                true,
120            )?,
121            None::<&()>,
122        )
123        .await?;
124
125        Ok(())
126    }
127
128    /// Updates or creates the player for this guild.
129    pub async fn update_player(
130        &self,
131        guild_id: impl Into<GuildId>,
132        session_id: &str,
133        data: &http::UpdatePlayer,
134        no_replace: bool,
135    ) -> LavalinkResult<player::Player> {
136        let guild_id = guild_id.into();
137
138        let uri = self.path_to_uri(
139            &format!(
140                "/sessions/{}/players/{}?noReplace={}",
141                session_id, guild_id.0, no_replace
142            ),
143            true,
144        )?;
145
146        let response = self
147            .request::<crate::error::RequestResult<_>, _, _>(Method::PATCH, uri, Some(data))
148            .await?
149            .to_result()?;
150
151        Ok(response)
152    }
153
154    /// Updates the session with the resuming state and timeout.
155    pub async fn set_resuming_state(
156        &self,
157        session_id: &str,
158        resuming_state: &http::ResumingState,
159    ) -> LavalinkResult<http::ResumingState> {
160        let response = self
161            .request::<crate::error::RequestResult<_>, _, _>(
162                Method::PATCH,
163                self.path_to_uri(&format!("/sessions/{}", session_id), true)?,
164                Some(resuming_state),
165            )
166            .await?
167            .to_result()?;
168
169        Ok(response)
170    }
171
172    /// Resolves audio tracks for use with the `update_player` endpoint.
173    ///
174    /// # Parameters
175    ///
176    /// - `identifier`: A track identifier.
177    ///  - Can be a url: "https://youtu.be/watch?v=DrM2lo6B04I"
178    ///  - A unique identifier: "DrM2lo6B04I"
179    ///  - A search: "ytsearch:Ne Obliviscaris - Forget Not"
180    pub async fn load_tracks(&self, identifier: &str) -> LavalinkResult<track::Track> {
181        let uri = self.path_to_uri(
182            &format!("/loadtracks?identifier={}", urlencoding::encode(identifier)),
183            true,
184        )?;
185
186        let response = self
187            .request::<crate::error::RequestResult<track::Track>, _, _>(
188                Method::GET,
189                uri,
190                None::<&()>,
191            )
192            .await?
193            .to_result()?;
194
195        match response.data {
196            Some(track::TrackLoadData::Error(why)) => Err(why.into()),
197            _ => Ok(response),
198        }
199    }
200
201    /// Request Lavalink server version.
202    pub async fn version(&self) -> LavalinkResult<String> {
203        let response = self
204            .raw_request(
205                Method::GET,
206                self.path_to_uri("/version", false)?,
207                None::<&()>,
208            )
209            .await?;
210
211        Ok(response)
212    }
213
214    /// Request Lavalink statistics.
215    ///
216    /// NOTE: The frame stats will never be returned.
217    pub async fn stats(&self) -> LavalinkResult<events::Stats> {
218        let response = self
219            .request::<crate::error::RequestResult<_>, _, _>(
220                Method::GET,
221                self.path_to_uri("/stats", true)?,
222                None::<&()>,
223            )
224            .await?
225            .to_result()?;
226
227        Ok(response)
228    }
229
230    /// Request Lavalink server information.
231    pub async fn info(&self) -> LavalinkResult<http::Info> {
232        let response = self
233            .request::<crate::error::RequestResult<_>, _, _>(
234                Method::GET,
235                self.path_to_uri("/info", true)?,
236                None::<&()>,
237            )
238            .await?
239            .to_result()?;
240
241        Ok(response)
242    }
243
244    /// Decode a single track into its info.
245    ///
246    /// # Parameters
247    ///
248    /// - `track`: base64 encoded track data.
249    pub async fn decode_track(&self, track: &str) -> LavalinkResult<track::TrackData> {
250        let uri = self.path_to_uri(
251            &format!("/decodetrack?encodedTrack={}", urlencoding::encode(track)),
252            true,
253        )?;
254
255        let response = self
256            .request::<crate::error::RequestResult<_>, _, _>(Method::GET, uri, None::<&()>)
257            .await?
258            .to_result()?;
259
260        Ok(response)
261    }
262
263    /// Decode multiple tracks into their info.
264    ///
265    /// # Parameters
266    ///
267    /// - `tracks`: base64 encoded tracks.
268    pub async fn decode_tracks(&self, tracks: &[String]) -> LavalinkResult<Vec<track::TrackData>> {
269        let response = self
270            .request::<crate::error::RequestResult<_>, _, _>(
271                Method::POST,
272                self.path_to_uri("/decodetracks", true)?,
273                Some(tracks),
274            )
275            .await?
276            .to_result()?;
277
278        Ok(response)
279    }
280
281    /// Returns the player for this guild in this session.
282    pub async fn get_player(
283        &self,
284        guild_id: impl Into<GuildId>,
285        session_id: &str,
286    ) -> LavalinkResult<player::Player> {
287        let guild_id = guild_id.into();
288
289        let response = self
290            .request::<crate::error::RequestResult<_>, _, _>(
291                Method::GET,
292                self.path_to_uri(
293                    &format!("/sessions/{}/players/{}", session_id, guild_id.0),
294                    true,
295                )?,
296                None::<&()>,
297            )
298            .await?
299            .to_result()?;
300
301        Ok(response)
302    }
303
304    /// Returns a list of players in this specific session.
305    pub async fn get_players(&self, session_id: &str) -> LavalinkResult<Vec<player::Player>> {
306        let response = self
307            .request::<crate::error::RequestResult<_>, _, _>(
308                Method::GET,
309                self.path_to_uri(&format!("/sessions/{}/players", session_id), true)?,
310                None::<&()>,
311            )
312            .await?
313            .to_result()?;
314
315        Ok(response)
316    }
317}