1use crate::auth::AuthToken;
35use crate::parse::ParseFrom;
36use crate::{RawResult, Result};
37use std::borrow::Cow;
38use std::future::Future;
39
40use private::Sealed;
41
42pub use album::*;
43pub use artist::*;
44pub use continuations::*;
45pub use history::*;
46pub use library::*;
47pub use playlist::*;
48pub use podcasts::*;
49pub use recommendations::*;
50pub use search::*;
51pub use upload::*;
52
53mod artist;
54mod continuations;
55mod history;
56mod library;
57mod playlist;
58mod podcasts;
59mod recommendations;
60mod search;
61mod upload;
62
63mod private {
64 pub trait Sealed {}
65}
66
67pub trait Query<A: AuthToken>: Sized {
71 type Output: ParseFrom<Self>;
72 type Method: QueryMethod<Self, A, Self::Output>;
73}
74
75pub trait PostQuery {
77 fn header(&self) -> serde_json::Map<String, serde_json::Value>;
78 fn params(&self) -> Vec<(&str, Cow<str>)>;
79 fn path(&self) -> &str;
80}
81pub trait GetQuery {
83 fn url(&self) -> &str;
84 fn params(&self) -> Vec<(&str, Cow<str>)>;
85}
86
87pub struct GetMethod;
89pub struct PostMethod;
91
92#[allow(async_fn_in_trait)]
98pub trait QueryMethod<Q, A, O>: Sealed
99where
100 Q: Query<A>,
101 A: AuthToken,
102{
103 async fn call<'a>(
104 query: &'a Q,
105 client: &crate::client::Client,
106 tok: &A,
107 ) -> Result<RawResult<'a, Q, A>>;
108}
109
110impl Sealed for GetMethod {}
111impl<Q, A, O> QueryMethod<Q, A, O> for GetMethod
112where
113 Q: GetQuery + Query<A, Output = O>,
114 A: AuthToken,
115{
116 fn call<'a>(
117 query: &'a Q,
118 client: &crate::client::Client,
119 tok: &A,
120 ) -> impl Future<Output = Result<RawResult<'a, Q, A>>>
121 where
122 Self: Sized,
123 {
124 tok.raw_query_get(client, query)
125 }
126}
127
128impl Sealed for PostMethod {}
129impl<Q, A, O> QueryMethod<Q, A, O> for PostMethod
130where
131 Q: PostQuery + Query<A, Output = O>,
132 A: AuthToken,
133{
134 fn call<'a>(
135 query: &'a Q,
136 client: &crate::client::Client,
137 tok: &A,
138 ) -> impl Future<Output = Result<RawResult<'a, Q, A>>>
139 where
140 Self: Sized,
141 {
142 tok.raw_query_post(client, query)
143 }
144}
145
146pub mod album {
147 use super::{PostMethod, PostQuery, Query};
148 use crate::{
149 auth::AuthToken,
150 common::{AlbumID, YoutubeID},
151 parse::GetAlbum,
152 };
153 use serde_json::json;
154
155 #[derive(Clone)]
156 pub struct GetAlbumQuery<'a> {
157 browse_id: AlbumID<'a>,
158 }
159 impl<A: AuthToken> Query<A> for GetAlbumQuery<'_> {
160 type Output = GetAlbum;
161 type Method = PostMethod;
162 }
163 impl PostQuery for GetAlbumQuery<'_> {
164 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
165 let serde_json::Value::Object(map) = json!({
166 "browseId" : self.browse_id.get_raw(),
167 }) else {
168 unreachable!("Created a map");
169 };
170 map
171 }
172 fn path(&self) -> &str {
173 "browse"
174 }
175 fn params(&self) -> std::vec::Vec<(&str, std::borrow::Cow<'_, str>)> {
176 vec![]
177 }
178 }
179 impl<'a> GetAlbumQuery<'_> {
180 pub fn new<T: Into<AlbumID<'a>>>(browse_id: T) -> GetAlbumQuery<'a> {
181 GetAlbumQuery {
182 browse_id: browse_id.into(),
183 }
184 }
185 }
186}
187
188pub mod lyrics {
189 use super::{PostMethod, PostQuery, Query};
190 use crate::{
191 auth::AuthToken,
192 common::{LyricsID, YoutubeID},
193 parse::Lyrics,
194 };
195 use serde_json::json;
196
197 pub struct GetLyricsQuery<'a> {
198 id: LyricsID<'a>,
199 }
200 impl<A: AuthToken> Query<A> for GetLyricsQuery<'_> {
201 type Output = Lyrics;
202 type Method = PostMethod;
203 }
204 impl PostQuery for GetLyricsQuery<'_> {
205 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
206 let serde_json::Value::Object(map) = json!({
207 "browseId": self.id.get_raw(),
208 }) else {
209 unreachable!()
210 };
211 map
212 }
213 fn path(&self) -> &str {
214 "browse"
215 }
216 fn params(&self) -> std::vec::Vec<(&str, std::borrow::Cow<'_, str>)> {
217 vec![]
218 }
219 }
220 impl<'a> GetLyricsQuery<'a> {
221 pub fn new(id: LyricsID<'a>) -> GetLyricsQuery<'a> {
222 GetLyricsQuery { id }
223 }
224 }
225}
226
227pub mod watch {
228 use super::{PostMethod, PostQuery, Query};
229 use crate::{
230 auth::AuthToken,
231 common::{PlaylistID, VideoID, YoutubeID},
232 };
233 use serde_json::json;
234 use std::borrow::Cow;
235
236 pub trait GetWatchPlaylistQueryID {
237 fn get_video_id(&self) -> Option<Cow<str>>;
238 fn get_playlist_id(&self) -> Cow<str>;
239 }
240
241 pub struct GetWatchPlaylistQuery<T: GetWatchPlaylistQueryID> {
242 id: T,
243 }
244 pub struct VideoAndPlaylistID<'a> {
245 video_id: VideoID<'a>,
246 playlist_id: PlaylistID<'a>,
247 }
248
249 impl GetWatchPlaylistQueryID for VideoAndPlaylistID<'_> {
250 fn get_video_id(&self) -> Option<Cow<str>> {
251 Some(self.video_id.get_raw().into())
252 }
253
254 fn get_playlist_id(&self) -> Cow<str> {
255 self.playlist_id.get_raw().into()
256 }
257 }
258 impl GetWatchPlaylistQueryID for VideoID<'_> {
259 fn get_video_id(&self) -> Option<Cow<str>> {
260 Some(self.get_raw().into())
261 }
262
263 fn get_playlist_id(&self) -> Cow<str> {
264 format!("RDAMVM{}", self.get_raw()).into()
265 }
266 }
267 impl GetWatchPlaylistQueryID for PlaylistID<'_> {
268 fn get_video_id(&self) -> Option<Cow<str>> {
269 None
270 }
271 fn get_playlist_id(&self) -> Cow<str> {
272 self.get_raw().into()
273 }
274 }
275
276 impl<T: GetWatchPlaylistQueryID, A: AuthToken> Query<A> for GetWatchPlaylistQuery<T> {
277 type Output = crate::parse::WatchPlaylist;
278 type Method = PostMethod;
279 }
280 impl<T: GetWatchPlaylistQueryID> PostQuery for GetWatchPlaylistQuery<T> {
281 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
282 let serde_json::Value::Object(mut map) = json!({
283 "enablePersistentPlaylistPanel": true,
284 "isAudioOnly": true,
285 "tunerSettingValue": "AUTOMIX_SETTING_NORMAL",
286 "playlistId" : self.id.get_playlist_id(),
287 }) else {
288 unreachable!()
289 };
290 if let Some(video_id) = self.id.get_video_id() {
291 map.insert("videoId".to_string(), json!(video_id));
292 };
293 map
294 }
295 fn path(&self) -> &str {
296 "next"
297 }
298 fn params(&self) -> Vec<(&str, Cow<str>)> {
299 vec![]
300 }
301 }
302 impl<'a> GetWatchPlaylistQuery<VideoID<'a>> {
303 pub fn new_from_video_id<T: Into<VideoID<'a>>>(
304 id: T,
305 ) -> GetWatchPlaylistQuery<VideoID<'a>> {
306 GetWatchPlaylistQuery { id: id.into() }
307 }
308 pub fn with_playlist_id(
309 self,
310 playlist_id: PlaylistID<'a>,
311 ) -> GetWatchPlaylistQuery<VideoAndPlaylistID<'a>> {
312 GetWatchPlaylistQuery {
313 id: VideoAndPlaylistID {
314 video_id: self.id,
315 playlist_id,
316 },
317 }
318 }
319 }
320 impl<'a> GetWatchPlaylistQuery<PlaylistID<'a>> {
321 pub fn new_from_playlist_id(id: PlaylistID<'a>) -> GetWatchPlaylistQuery<PlaylistID<'a>> {
322 GetWatchPlaylistQuery { id }
323 }
324 pub fn with_video_id(
325 self,
326 video_id: VideoID<'a>,
327 ) -> GetWatchPlaylistQuery<VideoAndPlaylistID<'a>> {
328 GetWatchPlaylistQuery {
329 id: VideoAndPlaylistID {
330 video_id,
331 playlist_id: self.id,
332 },
333 }
334 }
335 }
336}
337
338pub mod rate {
339 use std::borrow::Cow;
340
341 use super::{PostMethod, PostQuery, Query};
342 use crate::{
343 auth::AuthToken,
344 common::{LikeStatus, PlaylistID, VideoID, YoutubeID},
345 };
346 use serde_json::json;
347
348 pub struct RateSongQuery<'a> {
349 video_id: VideoID<'a>,
350 rating: LikeStatus,
351 }
352 impl<'a> RateSongQuery<'a> {
353 pub fn new(video_id: VideoID<'a>, rating: LikeStatus) -> Self {
354 Self { video_id, rating }
355 }
356 }
357 pub struct RatePlaylistQuery<'a> {
358 playlist_id: PlaylistID<'a>,
359 rating: LikeStatus,
360 }
361 impl<'a> RatePlaylistQuery<'a> {
362 pub fn new(playlist_id: PlaylistID<'a>, rating: LikeStatus) -> Self {
363 Self {
364 playlist_id,
365 rating,
366 }
367 }
368 }
369
370 impl<A: AuthToken> Query<A> for RateSongQuery<'_> {
372 type Output = ();
373 type Method = PostMethod;
374 }
375 impl PostQuery for RateSongQuery<'_> {
376 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
377 serde_json::Map::from_iter([(
378 "target".to_string(),
379 json!({"videoId" : self.video_id.get_raw()} ),
380 )])
381 }
382 fn params(&self) -> Vec<(&str, Cow<str>)> {
383 vec![]
384 }
385 fn path(&self) -> &str {
386 like_endpoint(&self.rating)
387 }
388 }
389
390 impl<A: AuthToken> Query<A> for RatePlaylistQuery<'_> {
392 type Output = ();
393 type Method = PostMethod;
394 }
395
396 impl PostQuery for RatePlaylistQuery<'_> {
397 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
398 serde_json::Map::from_iter([(
399 "target".to_string(),
400 json!({"playlistId" : self.playlist_id.get_raw()} ),
401 )])
402 }
403 fn params(&self) -> Vec<(&str, Cow<str>)> {
404 vec![]
405 }
406 fn path(&self) -> &str {
407 like_endpoint(&self.rating)
408 }
409 }
410
411 fn like_endpoint(rating: &LikeStatus) -> &'static str {
412 match *rating {
413 LikeStatus::Liked => "like/like",
414 LikeStatus::Disliked => "like/dislike",
415 LikeStatus::Indifferent => "like/removelike",
416 }
417 }
418}
419
420pub mod song {
422 use super::{PostMethod, PostQuery, Query};
423 use crate::common::VideoID;
424 use crate::{auth::AuthToken, common::SongTrackingUrl, Result};
425 use serde_json::json;
426 use std::borrow::Cow;
427 use std::time::SystemTime;
428
429 pub struct GetSongTrackingUrlQuery<'a> {
430 video_id: VideoID<'a>,
431 signature_timestamp: u64,
432 }
433
434 impl GetSongTrackingUrlQuery<'_> {
435 pub fn new(video_id: VideoID) -> Result<GetSongTrackingUrlQuery<'_>> {
440 let signature_timestamp = get_signature_timestamp()?;
441 Ok(GetSongTrackingUrlQuery {
442 video_id,
443 signature_timestamp,
444 })
445 }
446 }
447
448 impl<A: AuthToken> Query<A> for GetSongTrackingUrlQuery<'_> {
449 type Output = SongTrackingUrl<'static>;
450 type Method = PostMethod;
451 }
452 impl PostQuery for GetSongTrackingUrlQuery<'_> {
453 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
454 serde_json::Map::from_iter([
455 (
456 "playbackContext".to_string(),
457 json!(
458 {
459 "contentPlaybackContext": {
460 "signatureTimestamp": self.signature_timestamp
461 }
462 }
463 ),
464 ),
465 ("video_id".to_string(), json!(self.video_id)),
466 ])
467 }
468 fn params(&self) -> Vec<(&str, Cow<str>)> {
469 vec![]
470 }
471 fn path(&self) -> &str {
472 "player"
473 }
474 }
475
476 fn get_signature_timestamp() -> Result<u64> {
480 const SECONDS_IN_DAY: u64 = 60 * 60 * 24;
481 Ok(SystemTime::now()
482 .duration_since(SystemTime::UNIX_EPOCH)?
483 .as_secs()
484 .saturating_div(SECONDS_IN_DAY))
486 }
487}