mirror of
https://github.com/beetbox/beets.git
synced 2025-12-27 11:02:43 +01:00
Merge pull request #3832 from vincentDcmps/master
Automatically choose subsonic authentication algorithm based on server version
This commit is contained in:
commit
6680f692f6
3 changed files with 84 additions and 18 deletions
|
|
@ -29,26 +29,46 @@ import string
|
|||
|
||||
import requests
|
||||
|
||||
from binascii import hexlify
|
||||
from beets import config
|
||||
from beets.plugins import BeetsPlugin
|
||||
|
||||
__author__ = 'https://github.com/maffo999'
|
||||
AUTH_TOKEN_VERSION = (1, 12)
|
||||
|
||||
|
||||
class SubsonicUpdate(BeetsPlugin):
|
||||
def __init__(self):
|
||||
super(SubsonicUpdate, self).__init__()
|
||||
|
||||
# Set default configuration values
|
||||
config['subsonic'].add({
|
||||
'user': 'admin',
|
||||
'pass': 'admin',
|
||||
'url': 'http://localhost:4040',
|
||||
})
|
||||
|
||||
config['subsonic']['pass'].redact = True
|
||||
self._version = None
|
||||
self._auth = None
|
||||
self.register_listener('import', self.start_scan)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
if (self._version is None):
|
||||
self._version = self.__get_version()
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def auth(self):
|
||||
if (self._auth is None):
|
||||
if(self.version is not None):
|
||||
if self.version > AUTH_TOKEN_VERSION:
|
||||
self._auth = "token"
|
||||
else:
|
||||
self._auth = "password"
|
||||
self._log.info(
|
||||
u"using '{}' authentication method".format(self._auth))
|
||||
return self._auth
|
||||
|
||||
@staticmethod
|
||||
def __create_token():
|
||||
"""Create salt and token from given password.
|
||||
|
|
@ -67,10 +87,10 @@ class SubsonicUpdate(BeetsPlugin):
|
|||
return salt, token
|
||||
|
||||
@staticmethod
|
||||
def __format_url():
|
||||
"""Get the Subsonic URL to trigger a scan. Uses either the url
|
||||
config option or the deprecated host, port, and context_path config
|
||||
options together.
|
||||
def __format_url(endpoint):
|
||||
"""Get the Subsonic URL to trigger the given endpoint.
|
||||
Uses either the url config option or the deprecated host, port,
|
||||
and context_path config options together.
|
||||
|
||||
:return: Endpoint for updating Subsonic
|
||||
"""
|
||||
|
|
@ -88,22 +108,55 @@ class SubsonicUpdate(BeetsPlugin):
|
|||
context_path = ''
|
||||
url = "http://{}:{}{}".format(host, port, context_path)
|
||||
|
||||
return url + '/rest/startScan'
|
||||
|
||||
def start_scan(self):
|
||||
user = config['subsonic']['user'].as_str()
|
||||
url = self.__format_url()
|
||||
salt, token = self.__create_token()
|
||||
return url + '/rest/{}'.format(endpoint)
|
||||
|
||||
def __get_version(self):
|
||||
url = self.__format_url("ping.view")
|
||||
payload = {
|
||||
'u': user,
|
||||
't': token,
|
||||
's': salt,
|
||||
'v': '1.15.0', # Subsonic 6.1 and newer.
|
||||
'c': 'beets',
|
||||
'f': 'json'
|
||||
}
|
||||
try:
|
||||
response = requests.get(url, params=payload)
|
||||
if response.status_code == 200:
|
||||
json = response.json()
|
||||
version = json['subsonic-response']['version']
|
||||
self._log.info(
|
||||
u'subsonic version:{0} '.format(version))
|
||||
return tuple(int(s) for s in version.split('.'))
|
||||
else:
|
||||
self._log.error(u'Error: {0}', json)
|
||||
return None
|
||||
except Exception as error:
|
||||
self._log.error(u'Error: {0}'.format(error))
|
||||
return None
|
||||
|
||||
def start_scan(self):
|
||||
user = config['subsonic']['user'].as_str()
|
||||
url = self.__format_url("startScan.view")
|
||||
|
||||
if self.auth == 'token':
|
||||
salt, token = self.__create_token()
|
||||
payload = {
|
||||
'u': user,
|
||||
't': token,
|
||||
's': salt,
|
||||
'v': self.version, # Subsonic 6.1 and newer.
|
||||
'c': 'beets',
|
||||
'f': 'json'
|
||||
}
|
||||
elif self.auth == 'password':
|
||||
password = config['subsonic']['pass'].as_str()
|
||||
encpass = hexlify(password.encode()).decode()
|
||||
payload = {
|
||||
'u': user,
|
||||
'p': 'enc:{}'.format(encpass),
|
||||
'v': self.version,
|
||||
'c': 'beets',
|
||||
'f': 'json'
|
||||
}
|
||||
else:
|
||||
return
|
||||
try:
|
||||
response = requests.get(url, params=payload)
|
||||
json = response.json()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ New features:
|
|||
* :doc:`/plugins/chroma`: Update file metadata after generating fingerprints through the `submit` command.
|
||||
* :doc:`/plugins/lastgenre`: Added more heavy metal genres: https://en.wikipedia.org/wiki/Heavy_metal_genres to genres.txt and genres-tree.yaml
|
||||
* :doc:`/plugins/subsonicplaylist`: import playlist from a subsonic server.
|
||||
* :doc:`/plugins/subsonicupdate`: manage tocken and password authentifications method by checking server version.
|
||||
* A new :ref:`reflink` config option instructs the importer to create fast,
|
||||
copy-on-write file clones on filesystems that support them. Thanks to
|
||||
:user:`rubdos`.
|
||||
|
|
|
|||
|
|
@ -39,9 +39,21 @@ class SubsonicPluginTest(_common.TestCase, TestHelper):
|
|||
config["subsonic"]["user"] = "admin"
|
||||
config["subsonic"]["pass"] = "admin"
|
||||
config["subsonic"]["url"] = "http://localhost:4040"
|
||||
|
||||
responses.add(
|
||||
responses.GET,
|
||||
'http://localhost:4040/rest/ping.view',
|
||||
status=200,
|
||||
body=self.PING_BODY
|
||||
)
|
||||
self.subsonicupdate = subsonicupdate.SubsonicUpdate()
|
||||
|
||||
PING_BODY = '''
|
||||
{
|
||||
"subsonic-response": {
|
||||
"status": "failed",
|
||||
"version": "1.15.0"
|
||||
}
|
||||
}
|
||||
'''
|
||||
SUCCESS_BODY = '''
|
||||
{
|
||||
"subsonic-response": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue