1
0
Fork 0
mirror of https://github.com/librespot-org/librespot.git synced 2025-10-05 02:39:53 +02:00

Improvements towards supporting pagination

Not there yet, as Apollo stations always return autoplay
recommendations even if you set autoplay to false. Along the way
as an effort to bring the protocol up to spec:

- And support for and use different Apollo station scopes depending
  on whether we are using autoplay or not. For autoplay, get a
  "stations" scope and follow the "tracks" pages from there. Otherwise
  use "tracks" immediately for the active scope (playlist, album).

- For the above point we only need the fields from `PageContext`
  so use that instead of a `StationContext`.

- Add some documentation from API reverse engineering: things seen
  in the wild, some of them to do, others documented for posterity's
  sake.

- Update the Spirc device state based on what the latest desktop
  client puts out. Unfortunately none of it seems to change the
  behavior necessary to support external episodes, shows, but
  at least we're doing the right thing.

- Add a salt to HTTPS queries to defeat any caching.

- Add country metrics to HTTPS queries.

- Fix `get_radio_for_track` to use the right Spotify ID format.

- Fix a bug from the previous commit, where the playback position
  might not advance when hitting next and the autoplay context
  is loaded initially.
This commit is contained in:
Roderick van Domburg 2022-10-01 23:01:17 +02:00
parent bfb7d5689c
commit f10b8f69f8
No known key found for this signature in database
GPG key ID: FE2585E713F9F30A
2 changed files with 139 additions and 65 deletions

View file

@ -15,6 +15,7 @@ use hyper::{
Body, HeaderMap, Method, Request,
};
use protobuf::{Message, ProtobufEnum};
use rand::RngCore;
use sha1::{Digest, Sha1};
use sysinfo::{System, SystemExt};
use thiserror::Error;
@ -435,13 +436,26 @@ impl SpClient {
let mut url = self.base_url().await?;
url.push_str(endpoint);
// Add metrics. There is also an optional `partner` key with a value like
// `vodafone-uk` but we've yet to discover how we can find that value.
let separator = match url.find('?') {
Some(_) => "&",
None => "?",
};
let _ = write!(url, "{}product=0", separator);
// Add metrics. There is also an optional `partner` key with a value like
// `vodafone-uk` but we've yet to discover how we can find that value.
// For the sake of documentation you could also do "product=free" but
// we only support premium anyway.
let _ = write!(
url,
"{}product=0&country={}",
separator,
self.session().country()
);
// Defeat caches. Spotify-generated URLs already contain this.
if !url.contains("salt=") {
let _ = write!(url, "&salt={}", rand::thread_rng().next_u32());
}
let mut request = Request::builder()
.method(method)
@ -616,29 +630,49 @@ impl SpClient {
pub async fn get_radio_for_track(&self, track_id: &SpotifyId) -> SpClientResult {
let endpoint = format!(
"/inspiredby-mix/v2/seed_to_playlist/{}?response-format=json",
track_id.to_base62()?
track_id.to_uri()?
);
self.request_as_json(&Method::GET, &endpoint, None, None)
.await
}
// Known working scopes: stations, tracks
// For others see: https://gist.github.com/roderickvd/62df5b74d2179a12de6817a37bb474f9
//
// Seen-in-the-wild but unimplemented query parameters:
// - image_style=gradient_overlay
// - excludeClusters=true
// - language=en
// - count_tracks=0
// - market=from_token
pub async fn get_apollo_station(
&self,
scope: &str,
context_uri: &str,
count: usize,
count: Option<usize>,
previous_tracks: Vec<SpotifyId>,
autoplay: bool,
) -> SpClientResult {
let mut endpoint = format!(
"/radio-apollo/v3/{}/{}?autoplay={}",
scope, context_uri, autoplay,
);
// Spotify has a default of 50
if let Some(count) = count {
let _ = write!(endpoint, "&count={}", count);
}
let previous_track_str = previous_tracks
.iter()
.map(|track| track.to_base62())
.collect::<Result<Vec<_>, _>>()?
.join(",");
let endpoint = format!(
"/radio-apollo/v3/stations/{}?count={}&prev_tracks={}&autoplay={}",
context_uri, count, previous_track_str, autoplay,
);
// better than checking `previous_tracks.len() > 0` because the `filter_map` could still return 0 items
if !previous_track_str.is_empty() {
let _ = write!(endpoint, "&prev_tracks={}", previous_track_str);
}
self.request_as_json(&Method::GET, &endpoint, None, None)
.await
@ -650,6 +684,9 @@ impl SpClient {
.await
}
// TODO: Seen-in-the-wild but unimplemented endpoints
// - /presence-view/v1/buddylist
// TODO: Find endpoint for newer canvas.proto and upgrade to that.
pub async fn get_canvases(&self, request: EntityCanvazRequest) -> SpClientResult {
let endpoint = "/canvaz-cache/v0/canvases";