feat(search): use Qobuz API most_popular field directly
Replace heuristic-based most popular detection with Qobuz's actual most_popular field from the catalog/search endpoint. - Add CATALOG_SEARCH endpoint path - Make app_id() and get_http() public on QobuzClient - Add MostPopularItem enum with tagged serialization (artists/albums/tracks) - Update search_all to use catalog/search and parse most_popular field - Update SearchView template to use allResults.most_popular with new types - Add MostPopularItem type to searchState store
This commit is contained in:
1 parent
aa16a9276b
commit
d8bd93374f
5 files changed
+84
-68
No files matched your search
@@ -73,8 +73,8 @@ impl QobuzClient {
|
||||
self.locale.read().await.clone()
|
||||
}
|
||||
|
||||
/// Get app ID
|
||||
async fn app_id(&self) -> Result<String> {
|
||||
/// Get app ID (public for catalog search)
|
||||
pub async fn app_id(&self) -> Result<String> {
|
||||
self.tokens
|
||||
.read()
|
||||
.await
|
||||
@@ -83,6 +83,11 @@ impl QobuzClient {
|
||||
.ok_or_else(|| ApiError::BundleExtractionError("Client not initialized".to_string()))
|
||||
}
|
||||
|
||||
/// Get HTTP client reference (public for catalog search)
|
||||
pub fn get_http(&self) -> &Client {
|
||||
&self.http
|
||||
}
|
||||
|
||||
/// Get validated secret (validates on first use)
|
||||
async fn secret(&self) -> Result<String> {
|
||||
// Check if we already have a validated secret
|
||||
|
||||
@@ -39,6 +39,9 @@ pub mod paths {
|
||||
|
||||
// Label
|
||||
pub const LABEL_GET: &str = "/label/get";
|
||||
|
||||
// Catalog (combined search)
|
||||
pub const CATALOG_SEARCH: &str = "/catalog/search";
|
||||
}
|
||||
|
||||
/// Build full URL for an endpoint
|
||||
|
||||
@@ -2,16 +2,27 @@
|
||||
|
||||
use tauri::State;
|
||||
|
||||
use crate::api::{Album, Artist, ArtistAlbums, LabelDetail, SearchResultsPage, Track};
|
||||
use crate::api::{endpoints, endpoints::paths, Album, Artist, ArtistAlbums, LabelDetail, SearchResultsPage, Track};
|
||||
use crate::api_cache::ApiCacheState;
|
||||
use crate::AppState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
/// Most popular item from catalog search - can be a track, album, or artist
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "content", rename_all = "lowercase")]
|
||||
pub enum MostPopularItem {
|
||||
Tracks(Track),
|
||||
Albums(Album),
|
||||
Artists(Artist),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SearchAllResults {
|
||||
pub albums: SearchResultsPage<Album>,
|
||||
pub tracks: SearchResultsPage<Track>,
|
||||
pub artists: SearchResultsPage<Artist>,
|
||||
pub most_popular: Option<MostPopularItem>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -62,22 +73,59 @@ pub async fn search_artists(
|
||||
#[tauri::command]
|
||||
pub async fn search_all(
|
||||
query: String,
|
||||
search_type: Option<String>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<SearchAllResults, String> {
|
||||
let client = state.client.lock().await;
|
||||
let st = search_type.as_deref();
|
||||
|
||||
let (albums_result, tracks_result, artists_result) = tokio::join!(
|
||||
client.search_albums(&query, 30, 0, st),
|
||||
client.search_tracks(&query, 8, 0, st),
|
||||
client.search_artists(&query, 12, 0, st)
|
||||
);
|
||||
// Use catalog/search endpoint which returns everything including most_popular
|
||||
let url = endpoints::build_url(paths::CATALOG_SEARCH);
|
||||
let response: Value = client
|
||||
.get_http()
|
||||
.get(&url)
|
||||
.header("X-App-Id", client.app_id().await.map_err(|e| e.to_string())?)
|
||||
.query(&[
|
||||
("query", query.as_str()),
|
||||
("limit", "30"),
|
||||
("offset", "0"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Request failed: {}", e))?
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("JSON parse failed: {}", e))?;
|
||||
|
||||
// Parse albums
|
||||
let albums: SearchResultsPage<Album> = response
|
||||
.get("albums")
|
||||
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
||||
.unwrap_or_else(|| SearchResultsPage { items: vec![], total: 0, offset: 0, limit: 30 });
|
||||
|
||||
// Parse tracks
|
||||
let tracks: SearchResultsPage<Track> = response
|
||||
.get("tracks")
|
||||
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
||||
.unwrap_or_else(|| SearchResultsPage { items: vec![], total: 0, offset: 0, limit: 30 });
|
||||
|
||||
// Parse artists
|
||||
let artists: SearchResultsPage<Artist> = response
|
||||
.get("artists")
|
||||
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
||||
.unwrap_or_else(|| SearchResultsPage { items: vec![], total: 0, offset: 0, limit: 30 });
|
||||
|
||||
// Parse most_popular - get the first item
|
||||
let most_popular: Option<MostPopularItem> = response
|
||||
.get("most_popular")
|
||||
.and_then(|mp| mp.get("items"))
|
||||
.and_then(|items| items.as_array())
|
||||
.and_then(|arr| arr.first())
|
||||
.and_then(|item| serde_json::from_value(item.clone()).ok());
|
||||
|
||||
Ok(SearchAllResults {
|
||||
albums: albums_result.map_err(|e| e.to_string())?,
|
||||
tracks: tracks_result.map_err(|e| e.to_string())?,
|
||||
artists: artists_result.map_err(|e| e.to_string())?,
|
||||
albums,
|
||||
tracks,
|
||||
artists,
|
||||
most_popular,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user