diff --git a/beetsplug/mbcollection.py b/beetsplug/mbcollection.py index a590a76059..99c89b89ee 100644 --- a/beetsplug/mbcollection.py +++ b/beetsplug/mbcollection.py @@ -22,9 +22,15 @@ import musicbrainzngs import re +import json + +from requests_oauthlib import OAuth2Session +from requests_oauthlib.oauth2_session import (TokenExpiredError, + InsecureTransportError) SUBMISSION_CHUNK_SIZE = 200 UUID_REGEX = r'^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$' +ERRORS = (TokenExpiredError, InsecureTransportError) def mb_call(func, *args, **kwargs): @@ -112,3 +118,78 @@ def update_album_list(self, album_list): ) submit_albums(collection_id, album_ids) self._log.info(u'...MusicBrainz collection updated.') + + +class OAuth(BeetsPlugin): + def __init__(self): + super(OAuth, self).__init__() + self.config.add({ + 'client_id': 'qLYLK0LlrhifkgXq0N-J4w', # Needs to be generated + 'client_secret': 'XPiJdmSkh6uJDoWguo-J7g', + 'tokenfile': 'oauth2_token.json' + }) + self.redirect_uri = "urn:ietf:wg:oauth:2.0:oob" + self.scope = ['profile', 'collection'] + self.auth_url = "https://musicbrainz.org/oauth2/authorize" + self.token_uri = "https://musicbrainz.org/oauth2/token" + self.params = {'response_type': 'code'} + self.service = OAuth2Session(self.config['client_id'].as_str(), + redirect_uri=self.redirect_uri, + scope=self.scope) + self.config['client_id'].redact = True + self.config['client_secret'].redact = True + + self.register_listener('import_begin', self.setup) + + def authorize_url(self): + return self.service.authorization_url(self.auth_url) + + def get_token(self, code): + data = {'code': code, 'redirect_uri': self.redirect_uri, + 'grant_type': 'authorization_code', 'token_type': 'bearer'} + client_id = self.config['client.id'].as_str() + client_secret = self.config['client_secret'].as_str() + session = self.service.fetch_token(self.token_uri, + code=code, headers=data, + client_id=client_id, + client_secret=client_secret) + return session['access_token'], session['refresh_token'] + + def setup(self): + try: + with open(self.tokenfile()) as f: + tokendata = json.load(f) + except IOError: + token, refresh_token = self.authenticate() + else: + token = tokendata['token'] + refresh_token = tokendata['refresh_token'] + return token, refresh_token + + def authenticate(self): + try: + url = self.authorize_url() + except ERRORS as e: + self._log.debug(u'authentication error: {0}', e) + raise ui.UserError(u'Communication with Musicbrainz failed') + + ui.print_(u"To authenticate, visit:") + ui.print_(url) + + data = ui.input_(u"Enter the string displayed in your browser:") + + try: + token, refresh_token = self.get_token(data) + except ERRORS as e: + self._log.debug(u'authentication error: {0}', e) + raise ui.UserError(u'Token request failed') + + with open(self.tokenfile(), 'w') as f: + json.dump({'token': token, 'refresh_token': refresh_token}, f) + + return token, refresh_token + + def tokenfile(self): + from os import path + basedir = path.abspath(path.dirname(__file__)) + return "".join([basedir, '/token.json'])