Skip to main content

ytmapi_rs/query/playlist/
additems.rs

1use super::SpecialisedQuery;
2use crate::auth::AuthToken;
3use crate::common::{PlaylistID, VideoID};
4use crate::parse::AddPlaylistItem;
5use crate::query::{PostMethod, PostQuery, Query};
6use serde_json::json;
7use std::borrow::Cow;
8
9#[derive(Default, Debug, Clone, PartialEq)]
10pub enum DuplicateHandlingMode {
11    #[default]
12    ReturnError,
13    Unhandled,
14}
15
16// XXX: Query type potentially does not need to be mutually exclusive.
17pub struct AddPlaylistItemsQuery<'a, T: SpecialisedQuery> {
18    id: PlaylistID<'a>,
19    query_type: T,
20}
21/// Helper struct for AddPlaylistItemsQuery
22#[derive(Default, Debug, Clone, PartialEq)]
23pub struct AddVideosToPlaylist<'a> {
24    video_ids: Vec<VideoID<'a>>,
25    duplicate_handling_mode: DuplicateHandlingMode,
26}
27/// Helper struct for AddPlaylistItemsQuery
28#[derive(Debug, Clone, PartialEq)]
29pub struct AddPlaylistToPlaylist<'a> {
30    source_playlist: PlaylistID<'a>,
31}
32impl SpecialisedQuery for AddVideosToPlaylist<'_> {
33    fn additional_header(&self) -> Option<(String, serde_json::Value)> {
34        let actions = self
35            .video_ids
36            .iter()
37            .map(|v| match self.duplicate_handling_mode {
38                DuplicateHandlingMode::ReturnError => json!({
39                    "action" : "ACTION_ADD_VIDEO",
40                    "addedVideoId" : v,
41                }),
42                DuplicateHandlingMode::Unhandled => json!({
43                    "action" : "ACTION_ADD_VIDEO",
44                    "addedVideoId" : v,
45                    "dedupeOption" : "DEDUPE_OPTION_SKIP",
46                }),
47            });
48        Some(("actions".to_string(), actions.collect()))
49    }
50}
51impl SpecialisedQuery for AddPlaylistToPlaylist<'_> {
52    fn additional_header(&self) -> Option<(String, serde_json::Value)> {
53        Some((
54            "actions".to_string(),
55            json!([{
56                "action" : "ACTION_ADD_PLAYLIST",
57                "addedFullListId" : self.source_playlist,
58            },
59            {
60                "action" : "ACTION_ADD_VIDEO",
61                "addedVideoId" : null,
62            }]),
63        ))
64    }
65}
66impl<'a> AddPlaylistItemsQuery<'a, AddPlaylistToPlaylist<'a>> {
67    pub fn new_from_playlist(id: PlaylistID<'a>, source_playlist: PlaylistID<'a>) -> Self {
68        Self {
69            id,
70            query_type: AddPlaylistToPlaylist { source_playlist },
71        }
72    }
73}
74impl<'a> AddPlaylistItemsQuery<'a, AddVideosToPlaylist<'a>> {
75    pub fn new_from_videos(
76        id: PlaylistID<'a>,
77        video_ids: impl IntoIterator<Item = VideoID<'a>>,
78        duplicate_handling_mode: DuplicateHandlingMode,
79    ) -> Self {
80        Self {
81            id,
82            query_type: AddVideosToPlaylist {
83                video_ids: video_ids.into_iter().collect(),
84                duplicate_handling_mode,
85            },
86        }
87    }
88}
89
90impl<A: AuthToken, T: SpecialisedQuery> Query<A> for AddPlaylistItemsQuery<'_, T> {
91    type Output = Vec<AddPlaylistItem>;
92    type Method = PostMethod;
93}
94impl<T: SpecialisedQuery> PostQuery for AddPlaylistItemsQuery<'_, T> {
95    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
96        let serde_json::Value::Object(mut map) = json!({
97            "playlistId" : self.id,
98        }) else {
99            unreachable!()
100        };
101        if let Some(additional_header) = self.query_type.additional_header() {
102            map.insert(additional_header.0, additional_header.1);
103        }
104        map
105    }
106    fn path(&self) -> &str {
107        "browse/edit_playlist"
108    }
109    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
110        vec![]
111    }
112}