ytmapi_rs/query/
playlist.rs1use 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
17pub 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#[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
77pub struct GetPlaylistTracksQuery<'a> {
80 id: PlaylistID<'a>,
81}
82
83pub 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 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
188impl<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 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 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}