Closed ShayBox closed 1 year ago
I can't reproduce the issue on my end so it must be that your token doesn't actually have the required scopes. I see you're requesting the correct scopes, however you should set .show_dialog(true)
on the authorization code client builder to force the user to have to re-approve the application with the new scopes. Adding new scopes won't retroactively apply to existing tokens.
I tried show_dialog
, removing access to my app, creating a new app, and creating a test project, I can't get it to work
use anyhow::bail;
use ferrispot::{client::SpotifyClientBuilder, prelude::*, scope::Scope};
use tiny_http::{Header, Response, Server};
use url::Url;
fn main() -> Result<(), anyhow::Error> {
let spotify_client = SpotifyClientBuilder::new("fea982db1b3e4f3993fb1d1e87903d56")
.client_secret("c58e9fd4814c4ffca383389453ecd5b7")
.build_sync()?;
let redirect_uri = "http://127.0.0.1:2345";
let incomplete_auth_code_client = spotify_client
.authorization_code_client(redirect_uri)
.scopes([Scope::UserModifyPlaybackState, Scope::UserReadPlaybackState])
.show_dialog(true)
.build();
let authorize_url = incomplete_auth_code_client.get_authorize_url();
let (code, state) = get_user_authorization(&authorize_url, redirect_uri)?;
let user_client = incomplete_auth_code_client.finalize(code.trim(), state.trim())?;
user_client.pause().send_sync()?;
Ok(())
}
fn get_user_authorization(
authorize_url: &str,
redirect_uri: &str,
) -> Result<(String, String), anyhow::Error> {
match webbrowser::open(authorize_url) {
Ok(ok) => ok,
Err(why) => eprintln!(
"Error when trying to open an URL in your browser: {:?}. \
Please navigate here manually: {}",
why, authorize_url
),
}
let addr = redirect_uri.replace("http://", "").replace("https://", "");
let server = Server::http(addr).expect("Failed to bind server");
let request = match server.recv() {
Ok(rq) => rq,
Err(e) => panic!("Failed to get request: {e}"),
};
let request_url = redirect_uri.to_owned() + request.url();
let parsed_url = Url::parse(&request_url)?;
let header = Header::from_bytes(&b"Content-Type"[..], &b"text/html"[..]).unwrap();
let mut response;
if parsed_url.query_pairs().count() == 2 {
response = Response::from_string(
"<h1>You may close this tab</h1> \
<script>window.close()</script>",
);
} else {
response = Response::from_string("<h1>An error has occurred</h1>");
}
response.add_header(header);
request.respond(response).expect("Failed to send response");
let Some(code) = parsed_url.query_pairs().next() else {
bail!("None")
};
let Some(state) = parsed_url.query_pairs().nth(1) else {
bail!("None")
};
Ok((code.1.into(), state.1.into()))
}
[package]
name = "test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.66"
ferrispot = { version = "0.4.0", features = ["sync"] }
tiny_http = "0.12.0"
url = "2.3.1"
webbrowser = "0.8.2"
If you'd like to try it on my app I can whitelist you, but like I said I tried two apps with the same results, I have no idea why it works for you but not me 🤔
Alright, I've figured it out. I was confused what Spotify responds with when the access token expires, which is indeed a 401 Unauthorized, not a 403 Forbidden. The 403 here is caused by the player command being restricted for the current playback. CurrentlyPlayingItem
has an .actions()
object that has a disallows
field that contains booleans that tell whether or not certain actions are disallowed on the current playback. For example, pausing an already paused playback is disallowed (pausing = true
), or skipping an ad with .next()
is disallowed (skipping_next = true
). Attempting a disallowed action will return a 403 Forbidden. I'll have to update the error handling to account for this case and return a more descriptive error.
Also, you should rotate that client secret if you haven't already.
I don't know if you finished working or not, but I tested the latest commit
I logged the actions and got
Actions {
disallows: Disallows {
interrupting_playback: false,
pausing: false,
resuming: true,
seeking: false,
skipping_next: false,
skipping_prev: false,
toggling_repeat_context: false,
toggling_shuffle: false,
toggling_repeat_track: false,
transferring_playback: false,
},
}
Error: The endpoint is forbidden
The attempted action was pause
, which should be allowed (and appears to be?)
Can you run the program with logging enabled, or run the same request in the Spotify developer console? I'd like to know what the API error response is if it's not about restricted player controls.
Ah, well that's dumb
{
"error": {
"status": 403,
"message": "Player command failed: Premium required",
"reason": "PREMIUM_REQUIRED"
}
}
I had a hunch that was it. I'll add an error for it, thanks!
I'm using 0.4.0 and getting
The endpoint is forbidden
with any playback state control such as pause, next, previous, etc.I believe I have all the required scopes and I've re-authorized to make sure my token is updated.
Log: https://pastebin.com/QvqnnNes
Source: https://pastebin.com/jBZvMD1M
Full Source: https://pastebin.com/b9qA7Jaq