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
14pub trait SearchType: Default {
18 fn specialised_params(&self, spelling_mode: &SpellingMode) -> Option<Cow<str>>;
19}
20
21pub trait UnfilteredSearchType: SearchType {}
24
25#[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#[derive(PartialEq, Debug, Clone, Default)]
37pub enum SpellingMode {
38 #[default]
41 ExactMatch,
42 WithSuggestions,
43}
44
45#[derive(Default, Debug, Clone, PartialEq)]
47pub struct BasicSearch;
48#[derive(Default, Debug, Clone, PartialEq)]
50pub struct LibrarySearch;
51#[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 Some("agIYAw%3D%3D".into())
67 }
68}
69impl SearchType for LibrarySearch {
70 fn specialised_params(&self, _: &SpellingMode) -> Option<Cow<str>> {
71 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
97impl<'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
109impl<'a> SearchQuery<'a, BasicSearch> {
111 pub fn new<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, BasicSearch> {
112 SearchQuery {
113 query: q.into(),
114 spelling_mode: SpellingMode::default(),
115 search_type: BasicSearch {},
116 }
117 }
118}
119
120impl<'a, S: SearchType> SearchQuery<'a, S> {
121 pub fn with_spelling_mode(mut self, spelling_mode: SpellingMode) -> Self {
123 self.spelling_mode = spelling_mode;
124 self
125 }
126 pub fn with_query<Q: Into<Cow<'a, str>>>(mut self, query: Q) -> Self {
128 self.query = query.into();
129 self
130 }
131}
132
133impl<'a> SearchQuery<'a, BasicSearch> {
134 pub fn with_filter<F: FilteredSearchType>(
136 self,
137 filter: F,
138 ) -> SearchQuery<'a, FilteredSearch<F>> {
139 SearchQuery {
140 query: self.query,
141 spelling_mode: self.spelling_mode,
142 search_type: FilteredSearch { filter },
143 }
144 }
145 pub fn uploads(self) -> SearchQuery<'a, UploadSearch> {
147 SearchQuery {
148 query: self.query,
149 spelling_mode: self.spelling_mode,
150 search_type: UploadSearch,
151 }
152 }
153 pub fn library(self) -> SearchQuery<'a, LibrarySearch> {
155 SearchQuery {
156 query: self.query,
157 spelling_mode: self.spelling_mode,
158 search_type: LibrarySearch,
159 }
160 }
161}
162
163impl<'a, F: FilteredSearchType> SearchQuery<'a, FilteredSearch<F>> {
164 pub fn with_filter<F2: FilteredSearchType>(
166 self,
167 filter: F2,
168 ) -> SearchQuery<'a, FilteredSearch<F2>> {
169 SearchQuery {
170 query: self.query,
171 spelling_mode: self.spelling_mode,
172 search_type: FilteredSearch { filter },
173 }
174 }
175 pub fn unfiltered(self) -> SearchQuery<'a, BasicSearch> {
177 SearchQuery {
178 query: self.query,
179 spelling_mode: self.spelling_mode,
180 search_type: BasicSearch,
181 }
182 }
183}
184
185impl<'a> SearchQuery<'a, UploadSearch> {
186 pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
188 SearchQuery {
189 query: self.query,
190 spelling_mode: self.spelling_mode,
191 search_type: BasicSearch,
192 }
193 }
194}
195impl<'a> SearchQuery<'a, LibrarySearch> {
196 pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
198 SearchQuery {
199 query: self.query,
200 spelling_mode: self.spelling_mode,
201 search_type: BasicSearch,
202 }
203 }
204}
205
206#[derive(PartialEq, Debug, Clone)]
207pub struct GetSearchSuggestionsQuery<'a> {
208 query: Cow<'a, str>,
209}
210
211impl<'a> GetSearchSuggestionsQuery<'a> {
212 pub fn new<S: Into<Cow<'a, str>>>(value: S) -> GetSearchSuggestionsQuery<'a> {
213 GetSearchSuggestionsQuery {
214 query: value.into(),
215 }
216 }
217}
218
219impl<'a, S: Into<Cow<'a, str>>> From<S> for GetSearchSuggestionsQuery<'a> {
220 fn from(value: S) -> GetSearchSuggestionsQuery<'a> {
221 GetSearchSuggestionsQuery::new(value)
222 }
223}
224
225impl<A: AuthToken> Query<A> for GetSearchSuggestionsQuery<'_> {
226 type Output = Vec<SearchSuggestion>;
227 type Method = PostMethod;
228}
229impl PostQuery for GetSearchSuggestionsQuery<'_> {
230 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
231 let value = self.query.as_ref().into();
232 serde_json::Map::from_iter([("input".into(), value)])
233 }
234 fn path(&self) -> &str {
235 "music/get_search_suggestions"
236 }
237 fn params(&self) -> Vec<(&str, Cow<str>)> {
238 vec![]
239 }
240}
241
242fn search_query_header<S: SearchType>(
243 query: &SearchQuery<S>,
244) -> serde_json::Map<String, serde_json::Value> {
245 let value = query.query.as_ref().into();
246 let params = search_query_params(query);
247 if let Some(params) = params {
248 serde_json::Map::from_iter([
249 ("query".to_string(), value),
250 ("params".to_string(), params.into()),
251 ])
252 } else {
253 serde_json::Map::from_iter([("query".to_string(), value)])
254 }
255}
256fn search_query_params<'a, S: SearchType>(query: &'a SearchQuery<'a, S>) -> Option<Cow<'a, str>> {
257 query.search_type.specialised_params(&query.spelling_mode)
258}