1use super::*;
2use crate::{common::SearchSuggestion, parse::SearchResults};
3pub use filteredsearch::*;
4use std::borrow::Cow;
5
6pub mod filteredsearch;
7
8const SPECIALIZED_PLAYLIST_EXACT_MATCH_PARAMS: &str = "BagwQDhAKEAMQBBAJEAU%3D";
9const SPECIALIZED_PLAYLIST_WITH_SUGGESTIONS_PARAMS: &str = "BQgIIAWoMEA4QChADEAQQCRAF";
10const SPECIALIZED_PLAYLIST_PREFIX_PARAMS: &str = "EgeKAQQoA";
11const SEARCH_QUERY_PATH: &str = "search";
12
13pub trait SearchType: Default {
17 fn specialised_params(&self, spelling_mode: &SpellingMode) -> Option<Cow<str>>;
18}
19
20pub trait UnfilteredSearchType: SearchType {}
23
24#[derive(PartialEq, Debug, Clone)]
26pub struct SearchQuery<'a, S: SearchType> {
27 query: Cow<'a, str>,
28 spelling_mode: SpellingMode,
29 search_type: S,
30}
31
32#[derive(PartialEq, Debug, Clone, Default)]
36pub enum SpellingMode {
37 #[default]
40 ExactMatch,
41 WithSuggestions,
42}
43
44#[derive(Default, Debug, Clone, PartialEq)]
46pub struct BasicSearch;
47#[derive(Default, Debug, Clone, PartialEq)]
49pub struct LibrarySearch;
50#[derive(Default, Debug, Clone, PartialEq)]
52pub struct UploadSearch;
53
54impl SearchType for BasicSearch {
55 fn specialised_params(&self, spelling_mode: &SpellingMode) -> Option<Cow<str>> {
56 match spelling_mode {
57 SpellingMode::ExactMatch => Some("EhGKAQ4IARABGAEgASgAOAFAAUICCAE%3D".into()),
58 SpellingMode::WithSuggestions => None,
59 }
60 }
61}
62impl SearchType for UploadSearch {
63 fn specialised_params(&self, _: &SpellingMode) -> Option<Cow<str>> {
64 Some("agIYAw%3D%3D".into())
66 }
67}
68impl SearchType for LibrarySearch {
69 fn specialised_params(&self, _: &SpellingMode) -> Option<Cow<str>> {
70 Some("agIYBA%3D%3D".into())
73 }
74}
75
76impl UnfilteredSearchType for BasicSearch {}
77impl UnfilteredSearchType for UploadSearch {}
78impl UnfilteredSearchType for LibrarySearch {}
79
80impl<S: UnfilteredSearchType, A: AuthToken> Query<A> for SearchQuery<'_, S> {
81 type Output = SearchResults;
82 type Method = PostMethod;
83}
84impl<S: UnfilteredSearchType> PostQuery for SearchQuery<'_, S> {
85 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
86 search_query_header(self)
87 }
88 fn path(&self) -> &str {
89 SEARCH_QUERY_PATH
90 }
91 fn params(&self) -> Vec<(&str, Cow<str>)> {
92 vec![]
93 }
94}
95
96impl<'a, Q: Into<Cow<'a, str>>, S: SearchType> From<Q> for SearchQuery<'a, S> {
99 fn from(value: Q) -> SearchQuery<'a, S> {
100 SearchQuery {
101 query: value.into(),
102 spelling_mode: SpellingMode::default(),
103 search_type: S::default(),
104 }
105 }
106}
107
108impl<'a> SearchQuery<'a, BasicSearch> {
110 pub fn new<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, BasicSearch> {
111 SearchQuery {
112 query: q.into(),
113 spelling_mode: SpellingMode::default(),
114 search_type: BasicSearch {},
115 }
116 }
117}
118
119impl<'a, S: SearchType> SearchQuery<'a, S> {
120 pub fn with_spelling_mode(mut self, spelling_mode: SpellingMode) -> Self {
122 self.spelling_mode = spelling_mode;
123 self
124 }
125 pub fn with_query<Q: Into<Cow<'a, str>>>(mut self, query: Q) -> Self {
127 self.query = query.into();
128 self
129 }
130}
131
132impl<'a> SearchQuery<'a, BasicSearch> {
133 pub fn with_filter<F: FilteredSearchType>(
135 self,
136 filter: F,
137 ) -> SearchQuery<'a, FilteredSearch<F>> {
138 SearchQuery {
139 query: self.query,
140 spelling_mode: self.spelling_mode,
141 search_type: FilteredSearch { filter },
142 }
143 }
144 pub fn uploads(self) -> SearchQuery<'a, UploadSearch> {
146 SearchQuery {
147 query: self.query,
148 spelling_mode: self.spelling_mode,
149 search_type: UploadSearch,
150 }
151 }
152 pub fn library(self) -> SearchQuery<'a, LibrarySearch> {
154 SearchQuery {
155 query: self.query,
156 spelling_mode: self.spelling_mode,
157 search_type: LibrarySearch,
158 }
159 }
160}
161
162impl<'a, F: FilteredSearchType> SearchQuery<'a, FilteredSearch<F>> {
163 pub fn with_filter<F2: FilteredSearchType>(
165 self,
166 filter: F2,
167 ) -> SearchQuery<'a, FilteredSearch<F2>> {
168 SearchQuery {
169 query: self.query,
170 spelling_mode: self.spelling_mode,
171 search_type: FilteredSearch { filter },
172 }
173 }
174 pub fn unfiltered(self) -> SearchQuery<'a, BasicSearch> {
176 SearchQuery {
177 query: self.query,
178 spelling_mode: self.spelling_mode,
179 search_type: BasicSearch,
180 }
181 }
182}
183
184impl<'a> SearchQuery<'a, UploadSearch> {
185 pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
187 SearchQuery {
188 query: self.query,
189 spelling_mode: self.spelling_mode,
190 search_type: BasicSearch,
191 }
192 }
193}
194impl<'a> SearchQuery<'a, LibrarySearch> {
195 pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
197 SearchQuery {
198 query: self.query,
199 spelling_mode: self.spelling_mode,
200 search_type: BasicSearch,
201 }
202 }
203}
204
205#[derive(PartialEq, Debug, Clone)]
206pub struct GetSearchSuggestionsQuery<'a> {
207 query: Cow<'a, str>,
208}
209
210impl<'a> GetSearchSuggestionsQuery<'a> {
211 pub fn new<S: Into<Cow<'a, str>>>(value: S) -> GetSearchSuggestionsQuery<'a> {
212 GetSearchSuggestionsQuery {
213 query: value.into(),
214 }
215 }
216}
217
218impl<'a, S: Into<Cow<'a, str>>> From<S> for GetSearchSuggestionsQuery<'a> {
219 fn from(value: S) -> GetSearchSuggestionsQuery<'a> {
220 GetSearchSuggestionsQuery::new(value)
221 }
222}
223
224impl<A: AuthToken> Query<A> for GetSearchSuggestionsQuery<'_> {
225 type Output = Vec<SearchSuggestion>;
226 type Method = PostMethod;
227}
228impl PostQuery for GetSearchSuggestionsQuery<'_> {
229 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
230 let value = self.query.as_ref().into();
231 serde_json::Map::from_iter([("input".into(), value)])
232 }
233 fn path(&self) -> &str {
234 "music/get_search_suggestions"
235 }
236 fn params(&self) -> Vec<(&str, Cow<str>)> {
237 vec![]
238 }
239}
240
241fn search_query_header<S: SearchType>(
242 query: &SearchQuery<S>,
243) -> serde_json::Map<String, serde_json::Value> {
244 let value = query.query.as_ref().into();
245 let params = search_query_params(query);
246 if let Some(params) = params {
247 serde_json::Map::from_iter([
248 ("query".to_string(), value),
249 ("params".to_string(), params.into()),
250 ])
251 } else {
252 serde_json::Map::from_iter([("query".to_string(), value)])
253 }
254}
255fn search_query_params<'a, S: SearchType>(query: &'a SearchQuery<'a, S>) -> Option<Cow<'a, str>> {
256 query.search_type.specialised_params(&query.spelling_mode)
257}