Skip to main content

ytmapi_rs/query/
playlist.rs

1use super::{PostMethod, PostQuery, Query};
2use crate::auth::{AuthToken, LoggedIn};
3use crate::common::{PlaylistID, SetVideoID, VideoID, YoutubeID};
4use crate::parse::{GetPlaylistDetails, PlaylistItem};
5pub use additems::*;
6pub use create::*;
7pub use edit::*;
8use serde::{Deserialize, Serialize};
9use serde_json::json;
10use std::borrow::Cow;
11use std::fmt::Display;
12
13pub mod additems;
14pub mod create;
15pub mod edit;
16
17// Potentially same functionality as similar trait for Create.
18pub trait SpecialisedQuery {
19    fn additional_header(&self) -> Option<(String, serde_json::Value)>;
20}
21
22pub trait GetWatchPlaylistQueryID {
23    fn get_video_id(&self) -> Option<Cow<'_, str>>;
24    fn get_playlist_id(&self) -> Cow<'_, str>;
25}
26
27//TODO: Likely Common
28#[derive(Default, PartialEq, Debug, Clone, Deserialize, Serialize)]
29pub enum PrivacyStatus {
30    Public,
31    #[default]
32    Private,
33    Unlisted,
34}
35impl Display for PrivacyStatus {
36    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
37        let str = match self {
38            PrivacyStatus::Public => "PUBLIC",
39            PrivacyStatus::Private => "PRIVATE",
40            PrivacyStatus::Unlisted => "UNLISTED",
41        };
42        write!(f, "{str}")
43    }
44}
45
46pub struct VideoAndPlaylistID<'a> {
47    video_id: VideoID<'a>,
48    playlist_id: PlaylistID<'a>,
49}
50impl GetWatchPlaylistQueryID for VideoAndPlaylistID<'_> {
51    fn get_video_id(&self) -> Option<Cow<'_, str>> {
52        Some(self.video_id.get_raw().into())
53    }
54
55    fn get_playlist_id(&self) -> Cow<'_, str> {
56        self.playlist_id.get_raw().into()
57    }
58}
59impl GetWatchPlaylistQueryID for VideoID<'_> {
60    fn get_video_id(&self) -> Option<Cow<'_, str>> {
61        Some(self.get_raw().into())
62    }
63
64    fn get_playlist_id(&self) -> Cow<'_, str> {
65        format!("RDAMVM{}", self.get_raw()).into()
66    }
67}
68impl GetWatchPlaylistQueryID for PlaylistID<'_> {
69    fn get_video_id(&self) -> Option<Cow<'_, str>> {
70        None
71    }
72    fn get_playlist_id(&self) -> Cow<'_, str> {
73        self.get_raw().into()
74    }
75}
76
77// Suspect this requires a browseId, not a playlistId - i.e requires VL at the
78// start.
79pub struct GetPlaylistTracksQuery<'a> {
80    id: PlaylistID<'a>,
81}
82
83// Suspect this requires a browseId, not a playlistId - i.e requires VL at the
84// start.
85pub struct GetPlaylistDetailsQuery<'a> {
86    id: PlaylistID<'a>,
87}
88
89pub struct DeletePlaylistQuery<'a> {
90    id: PlaylistID<'a>,
91}
92
93pub struct GetWatchPlaylistQuery<T: GetWatchPlaylistQueryID> {
94    id: T,
95}
96
97pub struct RemovePlaylistItemsQuery<'a> {
98    id: PlaylistID<'a>,
99    video_items: Vec<SetVideoID<'a>>,
100}
101
102impl<'a> GetPlaylistTracksQuery<'a> {
103    pub fn new(id: PlaylistID<'a>) -> GetPlaylistTracksQuery<'a> {
104        GetPlaylistTracksQuery { id }
105    }
106}
107impl<'a> GetPlaylistDetailsQuery<'a> {
108    pub fn new(id: PlaylistID<'a>) -> GetPlaylistDetailsQuery<'a> {
109        GetPlaylistDetailsQuery { id }
110    }
111}
112impl<'a> DeletePlaylistQuery<'a> {
113    pub fn new(id: PlaylistID<'a>) -> DeletePlaylistQuery<'a> {
114        DeletePlaylistQuery { id }
115    }
116}
117impl<'a> From<PlaylistID<'a>> for DeletePlaylistQuery<'a> {
118    fn from(value: PlaylistID<'a>) -> Self {
119        DeletePlaylistQuery { id: value }
120    }
121}
122impl<'a> RemovePlaylistItemsQuery<'a> {
123    pub fn new(
124        id: PlaylistID<'a>,
125        video_items: impl IntoIterator<Item = SetVideoID<'a>>,
126    ) -> RemovePlaylistItemsQuery<'a> {
127        RemovePlaylistItemsQuery {
128            id,
129            video_items: video_items.into_iter().collect(),
130        }
131    }
132}
133impl<'a> GetWatchPlaylistQuery<VideoID<'a>> {
134    pub fn new_from_video_id<T: Into<VideoID<'a>>>(id: T) -> GetWatchPlaylistQuery<VideoID<'a>> {
135        GetWatchPlaylistQuery { id: id.into() }
136    }
137    pub fn with_playlist_id(
138        self,
139        playlist_id: PlaylistID<'a>,
140    ) -> GetWatchPlaylistQuery<VideoAndPlaylistID<'a>> {
141        GetWatchPlaylistQuery {
142            id: VideoAndPlaylistID {
143                video_id: self.id,
144                playlist_id,
145            },
146        }
147    }
148}
149impl<'a> GetWatchPlaylistQuery<PlaylistID<'a>> {
150    pub fn new_from_playlist_id(id: PlaylistID<'a>) -> GetWatchPlaylistQuery<PlaylistID<'a>> {
151        GetWatchPlaylistQuery { id }
152    }
153    pub fn with_video_id(
154        self,
155        video_id: VideoID<'a>,
156    ) -> GetWatchPlaylistQuery<VideoAndPlaylistID<'a>> {
157        GetWatchPlaylistQuery {
158            id: VideoAndPlaylistID {
159                video_id,
160                playlist_id: self.id,
161            },
162        }
163    }
164}
165
166impl<A: AuthToken> Query<A> for GetPlaylistTracksQuery<'_> {
167    type Output = Vec<PlaylistItem>;
168    type Method = PostMethod;
169}
170impl PostQuery for GetPlaylistTracksQuery<'_> {
171    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
172        // TODO: Confirm if processing required to add 'VL' portion of playlistId
173        let serde_json::Value::Object(map) = json!({
174            "browseId" : self.id.get_raw(),
175        }) else {
176            unreachable!()
177        };
178        map
179    }
180    fn path(&self) -> &str {
181        "browse"
182    }
183    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
184        vec![]
185    }
186}
187
188// Note - this is functionally the same as GetPlaylistQuery, however the output
189// is different.
190impl<A: AuthToken> Query<A> for GetPlaylistDetailsQuery<'_> {
191    type Output = GetPlaylistDetails;
192    type Method = PostMethod;
193}
194impl PostQuery for GetPlaylistDetailsQuery<'_> {
195    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
196        // TODO: Confirm if processing required to add 'VL' portion of playlistId
197        let serde_json::Value::Object(map) = json!({
198            "browseId" : self.id.get_raw(),
199        }) else {
200            unreachable!()
201        };
202        map
203    }
204    fn path(&self) -> &str {
205        "browse"
206    }
207    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
208        vec![]
209    }
210}
211
212impl<A: LoggedIn> Query<A> for DeletePlaylistQuery<'_> {
213    type Output = ();
214    type Method = PostMethod;
215}
216impl PostQuery for DeletePlaylistQuery<'_> {
217    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
218        // TODO: Confirm if processing required to remove 'VL' portion of playlistId
219        let serde_json::Value::Object(map) = json!({
220            "playlistId" : self.id.get_raw(),
221        }) else {
222            unreachable!()
223        };
224        map
225    }
226    fn path(&self) -> &str {
227        "playlist/delete"
228    }
229    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
230        vec![]
231    }
232}
233
234impl<A: LoggedIn> Query<A> for RemovePlaylistItemsQuery<'_> {
235    type Output = ();
236    type Method = PostMethod;
237}
238impl PostQuery for RemovePlaylistItemsQuery<'_> {
239    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
240        let serde_json::Value::Object(mut map) = json!({
241            "playlistId": self.id,
242        }) else {
243            unreachable!()
244        };
245        let actions: Vec<serde_json::Value> = self
246            .video_items
247            .iter()
248            .map(|v| {
249                json!(
250                {
251                    "setVideoId" : v,
252                    "action" : "ACTION_REMOVE_VIDEO",
253                })
254            })
255            .collect();
256        map.insert("actions".into(), json!(actions));
257        map
258    }
259    fn path(&self) -> &str {
260        "browse/edit_playlist"
261    }
262    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
263        vec![]
264    }
265}
266
267impl<T: GetWatchPlaylistQueryID, A: AuthToken> Query<A> for GetWatchPlaylistQuery<T> {
268    type Output = Vec<crate::parse::WatchPlaylistTrack>;
269    type Method = PostMethod;
270}
271impl<T: GetWatchPlaylistQueryID> PostQuery for GetWatchPlaylistQuery<T> {
272    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
273        let serde_json::Value::Object(mut map) = json!({
274            "enablePersistentPlaylistPanel": true,
275            "isAudioOnly": true,
276            "tunerSettingValue": "AUTOMIX_SETTING_NORMAL",
277            "playlistId" : self.id.get_playlist_id(),
278        }) else {
279            unreachable!()
280        };
281        if let Some(video_id) = self.id.get_video_id() {
282            map.insert("videoId".to_string(), json!(video_id));
283        };
284        map
285    }
286    fn path(&self) -> &str {
287        "next"
288    }
289    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
290        vec![]
291    }
292}