spotipy-dev / spotipy

A light weight Python library for the Spotify Web API
http://spotipy.readthedocs.org
MIT License
5.04k stars 959 forks source link

How to suppress opening browser for command line tool? #715

Closed joebonneau closed 3 years ago

joebonneau commented 3 years ago

Hi there,

First and foremost, thanks so much for developing this package. Having a lot of fun using it!

I'm developing a CLI and I want to suppress the browser from opening without prompting the user for the URL. When authenticating using SpotifyOAuth and specifying open_browser=True, the browser opens a new tab and immediately closes it (assuming that I already gave the API permission to that function on my account, otherwise that appears and persists).

To avoid that, I tried specifying open_browser=False - here is what I get with this input (my application is called spoticli):

▶ spoticli play 
Playback resumed.
Go to the following URL: https://accounts.spotify.com/authorize?client_id=520cdb9e1e8e401480625d095e3ad2ed&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8887%2Fcallback&scope=user-read-playback-state
Enter the URL you were redirected to: 

Neither outcome is desirable for what I'd ideally want. Ideally, assuming the user has already given Spotify the proper permissions, the entire process is headless and seamless. Any thoughts on how to achieve this?

Thanks!

stephanebruckert commented 3 years ago

I'm not totally sure I understand. When signing in a user from a Spotify app (here, your CLI), the user has to get redirected to the Spotify login window at least once to prove authentication and generate a token. Are you trying to avoid that?

This particular feature (open_browser) is useful in case the machine doesn't have a browser installed (for example you ssh into a remote machine). But the user still has to open the browser somewhere else, on another machine.

joebonneau commented 3 years ago

the user has to get redirected to the Spotify login window at least once to prove authentication and generate a token. Are you trying to avoid that?

My intent was to have the user be redirected one time to generate the token and then avoid any browser involvement on the user end beyond that. I'm thinking the issue I'm running into here is related to the way that I'm implementing the authentication. For some context, I submitted this question on Stack Overflow yesterday because I was having trouble passing the auth object to other commands. To get around that, I created a function connect that is called with every command:

def connect(
    scope: str,
    client_id: str = SPOTIFY_CLIENT_ID,
    client_secret: str = SPOTIFY_CLIENT_SECRET,
    redirect_uri: str = SPOTIFY_REDIRECT_URI,
) -> sp.Spotify:
    try:
        auth = sp.Spotify(
            auth_manager=SpotifyOAuth(
                scope=scope,
                client_id=client_id,
                client_secret=client_secret,
                redirect_uri=redirect_uri,
            )
        )

        return auth
    except:
        click.secho(
            "API authorization failed! Did you remember to set the environment variables?",
            fg="red",
        )

where the specific scope required for the action is passed in. Example below:

def next_track():
    sp_auth = connect(scope=USER_MODIFY_PLAYBACK_STATE)
    sp_auth.next_track()

    get_current_playback(display=True)

After reading your response, I'm guessing that my issue is due to the fact that a new authentication is happening with each call which requires the browser to open. Maybe the real question here is how to pass around the auth object so that I can change the implementation in the app.

stephanebruckert commented 3 years ago

Normally passing around sp_auth is the way to do it, so you would do:

sp_auth = connect(scope=USER_MODIFY_PLAYBACK_STATE)  # only once
next_track(sp_auth)
next_track(sp_auth)
next_track(sp_auth)

def next_track(sp_auth):
    sp_auth.next_track()
    get_current_playback(display=True)

So there is no need to connect multiple times, even though that still shouldn't open a new browser window every time, since the token is stored on the hard drive and spotipy is able to find the previous one. It doesn't seem to be the case. Can you verify that a hidden .cache file is stored in the same folder of your script? What OS are you using? Something tells me that your script is not allowed to create files, but it should. Can you see any error message/warning in the logs when running the script?

joebonneau commented 3 years ago

The method you mentioned was how I initially assumed I'd accomplish what I was trying to do, but it wasn't working in context of the click package I'm using to design the CLI. Maybe I need to go back to the drawing board on that piece.

There is a hidden .cache file that is generated and stored in the same folder as my script. I'm running on an Ubuntu machine. I don't see any specific error or warning other than that a GET or POST request was denied if authentication was unsuccessful.

stephanebruckert commented 3 years ago

To confirm whether the issue is in spotipy or not, you could try this example https://github.com/plamere/spotipy/blob/master/examples/my_playlists.py

Just run it multiple times with:

SPOTIPY_CLIENT_ID=replace SPOTIPY_CLIENT_SECRET=replace SPOTIPY_REDIRECT_URI=http://localhost:8080 python3 my_playlists.py

and it should only open the browser once.

joebonneau commented 3 years ago

Was able to confirm that it's not a Spotipy issue at least!

Is there an example of how to make a new authorization if one doesn't exist or it's expired? Thinking I would both need to check if .cache exists and then also validate the token information within.

stephanebruckert commented 3 years ago

an example of how to make a new authorization if one doesn't exist or it's expired

The my_playlists.py example does that, as by default spotipy looks for an existing token and refresh it if it is expired.

It only gets more complicated when looking to deal with multiple users, in which case the API/web example is probably interesting as it does more "manual" stuff https://github.com/plamere/spotipy/blob/master/examples/app.py

But let me know your use case and I might be able to help

joebonneau commented 3 years ago

I was able to fix the browser issue by changing my implementation of the authorization and how that object is being passed around the various commands. It was mostly an issue with how I set things up, but your insight about how things should work was super helpful and led me to the answer.

Thanks for taking the time to help me out - I really appreciate it!