yt_api/
playlistitems.rs

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/// custom error type for the search endpoint
16#[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
39/// request struct for the search endpoint
40pub 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	/// create struct with an [`ApiKey`](../struct.ApiKey.html)
68	#[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}