feat: add Qobuz playlist follow/unfollow (#172)

Discovered undocumented Qobuz API endpoints from bundle analysis:
- POST /playlist/subscribe { playlist_id }
- POST /playlist/unsubscribe { playlist_id }

This is the native "follow playlist" feature that syncs across all
Qobuz clients, separate from QBZ's existing "Copy to Library" (local
copy) and "Favorite" (local flag) features.

Added:
- subscribe_playlist/unsubscribe_playlist in both qbz-qobuz and legacy client
- v2_qobuz_subscribe_playlist / v2_qobuz_unsubscribe_playlist commands
- Bookmark toggle button in PlaylistDetailView (next to Copy button)
- i18n keys in all 5 locales

cc
This commit is contained in:
vicrodh committed 2026-03-19 11:36:56 -06:00
1 parent 5488839e97
commit 1ad16338fa
12 files changed
+163 -2

No files matched your search

+48
View File
@@ -1622,6 +1622,54 @@ impl QobuzClient {
Ok(response)
}
/// Subscribe to a Qobuz playlist (follow it in the user's library)
pub async fn subscribe_playlist(&self, playlist_id: u64) -> Result<()> {
let url = endpoints::build_url(paths::PLAYLIST_SUBSCRIBE);
let response = self
.http
.get(&url)
.headers(self.authenticated_headers().await?)
.query(&[("playlist_id", playlist_id.to_string())])
.send()
.await?;
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
return Err(ApiError::ApiResponse(format!(
"playlist/subscribe failed ({}): {}",
status, body
)));
}
Ok(())
}
/// Unsubscribe from a Qobuz playlist
pub async fn unsubscribe_playlist(&self, playlist_id: u64) -> Result<()> {
let url = endpoints::build_url(paths::PLAYLIST_UNSUBSCRIBE);
let response = self
.http
.get(&url)
.headers(self.authenticated_headers().await?)
.query(&[("playlist_id", playlist_id.to_string())])
.send()
.await?;
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
return Err(ApiError::ApiResponse(format!(
"playlist/unsubscribe failed ({}): {}",
status, body
)));
}
Ok(())
}
/// Add item to favorites
pub async fn add_favorite(&self, fav_type: &str, item_id: &str) -> Result<()> {
let url = endpoints::build_url(paths::FAVORITE_CREATE);
+2
View File
@@ -39,6 +39,8 @@ pub mod paths {
pub const PLAYLIST_DELETE_TRACKS: &str = "/playlist/deleteTracks";
pub const PLAYLIST_UPDATE: &str = "/playlist/update";
pub const PLAYLIST_GET_TAGS: &str = "/playlist/getTags";
pub const PLAYLIST_SUBSCRIBE: &str = "/playlist/subscribe";
pub const PLAYLIST_UNSUBSCRIBE: &str = "/playlist/unsubscribe";
// Favorites
pub const FAVORITE_GET_USER_FAVORITES: &str = "/favorite/getUserFavorites";