Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 577181bb73 | |||
| f0649bf2b5 | |||
| b630801150 | |||
| 99d57fa1f9 | |||
| f7ccef89ef | |||
| d90af02d8b | |||
| 4429451454 | |||
| 313b413fc5 | |||
| 30d0e8641e | |||
| b18b8e0094 | |||
| 66ae5ee093 | |||
| 426dcf964f | |||
| ce58b6993f | |||
| f51e5c3c28 | |||
| e966b5bfa2 | |||
| 9e7e542585 |
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.0.8
|
||||||
|
Released 29th November 2017 (by Heruwar)
|
||||||
|
* Fixes a security issue where the password is sent as plaintext in the URL query parameters when methods from libsonic_extas are used.
|
||||||
|
Also adds Subsonic hex encoding when using legacy auth.
|
||||||
|
* Adds support for URL paths like https://hostname.com/subsonic as requested in Github issue #17 and also encountered in some of the reports (#14 and #5)
|
||||||
|
* Fixes an error when the password only contains digits, which simpleplugin converts to a Long, which later fails when libsonic tries to salt the password expecting a string.
|
||||||
|
|
||||||
|
## v2.0.7
|
||||||
|
Released 18 April 2017
|
||||||
|
* Added Search (by silascutler)
|
||||||
|
|
||||||
## v2.0.6
|
## v2.0.6
|
||||||
Released 14 January 2017
|
Released 14 January 2017
|
||||||
* Upgrade to simpleplugin 2.1.0
|
* Upgrade to simpleplugin 2.1.0
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.audio.subsonic" name="Subsonic" version="2.0.6" provider-name="BasilFX,grosbouff">
|
<addon id="plugin.audio.subsonic" name="Subsonic" version="2.0.8" provider-name="BasilFX,grosbouff,silascutler,Heruwar">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.14.0"/>
|
<import addon="xbmc.python" version="2.14.0"/>
|
||||||
<import addon="script.module.dateutil" version="2.4.2"/>
|
<import addon="script.module.dateutil" version="2.4.2"/>
|
||||||
|
|||||||
@ -60,10 +60,11 @@ class SubsonicClient(libsonic.Connection):
|
|||||||
# Pick a default port
|
# Pick a default port
|
||||||
host = "%s://%s" % (scheme, parts.hostname)
|
host = "%s://%s" % (scheme, parts.hostname)
|
||||||
port = parts.port or {"http": 80, "https": 443}[scheme]
|
port = parts.port or {"http": 80, "https": 443}[scheme]
|
||||||
|
path = parts.path.rstrip('/') + '/rest'
|
||||||
|
|
||||||
# Invoke original constructor
|
# Invoke original constructor
|
||||||
super(SubsonicClient, self).__init__(
|
super(SubsonicClient, self).__init__(
|
||||||
host, username, password, port=port, appName='Kodi', apiVersion=apiversion, insecure=insecure, legacyAuth=legacyauth)
|
host, username, password, port=port, serverPath=path, appName='Kodi', apiVersion=apiversion, insecure=insecure, legacyAuth=legacyauth)
|
||||||
|
|
||||||
def getIndexes(self, *args, **kwargs):
|
def getIndexes(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -292,7 +293,8 @@ class SubsonicClient(libsonic.Connection):
|
|||||||
parts = list(urlparse.urlparse(
|
parts = list(urlparse.urlparse(
|
||||||
args[0].get_full_url() + "?" + args[0].data))
|
args[0].get_full_url() + "?" + args[0].data))
|
||||||
parts[4] = dict(urlparse.parse_qsl(parts[4]))
|
parts[4] = dict(urlparse.parse_qsl(parts[4]))
|
||||||
parts[4].update({"u": self.username, "p": self.password})
|
if self._legacyAuth:
|
||||||
|
parts[4].update({"u": self.username, "p": 'enc:%s' % self._hexEnc(self._rawPass)})
|
||||||
parts[4] = urllib.urlencode(parts[4])
|
parts[4] = urllib.urlencode(parts[4])
|
||||||
|
|
||||||
return urlparse.urlunparse(parts)
|
return urlparse.urlunparse(parts)
|
||||||
|
|||||||
43
main.py
43
main.py
@ -51,8 +51,8 @@ def get_connection():
|
|||||||
try:
|
try:
|
||||||
connection = libsonic_extra.SubsonicClient(
|
connection = libsonic_extra.SubsonicClient(
|
||||||
Addon().get_setting('subsonic_url'),
|
Addon().get_setting('subsonic_url'),
|
||||||
Addon().get_setting('username'),
|
Addon().get_setting('username', convert=False),
|
||||||
Addon().get_setting('password'),
|
Addon().get_setting('password', convert=False),
|
||||||
Addon().get_setting('apiversion'),
|
Addon().get_setting('apiversion'),
|
||||||
Addon().get_setting('insecure') == 'true',
|
Addon().get_setting('insecure') == 'true',
|
||||||
Addon().get_setting('legacyauth') == 'true',
|
Addon().get_setting('legacyauth') == 'true',
|
||||||
@ -103,7 +103,12 @@ def root(params):
|
|||||||
'name': Addon().get_localized_string(30022),
|
'name': Addon().get_localized_string(30022),
|
||||||
'callback': 'list_playlists',
|
'callback': 'list_playlists',
|
||||||
'thumb': None
|
'thumb': None
|
||||||
}
|
},
|
||||||
|
'search': {
|
||||||
|
'name': Addon().get_localized_string(30039),
|
||||||
|
'callback': 'search',
|
||||||
|
'thumb': None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Iterate through categories
|
# Iterate through categories
|
||||||
@ -612,6 +617,38 @@ def list_playlists(params):
|
|||||||
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
#content = None #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
#content = None #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
)
|
)
|
||||||
|
@plugin.action()
|
||||||
|
#@plugin.cached(cachetime) #cache (in minutes)
|
||||||
|
def search(params):
|
||||||
|
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
d = dialog.input(Addon().get_localized_string(30039), type=xbmcgui.INPUT_ALPHANUM)
|
||||||
|
if not d:
|
||||||
|
d = " "
|
||||||
|
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
if connection is False:
|
||||||
|
return
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
# Get items
|
||||||
|
items = connection.search2(query=d)
|
||||||
|
# Iterate through items
|
||||||
|
for item in items.get('searchResult2').get('song'):
|
||||||
|
entry = get_entry_track( item, params)
|
||||||
|
listing.append(entry)
|
||||||
|
|
||||||
|
if len(listing) == 1:
|
||||||
|
plugin.log('One single Media Folder found; do return listing from browse_indexes()...')
|
||||||
|
return browse_indexes(params)
|
||||||
|
else:
|
||||||
|
return plugin.create_listing(listing)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@plugin.action()
|
@plugin.action()
|
||||||
def play_track(params):
|
def play_track(params):
|
||||||
|
|||||||
@ -152,3 +152,9 @@ msgstr ""
|
|||||||
msgctxt "#30038"
|
msgctxt "#30038"
|
||||||
msgid "Browse"
|
msgid "Browse"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30039"
|
||||||
|
msgid "Search"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -151,3 +151,10 @@ msgstr "Pistes au hasard"
|
|||||||
msgctxt "#30038"
|
msgctxt "#30038"
|
||||||
msgid "Browse"
|
msgid "Browse"
|
||||||
msgstr "Parcourir"
|
msgstr "Parcourir"
|
||||||
|
|
||||||
|
|
||||||
|
msgctxt "#30039"
|
||||||
|
msgid "Search"
|
||||||
|
msgstr "Rechercher"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -151,3 +151,7 @@ msgstr "Zufällig lieder"
|
|||||||
msgctxt "#30038"
|
msgctxt "#30038"
|
||||||
msgid "Browse"
|
msgid "Browse"
|
||||||
msgstr "Durchsuchen"
|
msgstr "Durchsuchen"
|
||||||
|
|
||||||
|
msgctxt "#30039"
|
||||||
|
msgid "Search"
|
||||||
|
msgstr "Suche"
|
||||||
|
|||||||
Reference in New Issue
Block a user