ytmapi_rs/query/
library.rs

1use super::{PostMethod, PostQuery, Query};
2use crate::auth::LoggedIn;
3use crate::common::{
4    ApiOutcome, FeedbackTokenAddToLibrary, FeedbackTokenRemoveFromLibrary, YoutubeID,
5};
6use crate::parse::{
7    LibraryArtist, LibraryArtistSubscription, LibraryChannel, LibraryPlaylist, LibraryPodcast,
8    SearchResultAlbum, TableListSong,
9};
10use serde_json::json;
11use std::borrow::Cow;
12
13// NOTE: Authentication is required to use the queries in this module.
14// Currently, all queries are implemented with authentication however in future
15// this could be scaled back.
16
17#[derive(Default, Clone)]
18pub enum GetLibrarySortOrder {
19    NameAsc,
20    NameDesc,
21    RecentlySaved,
22    #[default]
23    Default,
24}
25
26pub struct GetLibraryPlaylistsQuery;
27#[derive(Default)]
28pub struct GetLibrarySongsQuery {
29    sort_order: GetLibrarySortOrder,
30}
31#[derive(Default)]
32pub struct GetLibraryAlbumsQuery {
33    sort_order: GetLibrarySortOrder,
34}
35#[derive(Default)]
36pub struct GetLibraryArtistSubscriptionsQuery {
37    sort_order: GetLibrarySortOrder,
38}
39#[derive(Default)]
40pub struct GetLibraryArtistsQuery {
41    sort_order: GetLibrarySortOrder,
42}
43#[derive(Default)]
44pub struct GetLibraryPodcastsQuery {
45    sort_order: GetLibrarySortOrder,
46}
47#[derive(Default)]
48pub struct GetLibraryChannelsQuery {
49    sort_order: GetLibrarySortOrder,
50}
51pub struct EditSongLibraryStatusQuery<'a> {
52    add_to_library_feedback_tokens: Vec<FeedbackTokenAddToLibrary<'a>>,
53    remove_from_library_feedback_tokens: Vec<FeedbackTokenRemoveFromLibrary<'a>>,
54}
55
56impl GetLibrarySongsQuery {
57    pub fn new(sort_order: GetLibrarySortOrder) -> Self {
58        Self { sort_order }
59    }
60}
61impl GetLibraryAlbumsQuery {
62    pub fn new(sort_order: GetLibrarySortOrder) -> Self {
63        Self { sort_order }
64    }
65}
66impl GetLibraryArtistSubscriptionsQuery {
67    pub fn new(sort_order: GetLibrarySortOrder) -> Self {
68        Self { sort_order }
69    }
70}
71impl GetLibraryArtistsQuery {
72    pub fn new(sort_order: GetLibrarySortOrder) -> Self {
73        Self { sort_order }
74    }
75}
76impl GetLibraryPodcastsQuery {
77    pub fn new(sort_order: GetLibrarySortOrder) -> Self {
78        Self { sort_order }
79    }
80}
81impl GetLibraryChannelsQuery {
82    pub fn new(sort_order: GetLibrarySortOrder) -> Self {
83        Self { sort_order }
84    }
85}
86impl<'a> EditSongLibraryStatusQuery<'a> {
87    pub fn new_from_add_to_library_feedback_tokens(
88        add_to_library_feedback_tokens: impl IntoIterator<Item = FeedbackTokenAddToLibrary<'a>>,
89    ) -> Self {
90        Self {
91            add_to_library_feedback_tokens: add_to_library_feedback_tokens.into_iter().collect(),
92            remove_from_library_feedback_tokens: vec![],
93        }
94    }
95    pub fn new_from_remove_from_library_feedback_tokens(
96        remove_from_library_feedback_tokens: impl IntoIterator<
97            Item = FeedbackTokenRemoveFromLibrary<'a>,
98        >,
99    ) -> Self {
100        Self {
101            add_to_library_feedback_tokens: vec![],
102            remove_from_library_feedback_tokens: remove_from_library_feedback_tokens
103                .into_iter()
104                .collect(),
105        }
106    }
107    pub fn with_add_to_library_feedback_tokens(
108        mut self,
109        add_to_library_feedback_tokens: impl IntoIterator<Item = FeedbackTokenAddToLibrary<'a>>,
110    ) -> Self {
111        self.add_to_library_feedback_tokens = add_to_library_feedback_tokens.into_iter().collect();
112        self
113    }
114    pub fn with_remove_from_library_feedback_tokens(
115        mut self,
116        remove_from_library_feedback_tokens: impl IntoIterator<
117            Item = FeedbackTokenRemoveFromLibrary<'a>,
118        >,
119    ) -> Self {
120        self.remove_from_library_feedback_tokens =
121            remove_from_library_feedback_tokens.into_iter().collect();
122        self
123    }
124}
125
126impl<A: LoggedIn> Query<A> for GetLibraryPlaylistsQuery {
127    type Output = Vec<LibraryPlaylist>;
128    type Method = PostMethod;
129}
130impl PostQuery for GetLibraryPlaylistsQuery {
131    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
132        FromIterator::from_iter([("browseId".to_string(), json!("FEmusic_liked_playlists"))])
133    }
134    fn path(&self) -> &str {
135        "browse"
136    }
137    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
138        vec![]
139    }
140}
141impl<A: LoggedIn> Query<A> for GetLibraryArtistsQuery {
142    type Output = Vec<LibraryArtist>;
143    type Method = PostMethod;
144}
145impl PostQuery for GetLibraryArtistsQuery {
146    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
147        if let Some(params) = get_sort_order_params(&self.sort_order) {
148            FromIterator::from_iter([
149                (
150                    "browseId".to_string(),
151                    json!("FEmusic_library_corpus_track_artists"),
152                ),
153                ("params".to_string(), json!(params)),
154            ])
155        } else {
156            FromIterator::from_iter([(
157                "browseId".to_string(),
158                json!("FEmusic_library_corpus_track_artists"),
159            )])
160        }
161    }
162    fn path(&self) -> &str {
163        "browse"
164    }
165    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
166        vec![]
167    }
168}
169
170impl<A: LoggedIn> Query<A> for GetLibrarySongsQuery {
171    type Output = Vec<TableListSong>;
172    type Method = PostMethod;
173}
174impl PostQuery for GetLibrarySongsQuery {
175    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
176        if let Some(params) = get_sort_order_params(&self.sort_order) {
177            serde_json::Map::from_iter([
178                ("browseId".to_string(), json!("FEmusic_liked_videos")),
179                ("params".to_string(), json!(params)),
180            ])
181        } else {
182            serde_json::Map::from_iter([("browseId".to_string(), json!("FEmusic_liked_videos"))])
183        }
184    }
185    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
186        vec![]
187    }
188    fn path(&self) -> &str {
189        "browse"
190    }
191}
192impl<A: LoggedIn> Query<A> for GetLibraryAlbumsQuery {
193    type Output = Vec<SearchResultAlbum>;
194    type Method = PostMethod;
195}
196impl PostQuery for GetLibraryAlbumsQuery {
197    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
198        if let Some(params) = get_sort_order_params(&self.sort_order) {
199            serde_json::Map::from_iter([
200                ("browseId".to_string(), json!("FEmusic_liked_albums")),
201                ("params".to_string(), json!(params)),
202            ])
203        } else {
204            serde_json::Map::from_iter([("browseId".to_string(), json!("FEmusic_liked_albums"))])
205        }
206    }
207    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
208        vec![]
209    }
210    fn path(&self) -> &str {
211        "browse"
212    }
213}
214impl<A: LoggedIn> Query<A> for GetLibraryArtistSubscriptionsQuery {
215    type Output = Vec<LibraryArtistSubscription>;
216    type Method = PostMethod;
217}
218impl PostQuery for GetLibraryArtistSubscriptionsQuery {
219    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
220        if let Some(params) = get_sort_order_params(&self.sort_order) {
221            serde_json::Map::from_iter([
222                (
223                    "browseId".to_string(),
224                    json!("FEmusic_library_corpus_artists"),
225                ),
226                ("params".to_string(), json!(params)),
227            ])
228        } else {
229            serde_json::Map::from_iter([(
230                "browseId".to_string(),
231                json!("FEmusic_library_corpus_artists"),
232            )])
233        }
234    }
235    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
236        vec![]
237    }
238    fn path(&self) -> &str {
239        "browse"
240    }
241}
242// NOTE: Does not work on brand accounts
243impl<A: LoggedIn> Query<A> for EditSongLibraryStatusQuery<'_> {
244    type Output = Vec<ApiOutcome>;
245    type Method = PostMethod;
246}
247impl PostQuery for EditSongLibraryStatusQuery<'_> {
248    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
249        let add_feedback_tokens_raw = self
250            .add_to_library_feedback_tokens
251            .iter()
252            .map(|t| t.get_raw());
253        let feedback_tokens = self
254            .remove_from_library_feedback_tokens
255            .iter()
256            .map(|t| t.get_raw())
257            .chain(add_feedback_tokens_raw)
258            .collect::<Vec<_>>();
259        serde_json::Map::from_iter([("feedbackTokens".to_string(), json!(feedback_tokens))])
260    }
261    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
262        vec![]
263    }
264    fn path(&self) -> &str {
265        "feedback"
266    }
267}
268impl<A: LoggedIn> Query<A> for GetLibraryPodcastsQuery {
269    type Output = Vec<LibraryPodcast>;
270    type Method = PostMethod;
271}
272impl PostQuery for GetLibraryPodcastsQuery {
273    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
274        if let Some(params) = get_sort_order_params(&self.sort_order) {
275            serde_json::Map::from_iter([
276                (
277                    "browseId".to_string(),
278                    json!("FEmusic_library_non_music_audio_list"),
279                ),
280                ("params".to_string(), json!(params)),
281            ])
282        } else {
283            serde_json::Map::from_iter([(
284                "browseId".to_string(),
285                json!("FEmusic_library_non_music_audio_list"),
286            )])
287        }
288    }
289    fn path(&self) -> &str {
290        "browse"
291    }
292    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
293        vec![]
294    }
295}
296impl<A: LoggedIn> Query<A> for GetLibraryChannelsQuery {
297    type Output = Vec<LibraryChannel>;
298    type Method = PostMethod;
299}
300impl PostQuery for GetLibraryChannelsQuery {
301    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
302        if let Some(params) = get_sort_order_params(&self.sort_order) {
303            serde_json::Map::from_iter([
304                (
305                    "browseId".to_string(),
306                    json!("FEmusic_library_non_music_audio_channels_list"),
307                ),
308                ("params".to_string(), json!(params)),
309            ])
310        } else {
311            serde_json::Map::from_iter([(
312                "browseId".to_string(),
313                json!("FEmusic_library_non_music_audio_channels_list"),
314            )])
315        }
316    }
317    fn path(&self) -> &str {
318        "browse"
319    }
320    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
321        vec![]
322    }
323}
324
325pub(crate) fn get_sort_order_params(o: &GetLibrarySortOrder) -> Option<&'static str> {
326    match o {
327        GetLibrarySortOrder::NameAsc => Some("ggMGKgQIARAA"),
328        GetLibrarySortOrder::NameDesc => Some("ggMGKgQIARAB"),
329        GetLibrarySortOrder::RecentlySaved => Some("ggMGKgQIABAB"),
330        GetLibrarySortOrder::Default => None,
331    }
332}