1use std::{
2 future::Future,
3 pin::Pin,
4 task::{Context, Poll},
5};
6
7use chrono::{DateTime, Utc};
8use futures::future::BoxFuture;
9use log::debug;
10use serde::{Deserialize, Serialize, Serializer};
11use snafu::{ResultExt, Snafu};
12
13use super::ApiKey;
14
15#[derive(Debug, Snafu)]
17pub enum Error {
18 #[snafu(display("failed to connect to the api: {}", string))]
19 Connection { string: String },
20 #[snafu(display("failed to deserialize: {} {}", string, source))]
21 Deserialization {
22 string: String,
23 source: serde_json::Error,
24 },
25 #[snafu(display("failed to serialize: {}", source))]
26 Serialization {
27 source: serde_urlencoded::ser::Error,
28 },
29}
30
31impl From<surf::Error> for Error {
32 fn from(surf_error: surf::Error) -> Self {
33 Error::Connection {
34 string: surf_error.to_string(),
35 }
36 }
37}
38
39pub struct PlaylistItems {
41 future: Option<BoxFuture<'static, Result<Response, Error>>>,
42 data: Option<PlaylistItemsData>,
43}
44
45#[derive(Debug, Clone, Serialize)]
46#[serde(rename_all = "camelCase")]
47struct PlaylistItemsData {
48 key: ApiKey,
49 part: String,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 id: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 max_results: Option<u8>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 on_behalf_of_content_owner: Option<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 page_token: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 playlist_id: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 video_id: Option<String>,
62}
63
64impl PlaylistItems {
65 const URL: &'static str = "https://www.googleapis.com/youtube/v3/playlistItems";
66
67 #[must_use]
69 pub fn new(key: ApiKey) -> Self {
70 Self {
71 future: None,
72 data: Some(PlaylistItemsData {
73 key,
74 part: String::from("snippet"),
75 id: None,
76 max_results: None,
77 on_behalf_of_content_owner: None,
78 page_token: None,
79 playlist_id: None,
80 video_id: None,
81 }),
82 }
83 }
84
85 #[must_use]
86 pub fn id(mut self, id: impl Into<String>) -> Self {
87 let mut data = self.data.take().unwrap();
88 data.id = Some(id.into());
89 self.data = Some(data);
90 self
91 }
92
93 #[must_use]
94 pub fn max_results(mut self, max_results: impl Into<u8>) -> Self {
95 let mut data = self.data.take().unwrap();
96 data.max_results = Some(max_results.into());
97 self.data = Some(data);
98 self
99 }
100
101 #[must_use]
102 pub fn on_behalf_of_content_owner(
103 mut self,
104 on_behalf_of_content_owner: impl Into<String>,
105 ) -> Self {
106 let mut data = self.data.take().unwrap();
107 data.on_behalf_of_content_owner = Some(on_behalf_of_content_owner.into());
108 self.data = Some(data);
109 self
110 }
111
112 #[must_use]
113 pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
114 let mut data = self.data.take().unwrap();
115 data.page_token = Some(page_token.into());
116 self.data = Some(data);
117 self
118 }
119
120 #[must_use]
121 pub fn playlist_id(mut self, playlist_id: impl Into<String>) -> Self {
122 let mut data = self.data.take().unwrap();
123 data.playlist_id = Some(playlist_id.into());
124 self.data = Some(data);
125 self
126 }
127
128 #[must_use]
129 pub fn video_id(mut self, video_id: impl Into<String>) -> Self {
130 let mut data = self.data.take().unwrap();
131 data.video_id = Some(video_id.into());
132 self.data = Some(data);
133 self
134 }
135}
136
137impl Future for PlaylistItems {
138 type Output = Result<Response, Error>;
139
140 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
141 if self.future.is_none() {
142 let data = self.data.take().unwrap();
143 self.future = Some(Box::pin(async move {
144 let url = format!(
145 "{}?{}",
146 Self::URL,
147 serde_urlencoded::to_string(&data).context(Serialization)?
148 );
149 debug!("getting {}", url);
150 let response = surf::get(&url).recv_string().await?;
151 serde_json::from_str(&response)
152 .with_context(move || Deserialization { string: response })
153 }));
154 }
155
156 self.future.as_mut().unwrap().as_mut().poll(cx)
157 }
158}
159
160#[derive(Debug, Clone, Serialize)]
161#[serde(rename_all = "camelCase")]
162pub enum ChannelType {
163 Any,
164 Show,
165}
166
167#[derive(Debug, Clone, Serialize)]
168#[serde(rename_all = "camelCase")]
169pub enum EventType {
170 Completed,
171 Live,
172 Upcoming,
173}
174
175#[derive(Debug, Clone)]
176pub struct VideoLocation {
177 longitude: f32,
178 latitude: f32,
179}
180
181impl VideoLocation {
182 #[must_use]
183 pub fn new(longitude: f32, latitude: f32) -> Self {
184 Self {
185 longitude,
186 latitude,
187 }
188 }
189}
190
191impl Serialize for VideoLocation {
192 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
193 where
194 S: Serializer,
195 {
196 serializer.serialize_str(&format!("{},{}", self.longitude, self.latitude))
197 }
198}
199
200#[derive(Debug, Clone, Serialize)]
201#[serde(rename_all = "camelCase")]
202pub enum Order {
203 Date,
204 Rating,
205 Relevance,
206 Title,
207 VideoCount,
208 ViewCount,
209}
210
211#[derive(Debug, Clone, Serialize)]
212#[serde(rename_all = "camelCase")]
213pub enum SafeSearch {
214 Moderate,
215 Strict,
216}
217
218#[derive(Debug, Clone, Serialize)]
219#[serde(rename_all = "camelCase")]
220pub enum ItemType {
221 Channel,
222 Playlist,
223 Video,
224}
225
226#[derive(Debug, Clone, Serialize)]
227#[serde(rename_all = "camelCase")]
228pub enum VideoCaption {
229 ClosedCaption,
230 None,
231}
232
233#[derive(Debug, Clone, Serialize)]
234#[serde(rename_all = "camelCase")]
235pub enum VideoDefinition {
236 High,
237 Standard,
238}
239
240#[derive(Debug, Clone, Serialize)]
241pub enum VideoDimension {
242 #[serde(rename = "3d")]
243 Three,
244 #[serde(rename = "2d")]
245 Two,
246}
247
248#[derive(Debug, Clone, Serialize)]
249#[serde(rename_all = "camelCase")]
250pub enum VideoDuration {
251 Long,
252 Medium,
253 Short,
254}
255
256#[derive(Debug, Clone, Serialize)]
257#[serde(rename_all = "camelCase")]
258pub enum VideoLicense {
259 CreativeCommon,
260 Youtube,
261}
262
263#[derive(Debug, Clone, Serialize)]
264#[serde(rename_all = "camelCase")]
265pub enum VideoType {
266 Episode,
267 Movie,
268}
269
270#[derive(Debug, Clone, Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct Response {
273 pub kind: String,
274 pub etag: String,
275 pub next_page_token: Option<String>,
276 pub prev_page_token: Option<String>,
277 pub page_info: PageInfo,
278 pub items: Vec<PlaylistResult>,
279}
280
281#[derive(Debug, Clone, Deserialize)]
282#[serde(rename_all = "camelCase")]
283pub struct PageInfo {
284 pub total_results: i64,
285 pub results_per_page: i64,
286}
287
288#[derive(Debug, Clone, Deserialize)]
289pub struct PlaylistResult {
290 pub kind: String,
291 pub etag: String,
292 pub id: String,
293 pub snippet: Snippet,
294 pub content_details: Option<ContentDetails>,
295 pub status: Option<Status>,
296}
297
298#[derive(Debug, Clone, Deserialize)]
299#[serde(rename_all = "camelCase")]
300pub struct Snippet {
301 pub published_at: Option<DateTime<Utc>>,
302 pub channel_id: Option<String>,
303 pub title: Option<String>,
304 pub description: Option<String>,
305 pub thumbnails: Option<Thumbnails>,
306 pub channel_title: Option<String>,
307 pub video_owner_channel_title: Option<String>,
308 pub video_owner_channel_id: Option<String>,
309 pub playlist_id: Option<String>,
310 pub position: Option<u32>,
311 pub resource_id: Resource,
312}
313
314#[derive(Debug, Clone, Deserialize)]
315pub struct Thumbnails {
316 pub default: Option<Thumbnail>,
317 pub medium: Option<Thumbnail>,
318 pub high: Option<Thumbnail>,
319 pub standard: Option<Thumbnail>,
320 pub maxres: Option<Thumbnail>,
321}
322
323#[derive(Debug, Clone, Deserialize)]
324pub struct Thumbnail {
325 pub url: String,
326 pub width: Option<u64>,
327 pub height: Option<u64>,
328}
329
330#[derive(Debug, Clone, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct Resource {
333 pub kind: String,
334 pub video_id: String,
335}
336
337#[derive(Debug, Clone, Deserialize)]
338#[serde(rename_all = "camelCase")]
339pub struct ContentDetails {
340 pub video_id: String,
341 pub start_at: String,
342 pub end_at: String,
343 pub note: String,
344 pub video_published_at: Option<DateTime<Utc>>,
345}
346
347#[derive(Debug, Clone, Deserialize)]
348pub struct Status {
349 pub privacy_status: String,
350}