Spotipy OAuth2 访问令牌,无需将 URL 粘贴到控制台 [英] Spotipy OAuth2 Access Token without pasting URL to console

查看:60
本文介绍了Spotipy OAuth2 访问令牌,无需将 URL 粘贴到控制台的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在开发 Python Spotify 管理应用程序.我正在使用 PySide2 (Qt) 创建 GUI.Qt 有一个浏览器功能来访问网站.我正在使用此代码进行身份验证:

I am currently working on a Python Spotify Managing Application. I am using PySide2 (Qt) to create the GUI. Qt has a Browser feature to visit websites. I am using this code to authenticate:

import spotipy
from spotipy.oauth2 import SpotifyOAuth

auth = SpotifyOAuth(scope=scope, cache_path='user_cache',
                    client_id=client_id, client_secret=client_secret,
                    redirect_uri=redirect_uri)

sp = spotipy.Spotify(auth_manager=auth)

print(auth.get_auth_response())

当我运行此代码时,它会在 Chrome 中打开一个浏览器窗口,并要求我登录我的 Spotify 帐户.然后它将我重定向到我的 redirect_uri.我必须将此链接粘贴到控制台中.

When I run this code it opens a browser window in Chrome and asks me to login to my Spotify account. Then it redirects me to my redirect_uri. I have to paste this link into the console.

我的问题是我不想将 URI 粘贴到控制台中.我希望应用程序从 PySide2 (Qt) 浏览器中获取 url(我知道如何获取当前链接等)并自动将其粘贴到控制台.

My problem is that I don't want to paste the URI into the console. I want the App to get the url from inside the PySide2 (Qt) browser (I know how to get the current link and so on) and paste it to the console automatically.

我的问题是:

  1. Spotipy 是否具有无需将链接粘贴到控制台即可制作 OAuth2 的功能?我想绕过输入并将重定向链接直接传递给spipy.

  1. Does Spotipy have a feature to make an OAuth2 without pasting the link to the console? I want to bypass the input and pass the redirect link directly to spotipy.

是否可以选择手动打开的浏览器?

Is it possible to select the browser it opens manually?

我想在没有 Flask 的情况下执行此操作,只需 PySide2(PyQt、Qt 等)最佳情况:只需从查询中获取令牌并将其用于 api 请求

I'd like to do it without Flask, just PySide2 (PyQt, Qt, etc.) Best case: Just get the token from the query and use it for api requests

推荐答案

一个可能的解决方案是在 Qt WebEngine 中实现一个新的方案,用于重定向请求.

A possible solution is to implement a new scheme in Qt WebEngine where the request is redirected.

另一方面,Spotipy 使用请求使请求阻塞事件循环,导致 GUI 冻结,因此我修改了请求,使它们异步.

On the other hand Spotipy uses Requests making the requests block the eventloop causing the GUI to freeze so I have modified the requests making them asynchronous.

from functools import cached_property, partial
import threading
import types

import spotipy
from spotipy.oauth2 import SpotifyOAuth, SpotifyClientCredentials

from PySide2 import QtCore, QtWidgets, QtWebEngineCore, QtWebEngineWidgets


class ReplySpotify(QtCore.QObject):
    finished = QtCore.Signal()

    def __init__(self, func, args=(), kwargs=None, parent=None):
        super().__init__(parent)
        self._results = None
        self._is_finished = False
        self._error_str = ""
        threading.Thread(
            target=self._execute, args=(func, args, kwargs), daemon=True
        ).start()

    @property
    def results(self):
        return self._results

    @property
    def error_str(self):
        return self._error_str

    def is_finished(self):
        return self._is_finished

    def has_error(self):
        return bool(self._error_str)

    def _execute(self, func, args, kwargs):
        if kwargs is None:
            kwargs = {}
        try:
            self._results = func(*args, **kwargs)
        except Exception as e:
            self._error_str = str(e)
        self._is_finished = True
        self.finished.emit()


def convert_to_reply(func, *args, **kwargs):
    reply = ReplySpotify(func, args, kwargs)
    return reply


class ConvertToReply(type):
    def __call__(cls, *args, **kw):
        klass = super().__call__(*args, **kw)
        for key in dir(klass):
            value = getattr(klass, key)
            if isinstance(value, types.MethodType) and not key.startswith("_"):
                wrapped = partial(convert_to_reply, value)
                setattr(klass, key, wrapped)
        return klass


class QSpotify(spotipy.Spotify, metaclass=ConvertToReply):
    pass


class QOauthHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler):
    authenticated = QtCore.Signal(str, dict)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._html = ""

    @property
    def html(self):
        return self._html

    @html.setter
    def html(self, html):
        self._html = html

    def requestStarted(self, request):
        request_url = request.requestUrl()
        if request_url.host() == "oauth":
            query = QtCore.QUrlQuery(request_url.query())
            d = dict()
            for k, v in query.queryItems():
                d[k] = v
            self.authenticated.emit(request_url.path(), d)

            buf = QtCore.QBuffer(parent=self)
            request.destroyed.connect(buf.deleteLater)
            buf.open(QtCore.QIODevice.WriteOnly)
            buf.write(self.html.encode())
            buf.seek(0)
            buf.close()
            request.reply(b"text/html", buf)


class QSpotifyOAuth(QtCore.QObject, SpotifyOAuth):
    authenticationRequired = QtCore.Signal(QtCore.QUrl)
    codeChanged = QtCore.Signal()

    def __init__(
        self,
        client_id=None,
        client_secret=None,
        redirect_uri=None,
        state=None,
        scope=None,
        cache_path=None,
        username=None,
        proxies=None,
        show_dialog=False,
        requests_session=True,
        requests_timeout=None,
        parent=None,
    ):
        QtCore.QObject.__init__(self, parent=None)
        SpotifyOAuth.__init__(
            self,
            client_id,
            client_secret,
            redirect_uri,
            state,
            scope,
            cache_path,
            username,
            proxies,
            show_dialog,
            requests_session,
            requests_timeout,
        )
        self._code = ""

    def get_auth_response(self, state=None):
        url = QtCore.QUrl.fromUserInput(self.get_authorize_url())
        self.authenticationRequired.emit(url)
        loop = QtCore.QEventLoop()
        self.codeChanged.connect(loop.quit)
        loop.exec_()
        if state is None:
            state = self.state
        return state, self.code

    @property
    def code(self):
        return self._code

    def autenticate(self, values):
        self._code = values.get("code", "")
        self.codeChanged.emit()


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        lay = QtWidgets.QHBoxLayout(self)
        lay.addWidget(self.view, stretch=1)
        lay.addWidget(self.log, stretch=1)

        self.view.hide()

        client_id = ""
        client_secret = ""
        self.qauth = QSpotifyOAuth(
            cache_path="user_cache",
            client_id=client_id,
            client_secret=client_secret,
            redirect_uri="qt://oauth/spotify",
            scope="user-library-read",
        )
        self.qclient = QSpotify(auth_manager=self.qauth)
        self.qauth.authenticationRequired.connect(self.view.load)
        self.qauth.authenticationRequired.connect(self.view.show)

        reply = self.qclient.current_user_saved_tracks()
        reply.setParent(self)
        reply.finished.connect(partial(self.on_finished, reply))

    @cached_property
    def view(self):
        return QtWebEngineWidgets.QWebEngineView()

    @cached_property
    def log(self):
        return QtWidgets.QTextEdit(readOnly=True)

    def handle(self, path, values):
        self.qauth.autenticate(values)
        self.view.hide()

    def on_finished(self, reply):
        reply.deleteLater()
        for item in reply.results["items"]:
            track = item["track"]
            text = "<b>%s</b> %s" % (track["artists"][0]["name"], track["name"])
            self.log.append(text)

        if reply.results["items"]:
            new_reply = self.qclient.next(reply.results)
            new_reply.setParent(self)
            new_reply.finished.connect(partial(self.on_finished, new_reply))


def main():
    import sys

    scheme = QtWebEngineCore.QWebEngineUrlScheme(b"qt")
    QtWebEngineCore.QWebEngineUrlScheme.registerScheme(scheme)

    app = QtWidgets.QApplication(sys.argv)
    QtCore.QCoreApplication.setOrganizationName("qtspotify")
    QtCore.QCoreApplication.setApplicationName("Qt Spotify")

    handler = QOauthHandler()

    profile = QtWebEngineWidgets.QWebEngineProfile.defaultProfile()
    """profile.setPersistentCookiesPolicy(
        QtWebEngineWidgets.QWebEngineProfile.NoPersistentCookies
    )"""
    profile.installUrlSchemeHandler(b"qt", handler)

    w = Widget()
    w.resize(640, 480)
    w.show()

    handler.authenticated.connect(w.handle)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

注意:您必须添加网址qt://oauth/spotify";到仪表板中的项目设置:

Note: You must add the url "qt://oauth/spotify" to the project settings in the dashboard:

这篇关于Spotipy OAuth2 访问令牌,无需将 URL 粘贴到控制台的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆