Skip to main content

ytmapi_rs/query/
search.rs

1use super::*;
2use crate::common::SearchSuggestion;
3use crate::parse::SearchResults;
4pub use filteredsearch::*;
5use std::borrow::Cow;
6
7pub mod filteredsearch;
8
9const SPECIALIZED_PLAYLIST_EXACT_MATCH_PARAMS: &str = "BagwQDhAKEAMQBBAJEAU%3D";
10const SPECIALIZED_PLAYLIST_WITH_SUGGESTIONS_PARAMS: &str = "BQgIIAWoMEA4QChADEAQQCRAF";
11const SPECIALIZED_PLAYLIST_PREFIX_PARAMS: &str = "EgeKAQQoA";
12const SEARCH_QUERY_PATH: &str = "search";
13
14// TODO Seal
15// TODO: Add relevant parameters.
16// Implements Default to allow simple implementation of Into<SearchQuery<S>>
17pub trait SearchType: Default {
18    fn specialised_params(&self, spelling_mode: &SpellingMode) -> Option<Cow<'_, str>>;
19}
20
21// Trait constraint - to simplify implementation of Query for BasicSearch,
22// LibrarySearch and UploadSearch.
23pub trait UnfilteredSearchType: SearchType {}
24
25/// An API search query.
26#[derive(PartialEq, Debug, Clone)]
27pub struct SearchQuery<'a, S: SearchType> {
28    query: Cow<'a, str>,
29    spelling_mode: SpellingMode,
30    search_type: S,
31}
32
33/// Whether or not to allow Google to attempt to auto correct spelling as part
34/// of the results. Has no affect on Uploads or Library.
35// XXX: May actually affect Library. To confirm.
36#[derive(PartialEq, Debug, Clone, Default)]
37pub enum SpellingMode {
38    // My personal preference is to use ExactMatch by default, so that's what I've set.
39    // Google's is WithSuggestions.
40    #[default]
41    ExactMatch,
42    WithSuggestions,
43}
44
45/// Helper struct for SearchQuery type state pattern.
46#[derive(Default, Debug, Clone, PartialEq)]
47pub struct BasicSearch;
48/// Helper struct for SearchQuery type state pattern.
49#[derive(Default, Debug, Clone, PartialEq)]
50pub struct LibrarySearch;
51/// Helper struct for SearchQuery type state pattern.
52#[derive(Default, Debug, Clone, PartialEq)]
53pub struct UploadSearch;
54
55impl SearchType for BasicSearch {
56    fn specialised_params(&self, spelling_mode: &SpellingMode) -> Option<Cow<'_, str>> {
57        match spelling_mode {
58            SpellingMode::ExactMatch => Some("EhGKAQ4IARABGAEgASgAOAFAAUICCAE%3D".into()),
59            SpellingMode::WithSuggestions => None,
60        }
61    }
62}
63impl SearchType for UploadSearch {
64    fn specialised_params(&self, _: &SpellingMode) -> Option<Cow<'_, str>> {
65        // TODO: Investigate if spelling suggestions take affect here.
66        Some("agIYAw%3D%3D".into())
67    }
68}
69impl SearchType for LibrarySearch {
70    fn specialised_params(&self, _: &SpellingMode) -> Option<Cow<'_, str>> {
71        // XXX: It may be possible to actually filter these, see sigma67/ytmusicapi for
72        // details. TODO: Investigate if spelling suggestions take affect here.
73        Some("agIYBA%3D%3D".into())
74    }
75}
76
77impl UnfilteredSearchType for BasicSearch {}
78impl UnfilteredSearchType for UploadSearch {}
79impl UnfilteredSearchType for LibrarySearch {}
80
81impl<S: UnfilteredSearchType, A: AuthToken> Query<A> for SearchQuery<'_, S> {
82    type Output = SearchResults;
83    type Method = PostMethod;
84}
85impl<S: UnfilteredSearchType> PostQuery for SearchQuery<'_, S> {
86    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
87        search_query_header(self)
88    }
89    fn path(&self) -> &str {
90        SEARCH_QUERY_PATH
91    }
92    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
93        vec![]
94    }
95}
96
97// This currently requires type annotations.
98// By default, uses SpellingMode exactmatch.
99impl<'a, Q: Into<Cow<'a, str>>, S: SearchType> From<Q> for SearchQuery<'a, S> {
100    fn from(value: Q) -> SearchQuery<'a, S> {
101        SearchQuery {
102            query: value.into(),
103            spelling_mode: SpellingMode::default(),
104            search_type: S::default(),
105        }
106    }
107}
108
109// By default, uses SpellingMode exactmatch.
110impl<'a> SearchQuery<'a, BasicSearch> {
111    #[deprecated = "To be removed in future release - see issue #353"]
112    pub fn new<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, BasicSearch> {
113        SearchQuery {
114            query: q.into(),
115            spelling_mode: SpellingMode::default(),
116            search_type: BasicSearch {},
117        }
118    }
119}
120
121impl<'a, S: SearchType> SearchQuery<'a, S> {
122    /// Set spelling mode.
123    pub fn with_spelling_mode(mut self, spelling_mode: SpellingMode) -> Self {
124        self.spelling_mode = spelling_mode;
125        self
126    }
127    /// Chnage the set query.
128    pub fn with_query<Q: Into<Cow<'a, str>>>(mut self, query: Q) -> Self {
129        self.query = query.into();
130        self
131    }
132}
133
134impl<'a> SearchQuery<'a, BasicSearch> {
135    /// Apply a filter to the search. May change type of results returned.
136    #[deprecated = "To be removed in future release - see issue #353"]
137    pub fn with_filter<F: FilteredSearchType>(
138        self,
139        filter: F,
140    ) -> SearchQuery<'a, FilteredSearch<F>> {
141        SearchQuery {
142            query: self.query,
143            spelling_mode: self.spelling_mode,
144            search_type: FilteredSearch { filter },
145        }
146    }
147    /// Search only uploads.
148    #[deprecated = "To be removed in future release - see issue #353"]
149    pub fn uploads(self) -> SearchQuery<'a, UploadSearch> {
150        SearchQuery {
151            query: self.query,
152            spelling_mode: self.spelling_mode,
153            search_type: UploadSearch,
154        }
155    }
156    /// Search only library.
157    #[deprecated = "To be removed in future release - see issue #353"]
158    pub fn library(self) -> SearchQuery<'a, LibrarySearch> {
159        SearchQuery {
160            query: self.query,
161            spelling_mode: self.spelling_mode,
162            search_type: LibrarySearch,
163        }
164    }
165}
166
167impl<'a, F: FilteredSearchType> SearchQuery<'a, FilteredSearch<F>> {
168    pub fn new_filtered<Q: Into<Cow<'a, str>>>(
169        q: Q,
170        filter: F,
171    ) -> SearchQuery<'a, FilteredSearch<F>> {
172        SearchQuery {
173            query: q.into(),
174            spelling_mode: SpellingMode::default(),
175            search_type: FilteredSearch { filter },
176        }
177    }
178    /// Apply a filter to the search. May change type of results returned.
179    pub fn with_filter<F2: FilteredSearchType>(
180        self,
181        filter: F2,
182    ) -> SearchQuery<'a, FilteredSearch<F2>> {
183        SearchQuery {
184            query: self.query,
185            spelling_mode: self.spelling_mode,
186            search_type: FilteredSearch { filter },
187        }
188    }
189    /// Remove filter from the query.
190    #[deprecated = "To be removed in future release - see issue #353"]
191    pub fn unfiltered(self) -> SearchQuery<'a, BasicSearch> {
192        SearchQuery {
193            query: self.query,
194            spelling_mode: self.spelling_mode,
195            search_type: BasicSearch,
196        }
197    }
198}
199
200impl<'a> SearchQuery<'a, UploadSearch> {
201    /// New upload search query
202    pub fn new_uploads<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, UploadSearch> {
203        SearchQuery {
204            query: q.into(),
205            spelling_mode: SpellingMode::default(),
206            search_type: UploadSearch,
207        }
208    }
209    /// Change scope to search generally instead of Uploads.
210    pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
211        SearchQuery {
212            query: self.query,
213            spelling_mode: self.spelling_mode,
214            search_type: BasicSearch,
215        }
216    }
217}
218impl<'a> SearchQuery<'a, LibrarySearch> {
219    /// New library search query
220    pub fn new_library<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, LibrarySearch> {
221        SearchQuery {
222            query: q.into(),
223            spelling_mode: SpellingMode::default(),
224            search_type: LibrarySearch,
225        }
226    }
227    /// Change scope to search generally instead of Library.
228    pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
229        SearchQuery {
230            query: self.query,
231            spelling_mode: self.spelling_mode,
232            search_type: BasicSearch,
233        }
234    }
235}
236
237#[derive(PartialEq, Debug, Clone)]
238pub struct GetSearchSuggestionsQuery<'a> {
239    query: Cow<'a, str>,
240}
241
242impl<'a> GetSearchSuggestionsQuery<'a> {
243    pub fn new<S: Into<Cow<'a, str>>>(value: S) -> GetSearchSuggestionsQuery<'a> {
244        GetSearchSuggestionsQuery {
245            query: value.into(),
246        }
247    }
248}
249
250impl<'a, S: Into<Cow<'a, str>>> From<S> for GetSearchSuggestionsQuery<'a> {
251    fn from(value: S) -> GetSearchSuggestionsQuery<'a> {
252        GetSearchSuggestionsQuery::new(value)
253    }
254}
255
256impl<A: AuthToken> Query<A> for GetSearchSuggestionsQuery<'_> {
257    type Output = Vec<SearchSuggestion>;
258    type Method = PostMethod;
259}
260impl PostQuery for GetSearchSuggestionsQuery<'_> {
261    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
262        let value = self.query.as_ref().into();
263        serde_json::Map::from_iter([("input".into(), value)])
264    }
265    fn path(&self) -> &str {
266        "music/get_search_suggestions"
267    }
268    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
269        vec![]
270    }
271}
272
273fn search_query_header<S: SearchType>(
274    query: &SearchQuery<S>,
275) -> serde_json::Map<String, serde_json::Value> {
276    let value = query.query.as_ref().into();
277    let params = search_query_params(query);
278    if let Some(params) = params {
279        serde_json::Map::from_iter([
280            ("query".to_string(), value),
281            ("params".to_string(), params.into()),
282        ])
283    } else {
284        serde_json::Map::from_iter([("query".to_string(), value)])
285    }
286}
287fn search_query_params<'a, S: SearchType>(query: &'a SearchQuery<'a, S>) -> Option<Cow<'a, str>> {
288    query.search_type.specialised_params(&query.spelling_mode)
289}