spotipy-dev / spotipy

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

Can't get spotipy.Spotify() for multiple users #612

Closed alessiocelentano closed 3 years ago

alessiocelentano commented 3 years ago

Hi! I asked this questions on StackOverflow but I didn't get an answer: I'm creating a Telegram Bot to get Spotify stats with Spotipy. This script will be used by multiple people, but after a user give authorizations he gets my stats. I have no idea why this happens.

def get_auth(message):
    username = str(message.from_user.id)
    scope = "user-top-read"
    auth = SpotifyOAuth(
        redirect_uri="http://localhost:8080",
        username=username,
        scope=scope
    )
    return auth

No problem with the code above, Bot redirects the user to the link (auth.get_authorize_url()) and get authorization successfully.

def get_client(message):
    auth = get_auth(message)
    spotify = spotipy.client.Spotify(
        auth_manager=auth,
        client_credentials_manager=SpotifyClientCredentials()
    )
    try:
        auth.get_auth_response(open_browser=False)
        print(spotify.me())  # Just for cache
        return spotify
    except Exception:
        return None

I think the problem is here, the variable returned is always referred to my Spotify account (and if I withdraw my authorization, it raises an error, although the user logged in with his account). This function has to return the right variable, can anyone help me? You can read the full code on this link

Peter-Schorn commented 3 years ago

There are several problems with your code:

spotify = spotipy.client.Spotify(
        auth_manager=auth,
        client_credentials_manager=SpotifyClientCredentials()
    )

You are assigning two different authorization managers to SpotifySpotifyOAuth and SpotifyClientCredentials—which doesn't make sense. You only need one authorization manager. If you need to access user data, then you should use SpotifyOAuth or SpotifyPKCE. Don't ever use the oauth_manager or client_credentials_manager parameters of Spotify.init. @stephanebruckert we should deprecate these parameters and emit a warning when anyone tries to use them; they just create confusion.

auth.get_auth_response(open_browser=False)

This method only performs the first step of the authorization process; you need to pass the authorization code that it returns into SpotifyOAuth.get_access_token to complete the authorization process. If you don't retain the authorization code that get_auth_response returns, then calling it is completely pointless. The way that spotipy works is that it will automatically start the authorization process for you when you make a call to an endpoint, such as Spotify.me, if you are not authorized yet. It is this line that is authorizing your app, not the one above it.

The user that the instance of Spotify returned by get_client is authorized for is which ever one logs in to their Spotify account in the browser, unless the authorization information is already stored in a cache file. Is this cache file the same for everyone that uses your app? If so then it probably contains your authorization information, and this is the reason that everyone is seeing the stats for your account.

If, when you open the authorization URL, you are immediately redirected without being given a change to log in to your account, then it is because you are already logged in to your Spotify account on your browser. If you want to log in with a different account, then set show_dialog to True in SpotifyOAuth.init.

and if I withdraw my authorization, it raises an error, although the user logged in with his account

What do you mean by this? Please be very specific about what you are doing in order to "withdraw my authorization".

Also, is everyone who uses your script supposed to get information about your Spotify account, instead of their Spotify account?

You need to provide more information about what you are trying to do. I'm very lost.

Peter-Schorn commented 3 years ago

Also, you should only create one instance of Spotify for each user. It looks like you're creating a new instance each time top_artists is called and then re-authorizing the user. That doesn't make any sense. You only need to authorize the user once.

alessiocelentano commented 3 years ago

Hi. Thank you for answering me and sorry for the inaccuracy of the question.

You are assigning two different authorization managers to Spotify

Okay, so the line should be spotify = spotipy.client.Spotify(auth_manager=auth).

The way that spotipy works is that it will automatically start the authorization process for you when you make a call to an endpoint, such as Spotify.me

So I can keep only a Spotify.me() and it start automatically the authorization process, right?

Is this cache file the same for everyone that uses your app?

No, I pass an username in SpotifyOAuth(), so at the first authorization it creates .cache-<username>. But if I have the username of the user who wants access to his data, how I get the right .cache? How do get_cached_token() work in this case, with multiple caches?

What do you mean by this? Please be very specific about what you are doing in order to "withdraw my authorization".

Don't mind about that. I mean that if I delete the authorization on Spotify (Home > App > Remove access) and other users try to access, it gives an error, because it probably use my data stored in .cache-<myusername>.

Also, is everyone who uses your script supposed to get information about your Spotify account, instead of their Spotify account?

No. They should get data about their account.

You need to provide more information about what you are trying to do. I'm very lost.

Simply this bot, once it gets authorization of the user and cached in .cache-<username>, should give some user data (not of mine). There are many sites that do that, I wanted to implement that on a Telegram Bot.

Also, you should only create one instance of Spotify for each user

Perfect, my question is how I should get data of a specific .cache, instead of creating an instance every time. Is there a function for do that or I should do it manually?

Peter-Schorn commented 3 years ago

If you provide the correct username when creating an instance of SpotifyOAuth, then SpotifyOAuth.get_cached_token should only access the cache file containing that username. You should print str(message.from_user.id) to the console to check that it is what you expect. You can also print SpotifyOAuth.username and SpotifyOAuth.cache_path to see where the authorization manager is looking for the cached authorization information.

You don't need to make a call to Spotify.me() if your app is not actually using the data that it returns. Just remove it entirely and spotipy will go through the authorization process if necessary when you make a call to current_user_top_tracks or current_user_top_artists or any of the other endpoints that your app ends up using.

alessiocelentano commented 3 years ago

Okay, one last thing: Once I get token = SpotifyOAuth.get_cached.token()["access_token"] how should i do to get the Spotify() instance? I did spotipy.client.Spotify(auth=token) but it doesn't seem work.

stephanebruckert commented 3 years ago

You can get some inspiration from this example Flask app https://github.com/plamere/spotipy/blob/master/examples/app.py - it handles multiple users

alessiocelentano commented 3 years ago

I followed your advices and read the example, but it still doesn't work and I don't know why. You can take a look at the code here. I've been trying for days now and even with your help it doesn't seem to work, what am I doing wrong? Brief explanation: If the user is not logged, bot sends two buttons:

Then the user can get his data, here there aren't problems. But an instance of my account is still created

Peter-Schorn commented 3 years ago

Did you add the print statements I suggested? What did you find? It would be extremely helpful if you actually did that.

alessiocelentano commented 3 years ago

The print are correct. I noticed an error that can be helpful:

[Errno 98] Address already in use
Traceback (most recent call last):
  File "/home/cele/.local/lib/python3.8/site-packages/pyrogram/dispatcher.py", line 210, in handler_worker
    await self.loop.run_in_executor(
  File "/usr/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "main.py", line 46, in authorize
    spotify = new_client(message)
  File "/home/cele/dev/spotistats/src/client.py", line 28, in new_client
    print(spotify.me())
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 1158, in me
    return self._get("me/")
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 291, in _get
    return self._internal_call("GET", url, payload, kwargs)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 221, in _internal_call
    headers = self._auth_headers()
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 212, in _auth_headers
    token = self.auth_manager.get_access_token(as_dict=False)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 484, in get_access_token
    "code": code or self.get_auth_response(),
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 439, in get_auth_response
    return self._get_auth_response_local_server(redirect_port)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 405, in _get_auth_response_local_server
    server = start_local_http_server(redirect_port)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 1227, in start_local_http_server
    server = HTTPServer(("127.0.0.1", port), handler)
  File "/usr/lib/python3.8/socketserver.py", line 452, in __init__
    self.server_bind()
  File "/usr/lib/python3.8/http/server.py", line 138, in server_bind
    socketserver.TCPServer.server_bind(self)
  File "/usr/lib/python3.8/socketserver.py", line 466, in server_bind
    self.socket.bind(self.server_address)
OSError: [Errno 98] Address already in use
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 60682)
Traceback (most recent call last):
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 1186, in do_GET
    state, auth_code = SpotifyOAuth.parse_auth_response_url(self.path)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 372, in parse_auth_response_url
    raise SpotifyOauthError("Received error from auth server: "
spotipy.oauth2.SpotifyOauthError: Received error from auth server: access_denied

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.8/socketserver.py", line 316, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/usr/lib/python3.8/socketserver.py", line 347, in process_request
    self.finish_request(request, client_address)
  File "/usr/lib/python3.8/socketserver.py", line 360, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3.8/socketserver.py", line 720, in __init__
    self.handle()
  File "/usr/lib/python3.8/http/server.py", line 427, in handle
    self.handle_one_request()
  File "/usr/lib/python3.8/http/server.py", line 415, in handle_one_request
    method()
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 1190, in do_GET
    self.server.state = err.state
AttributeError: 'SpotifyOauthError' object has no attribute 'state'
----------------------------------------
Server listening on localhost has not been accessed
Traceback (most recent call last):
  File "/home/cele/.local/lib/python3.8/site-packages/pyrogram/dispatcher.py", line 210, in handler_worker
    await self.loop.run_in_executor(
  File "/usr/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "main.py", line 46, in authorize
    spotify = new_client(message)
  File "/home/cele/dev/spotistats/src/client.py", line 28, in new_client
    print(spotify.me())
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 1158, in me
    return self._get("me/")
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 291, in _get
    return self._internal_call("GET", url, payload, kwargs)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 221, in _internal_call
    headers = self._auth_headers()
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/client.py", line 212, in _auth_headers
    token = self.auth_manager.get_access_token(as_dict=False)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 484, in get_access_token
    "code": code or self.get_auth_response(),
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 439, in get_auth_response
    return self._get_auth_response_local_server(redirect_port)
  File "/home/cele/.local/lib/python3.8/site-packages/spotipy/oauth2.py", line 417, in _get_auth_response_local_server
    raise SpotifyOauthError("Server listening on localhost has not been accessed")
spotipy.oauth2.SpotifyOauthError: Server listening on localhost has not been accessed

Can it be an error with the Redirect URI? SPOTIPY_REDIRECT_URI enviroment variable and redirect URI on Spotify for Developers are the same (http://localhost:8080), but after the user give the authorization, often the site give "ERR_CONNECTION_REFUSED"

Peter-Schorn commented 3 years ago

The reason for these errors is probably because the automatic authorization process does not work in the environment that your app is running in. What environment is it running in? Is it running on a server?

Can it be an error with the Redirect URI?

Yes, I think the issue is related to the redirect URI. It shouldn't be "http://localhost:8080" if the app is running on an external server. After you open the url from SpotifyOAuth.get_authorize_url() and the user logs in to their Spotify account and taps agree, Spotify will redirect to the redirect URI that you specified. The redirect URI should somehow redirect back to your bot. For example, a callback function in your code should be executed after Spotify redirects back to your app. Do you know how to do this? If you don't, than I can't help you because I've never used the telegram bot API before.

alessiocelentano commented 3 years ago

What environment is it running in? Is it running on a server?

It's running on my laptop with Arch Linux, so there shouldn't be problems, but the page with "Close" button is never loaded.

It shouldn't be "http://localhost:8080" if the app is running on an external server.

It is running on my PC and i read in the code that with localhost (and a port specified) I don't need to copy and paste the URL the browser is redirect to on the terminal.

a callback function in your code should be executed after Spotify redirects back to your app

What do you mean? What needs to be executed? I have a button that create a new spotipy.Spotify() instance for the user and store his data in a cache if the bot have the authorization, is not the same thing?

Peter-Schorn commented 3 years ago

"OSError: [Errno 98] Address already in use" means that there is another process already using port 8080 (which is what appears in your redirect URI). You need to find that process and kill it. Alternatively, restart your computer. See https://stackoverflow.com/a/39557155/12394554

"spotipy.oauth2.SpotifyOauthError: Received error from auth server: access_denied" means that, after you opened the authorization URL in the browser and the user logged in to their Spotify account, the user tapped "cancel" instead of "agree", meaning that they denied your app access to their Spotify account.

It's hard for me to provide more help because I don't really understand how telegram bots work. You need to contact someone in the telegram community and ask them if it is possible to implement the OAuth authorization process, which is a widely recognized standard not specific to the Spotify web API.

alessiocelentano commented 3 years ago

Okay, thanks for the help, I'll ask to someone in Telegram community to fix this problem (if possible)

Peter-Schorn commented 3 years ago

Specifically, you need to ask if you can setup a callback URL that redirects back to your application.