diff --git a/data/images/back_disabled.jpg b/data/images/back_disabled.jpg index 1e73a546..11b1c688 100644 Binary files a/data/images/back_disabled.jpg and b/data/images/back_disabled.jpg differ diff --git a/data/images/back_enabled.jpg b/data/images/back_enabled.jpg index a6d764c7..91ef2fea 100644 Binary files a/data/images/back_enabled.jpg and b/data/images/back_enabled.jpg differ diff --git a/data/images/forward_disabled.jpg b/data/images/forward_disabled.jpg index 28a9dc53..d6cb703b 100644 Binary files a/data/images/forward_disabled.jpg and b/data/images/forward_disabled.jpg differ diff --git a/data/images/forward_enabled.jpg b/data/images/forward_enabled.jpg index 598c075f..adb1ee98 100644 Binary files a/data/images/forward_enabled.jpg and b/data/images/forward_enabled.jpg differ diff --git a/data/images/headphoneslogo.png b/data/images/headphoneslogo.png index 2b77ac93..f4b48b2c 100644 Binary files a/data/images/headphoneslogo.png and b/data/images/headphoneslogo.png differ diff --git a/data/images/ui-bg_flat_0_aaaaaa_40x100.png b/data/images/ui-bg_flat_0_aaaaaa_40x100.png index 3c07dd4e..be476d18 100755 Binary files a/data/images/ui-bg_flat_0_aaaaaa_40x100.png and b/data/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/data/images/ui-bg_flat_0_eeeeee_40x100.png b/data/images/ui-bg_flat_0_eeeeee_40x100.png index 7d5e8992..ded77b96 100755 Binary files a/data/images/ui-bg_flat_0_eeeeee_40x100.png and b/data/images/ui-bg_flat_0_eeeeee_40x100.png differ diff --git a/data/images/ui-bg_flat_55_c0402a_40x100.png b/data/images/ui-bg_flat_55_c0402a_40x100.png index 6e53344d..973b7c2f 100755 Binary files a/data/images/ui-bg_flat_55_c0402a_40x100.png and b/data/images/ui-bg_flat_55_c0402a_40x100.png differ diff --git a/data/images/ui-bg_flat_55_eeeeee_40x100.png b/data/images/ui-bg_flat_55_eeeeee_40x100.png index bb8ee043..ded77b96 100755 Binary files a/data/images/ui-bg_flat_55_eeeeee_40x100.png and b/data/images/ui-bg_flat_55_eeeeee_40x100.png differ diff --git a/data/images/ui-bg_glass_100_f8f8f8_1x400.png b/data/images/ui-bg_glass_100_f8f8f8_1x400.png index 86f59cbf..3c07fdc3 100755 Binary files a/data/images/ui-bg_glass_100_f8f8f8_1x400.png and b/data/images/ui-bg_glass_100_f8f8f8_1x400.png differ diff --git a/data/images/ui-bg_glass_35_dddddd_1x400.png b/data/images/ui-bg_glass_35_dddddd_1x400.png index 8847220b..047f4dc7 100755 Binary files a/data/images/ui-bg_glass_35_dddddd_1x400.png and b/data/images/ui-bg_glass_35_dddddd_1x400.png differ diff --git a/data/images/ui-bg_glass_60_eeeeee_1x400.png b/data/images/ui-bg_glass_60_eeeeee_1x400.png index 57760b4d..331975b9 100755 Binary files a/data/images/ui-bg_glass_60_eeeeee_1x400.png and b/data/images/ui-bg_glass_60_eeeeee_1x400.png differ diff --git a/data/images/ui-bg_inset-hard_75_999999_1x100.png b/data/images/ui-bg_inset-hard_75_999999_1x100.png index b078d868..a4eeaa7c 100755 Binary files a/data/images/ui-bg_inset-hard_75_999999_1x100.png and b/data/images/ui-bg_inset-hard_75_999999_1x100.png differ diff --git a/data/images/ui-bg_inset-soft_50_c9c9c9_1x100.png b/data/images/ui-bg_inset-soft_50_c9c9c9_1x100.png index 09e002b1..41638da9 100755 Binary files a/data/images/ui-bg_inset-soft_50_c9c9c9_1x100.png and b/data/images/ui-bg_inset-soft_50_c9c9c9_1x100.png differ diff --git a/data/images/ui-icons_3383bb_256x240.png b/data/images/ui-icons_3383bb_256x240.png index c2eb45be..a305f6a8 100755 Binary files a/data/images/ui-icons_3383bb_256x240.png and b/data/images/ui-icons_3383bb_256x240.png differ diff --git a/data/images/ui-icons_454545_256x240.png b/data/images/ui-icons_454545_256x240.png index b6db1acd..5fc455f2 100755 Binary files a/data/images/ui-icons_454545_256x240.png and b/data/images/ui-icons_454545_256x240.png differ diff --git a/data/images/ui-icons_70b2e1_256x240.png b/data/images/ui-icons_70b2e1_256x240.png index 66f4f000..7986bdf6 100755 Binary files a/data/images/ui-icons_70b2e1_256x240.png and b/data/images/ui-icons_70b2e1_256x240.png differ diff --git a/data/images/ui-icons_999999_256x240.png b/data/images/ui-icons_999999_256x240.png index da7e727b..92a2262c 100755 Binary files a/data/images/ui-icons_999999_256x240.png and b/data/images/ui-icons_999999_256x240.png differ diff --git a/data/images/ui-icons_fbc856_256x240.png b/data/images/ui-icons_fbc856_256x240.png index 69480efc..d2643ba3 100755 Binary files a/data/images/ui-icons_fbc856_256x240.png and b/data/images/ui-icons_fbc856_256x240.png differ diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 02f40718..3859d4a7 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -1372,6 +1372,23 @@ +
+
+ +
+
+
+ +
+
+ Comma separated list. Leave blank to send to all devices +
+
+ +
+
+
+ @@ -2168,6 +2185,27 @@ } }); + if ($("#join").is(":checked")) + { + $("#joinoptions").show(); + } + else + { + $("#joinoptions").hide(); + } + + + $("#join").click(function(){ + if ($("#join").is(":checked")) + { + $("#joinoptions").slideDown(); + } + else + { + $("#joinoptions").slideUp(); + } + }); + if ($("#twitter").is(":checked")) { $("#twitteroptions").show(); diff --git a/data/interfaces/default/images/MusicBrainz_Album_Icon.png b/data/interfaces/default/images/MusicBrainz_Album_Icon.png index 16abf29b..5237a429 100644 Binary files a/data/interfaces/default/images/MusicBrainz_Album_Icon.png and b/data/interfaces/default/images/MusicBrainz_Album_Icon.png differ diff --git a/data/interfaces/default/images/MusicBrainz_Artist_Icon.png b/data/interfaces/default/images/MusicBrainz_Artist_Icon.png index 82e3381d..019a7e94 100644 Binary files a/data/interfaces/default/images/MusicBrainz_Artist_Icon.png and b/data/interfaces/default/images/MusicBrainz_Artist_Icon.png differ diff --git a/data/interfaces/default/images/NoAlbumArt.png b/data/interfaces/default/images/NoAlbumArt.png index 146e1ccb..a52551c2 100644 Binary files a/data/interfaces/default/images/NoAlbumArt.png and b/data/interfaces/default/images/NoAlbumArt.png differ diff --git a/data/interfaces/default/images/button.png b/data/interfaces/default/images/button.png index a5fd7830..b5357ff7 100644 Binary files a/data/interfaces/default/images/button.png and b/data/interfaces/default/images/button.png differ diff --git a/data/interfaces/default/images/icon_add.png b/data/interfaces/default/images/icon_add.png index 9e5ccc47..5cf8d665 100644 Binary files a/data/interfaces/default/images/icon_add.png and b/data/interfaces/default/images/icon_add.png differ diff --git a/data/interfaces/default/images/icon_delete.png b/data/interfaces/default/images/icon_delete.png index e019d290..b879ff8c 100644 Binary files a/data/interfaces/default/images/icon_delete.png and b/data/interfaces/default/images/icon_delete.png differ diff --git a/data/interfaces/default/images/icon_extra.gif b/data/interfaces/default/images/icon_extra.gif index d447c632..f4278827 100644 Binary files a/data/interfaces/default/images/icon_extra.gif and b/data/interfaces/default/images/icon_extra.gif differ diff --git a/data/interfaces/default/images/icon_gear.png b/data/interfaces/default/images/icon_gear.png index 6513b30b..a63b9a13 100644 Binary files a/data/interfaces/default/images/icon_gear.png and b/data/interfaces/default/images/icon_gear.png differ diff --git a/data/interfaces/default/images/icon_getextra.png b/data/interfaces/default/images/icon_getextra.png index 19fe357b..923d02d6 100644 Binary files a/data/interfaces/default/images/icon_getextra.png and b/data/interfaces/default/images/icon_getextra.png differ diff --git a/data/interfaces/default/images/icon_history.png b/data/interfaces/default/images/icon_history.png index 363289c2..2590faf0 100644 Binary files a/data/interfaces/default/images/icon_history.png and b/data/interfaces/default/images/icon_history.png differ diff --git a/data/interfaces/default/images/icon_like.png b/data/interfaces/default/images/icon_like.png index e3f638d1..91a3a3bf 100644 Binary files a/data/interfaces/default/images/icon_like.png and b/data/interfaces/default/images/icon_like.png differ diff --git a/data/interfaces/default/images/icon_logs.png b/data/interfaces/default/images/icon_logs.png index 4943de1a..9fc1dfac 100644 Binary files a/data/interfaces/default/images/icon_logs.png and b/data/interfaces/default/images/icon_logs.png differ diff --git a/data/interfaces/default/images/icon_manage.png b/data/interfaces/default/images/icon_manage.png index 25c1a7e8..d677a241 100644 Binary files a/data/interfaces/default/images/icon_manage.png and b/data/interfaces/default/images/icon_manage.png differ diff --git a/data/interfaces/default/images/icon_pause.png b/data/interfaces/default/images/icon_pause.png index 0c99a2b6..de884766 100644 Binary files a/data/interfaces/default/images/icon_pause.png and b/data/interfaces/default/images/icon_pause.png differ diff --git a/data/interfaces/default/images/icon_refresh.png b/data/interfaces/default/images/icon_refresh.png index 13953728..24cf6009 100644 Binary files a/data/interfaces/default/images/icon_refresh.png and b/data/interfaces/default/images/icon_refresh.png differ diff --git a/data/interfaces/default/images/icon_removeextra.png b/data/interfaces/default/images/icon_removeextra.png index 65acd0db..1f9f0f97 100644 Binary files a/data/interfaces/default/images/icon_removeextra.png and b/data/interfaces/default/images/icon_removeextra.png differ diff --git a/data/interfaces/default/images/icon_search.gif b/data/interfaces/default/images/icon_search.gif index a64534cc..867a6d43 100644 Binary files a/data/interfaces/default/images/icon_search.gif and b/data/interfaces/default/images/icon_search.gif differ diff --git a/data/interfaces/default/images/icon_search.png b/data/interfaces/default/images/icon_search.png index 9845cc95..7a6cd079 100644 Binary files a/data/interfaces/default/images/icon_search.png and b/data/interfaces/default/images/icon_search.png differ diff --git a/data/interfaces/default/images/icon_sprite_black.png b/data/interfaces/default/images/icon_sprite_black.png index fe079a59..2723382a 100644 Binary files a/data/interfaces/default/images/icon_sprite_black.png and b/data/interfaces/default/images/icon_sprite_black.png differ diff --git a/data/interfaces/default/images/icon_sprite_white.png b/data/interfaces/default/images/icon_sprite_white.png index 42f8f992..701b147c 100644 Binary files a/data/interfaces/default/images/icon_sprite_white.png and b/data/interfaces/default/images/icon_sprite_white.png differ diff --git a/data/interfaces/default/images/icon_upcoming.png b/data/interfaces/default/images/icon_upcoming.png index 2a7acbe6..6e421eb5 100644 Binary files a/data/interfaces/default/images/icon_upcoming.png and b/data/interfaces/default/images/icon_upcoming.png differ diff --git a/data/interfaces/default/images/icon_wanted.png b/data/interfaces/default/images/icon_wanted.png index a06fd6e1..d1a08aae 100644 Binary files a/data/interfaces/default/images/icon_wanted.png and b/data/interfaces/default/images/icon_wanted.png differ diff --git a/data/interfaces/default/images/loader_black.gif b/data/interfaces/default/images/loader_black.gif index e2a116c7..17c51b14 100644 Binary files a/data/interfaces/default/images/loader_black.gif and b/data/interfaces/default/images/loader_black.gif differ diff --git a/data/interfaces/default/images/loader_blue.gif b/data/interfaces/default/images/loader_blue.gif index 956c1cd0..40d87f68 100644 Binary files a/data/interfaces/default/images/loader_blue.gif and b/data/interfaces/default/images/loader_blue.gif differ diff --git a/data/interfaces/default/images/loader_refresh_black.gif b/data/interfaces/default/images/loader_refresh_black.gif index 0ea146c0..551a6655 100644 Binary files a/data/interfaces/default/images/loader_refresh_black.gif and b/data/interfaces/default/images/loader_refresh_black.gif differ diff --git a/data/interfaces/default/images/loader_refresh_blue.gif b/data/interfaces/default/images/loader_refresh_blue.gif index 57a76afb..722066f6 100644 Binary files a/data/interfaces/default/images/loader_refresh_blue.gif and b/data/interfaces/default/images/loader_refresh_blue.gif differ diff --git a/data/interfaces/default/images/no-cover-art.png b/data/interfaces/default/images/no-cover-art.png index 40334da4..b654dc71 100644 Binary files a/data/interfaces/default/images/no-cover-art.png and b/data/interfaces/default/images/no-cover-art.png differ diff --git a/data/interfaces/default/images/no-cover-artist.png b/data/interfaces/default/images/no-cover-artist.png index 40fe7903..ae97e049 100644 Binary files a/data/interfaces/default/images/no-cover-artist.png and b/data/interfaces/default/images/no-cover-artist.png differ diff --git a/data/interfaces/default/images/songkick.png b/data/interfaces/default/images/songkick.png index c18e440d..c5261bd8 100644 Binary files a/data/interfaces/default/images/songkick.png and b/data/interfaces/default/images/songkick.png differ diff --git a/data/interfaces/default/images/songkick_ribon.png b/data/interfaces/default/images/songkick_ribon.png index 57ad4ccb..ff760f37 100644 Binary files a/data/interfaces/default/images/songkick_ribon.png and b/data/interfaces/default/images/songkick_ribon.png differ diff --git a/data/interfaces/default/images/sort_asc.png b/data/interfaces/default/images/sort_asc.png index e61cf34c..bb2bc57c 100644 Binary files a/data/interfaces/default/images/sort_asc.png and b/data/interfaces/default/images/sort_asc.png differ diff --git a/data/interfaces/default/images/sort_both.png b/data/interfaces/default/images/sort_both.png index 7261de19..2753be8f 100644 Binary files a/data/interfaces/default/images/sort_both.png and b/data/interfaces/default/images/sort_both.png differ diff --git a/data/interfaces/default/images/sort_desc.png b/data/interfaces/default/images/sort_desc.png index 980a7ecb..c48f18ac 100644 Binary files a/data/interfaces/default/images/sort_desc.png and b/data/interfaces/default/images/sort_desc.png differ diff --git a/data/interfaces/default/images/toTop.gif b/data/interfaces/default/images/toTop.gif index cde291a4..110534a2 100644 Binary files a/data/interfaces/default/images/toTop.gif and b/data/interfaces/default/images/toTop.gif differ diff --git a/data/interfaces/default/images/trashcan.png b/data/interfaces/default/images/trashcan.png index de10cbda..595328d8 100644 Binary files a/data/interfaces/default/images/trashcan.png and b/data/interfaces/default/images/trashcan.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_close.png b/data/interfaces/default/js/fancybox/fancy_close.png index 07035307..ca41f1bd 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_close.png and b/data/interfaces/default/js/fancybox/fancy_close.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_loading.png b/data/interfaces/default/js/fancybox/fancy_loading.png index 25030179..f208508e 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_loading.png and b/data/interfaces/default/js/fancybox/fancy_loading.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_nav_left.png b/data/interfaces/default/js/fancybox/fancy_nav_left.png index ebaa6a4f..017d3b53 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_nav_left.png and b/data/interfaces/default/js/fancybox/fancy_nav_left.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_nav_right.png b/data/interfaces/default/js/fancybox/fancy_nav_right.png index 873294e9..03f64f41 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_nav_right.png and b/data/interfaces/default/js/fancybox/fancy_nav_right.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_shadow_e.png b/data/interfaces/default/js/fancybox/fancy_shadow_e.png index 2eda0893..fff72232 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_shadow_e.png and b/data/interfaces/default/js/fancybox/fancy_shadow_e.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_shadow_n.png b/data/interfaces/default/js/fancybox/fancy_shadow_n.png index 69aa10e2..76ca9b7a 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_shadow_n.png and b/data/interfaces/default/js/fancybox/fancy_shadow_n.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_shadow_s.png b/data/interfaces/default/js/fancybox/fancy_shadow_s.png index d8858bfb..d3b3c5b2 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_shadow_s.png and b/data/interfaces/default/js/fancybox/fancy_shadow_s.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_shadow_se.png b/data/interfaces/default/js/fancybox/fancy_shadow_se.png index 541e3ffd..0cce09c1 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_shadow_se.png and b/data/interfaces/default/js/fancybox/fancy_shadow_se.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_shadow_w.png b/data/interfaces/default/js/fancybox/fancy_shadow_w.png index 8a4e4a88..b9bde126 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_shadow_w.png and b/data/interfaces/default/js/fancybox/fancy_shadow_w.png differ diff --git a/data/interfaces/default/js/fancybox/fancy_title_over.png b/data/interfaces/default/js/fancybox/fancy_title_over.png index d9f458f4..74b9149e 100644 Binary files a/data/interfaces/default/js/fancybox/fancy_title_over.png and b/data/interfaces/default/js/fancybox/fancy_title_over.png differ diff --git a/data/interfaces/default/js/fancybox/fancybox-x.png b/data/interfaces/default/js/fancybox/fancybox-x.png index c2130f86..eb850fd3 100644 Binary files a/data/interfaces/default/js/fancybox/fancybox-x.png and b/data/interfaces/default/js/fancybox/fancybox-x.png differ diff --git a/data/interfaces/default/js/fancybox/fancybox-y.png b/data/interfaces/default/js/fancybox/fancybox-y.png index 7ef399b9..18458526 100644 Binary files a/data/interfaces/default/js/fancybox/fancybox-y.png and b/data/interfaces/default/js/fancybox/fancybox-y.png differ diff --git a/data/interfaces/default/js/fancybox/fancybox.png b/data/interfaces/default/js/fancybox/fancybox.png index 65e14f68..1a0dda99 100644 Binary files a/data/interfaces/default/js/fancybox/fancybox.png and b/data/interfaces/default/js/fancybox/fancybox.png differ diff --git a/headphones/__init__.py b/headphones/__init__.py index 8247e1c2..2bd3bf9c 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -621,8 +621,8 @@ def dbcheck(): c.execute('UPDATE snatched SET TorrentHash = FolderName WHERE Status LIKE "Seed_%"') # One off script to set CleanName to lower case - clean_name_mixed = c.execute('SELECT CleanName FROM have ORDER BY Date Desc').fetchone()[0] - if clean_name_mixed != clean_name_mixed.lower(): + clean_name_mixed = c.execute('SELECT CleanName FROM have ORDER BY Date Desc').fetchone() + if clean_name_mixed and clean_name_mixed[0] != clean_name_mixed[0].lower(): logger.info("Updating track clean name, this could take some time...") c.execute('UPDATE tracks SET CleanName = LOWER(CleanName) WHERE LOWER(CleanName) != CleanName') c.execute('UPDATE alltracks SET CleanName = LOWER(CleanName) WHERE LOWER(CleanName) != CleanName') diff --git a/headphones/config.py b/headphones/config.py index 98b23785..83c73415 100644 --- a/headphones/config.py +++ b/headphones/config.py @@ -145,6 +145,10 @@ _CONFIG_DEFINITIONS = { 'IGNORED_FILES': (list, 'Advanced', []), # path 'INCLUDE_EXTRAS': (int, 'General', 0), 'INTERFACE': (str, 'General', 'default'), + 'JOIN_APIKEY': (str, 'Join', ''), + 'JOIN_DEVICEID': (str, 'Join', ''), + 'JOIN_ENABLED': (int, 'Join', 0), + 'JOIN_ONSNATCH': (int, 'Join', 0), 'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'KAT': (int, 'Kat', 0), 'KAT_PROXY_URL': (str, 'Kat', ''), diff --git a/headphones/notifiers.py b/headphones/notifiers.py index 54f4f4b5..26cdb234 100644 --- a/headphones/notifiers.py +++ b/headphones/notifiers.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with Headphones. If not, see . -from urllib import urlencode +from urllib import urlencode, quote_plus import urllib import subprocess import json @@ -148,7 +148,9 @@ class PROWL(object): http_handler.request("POST", "/publicapi/add", - headers={'Content-type': "application/x-www-form-urlencoded"}, + headers={ + 'Content-type': + "application/x-www-form-urlencoded"}, body=urlencode(data)) response = http_handler.getresponse() request_status = response.status @@ -203,28 +205,34 @@ class XBMC(object): url = host + '/xbmcCmds/xbmcHttp/?' + url_command if self.password: - return request.request_content(url, auth=(self.username, self.password)) + return request.request_content(url, + auth=(self.username, self.password)) else: return request.request_content(url) def _sendjson(self, host, method, params={}): - data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}] + data = [ + {'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}] headers = {'Content-Type': 'application/json'} url = host + '/jsonrpc' if self.password: - response = request.request_json(url, method="post", data=json.dumps(data), - headers=headers, auth=(self.username, self.password)) + response = request.request_json( + url, method="post", + data=json.dumps(data), + headers=headers, auth=( + self.username, self.password)) else: - response = request.request_json(url, method="post", data=json.dumps(data), + response = request.request_json(url, method="post", + data=json.dumps(data), headers=headers) if response: return response[0]['result'] def update(self): - # From what I read you can't update the music library on a per directory or per path basis - # so need to update the whole thing + # From what I read you can't update the music library on a per + # directory or per path basis so need to update the whole thing hosts = [x.strip() for x in self.hosts.split(',')] @@ -247,18 +255,23 @@ class XBMC(object): logger.info('Sending notification command to XMBC @ ' + host) try: version = self._sendjson(host, 'Application.GetProperties', - {'properties': ['version']})['version']['major'] + {'properties': ['version']})[ + 'version']['major'] if version < 12: # Eden - notification = header + "," + message + "," + time + "," + albumartpath + notification = header + "," + message + "," + time + \ + "," + albumartpath notifycommand = {'command': 'ExecBuiltIn', - 'parameter': 'Notification(' + notification + ')'} + 'parameter': 'Notification(' + + notification + ')'} request = self._sendhttp(host, notifycommand) else: # Frodo - params = {'title': header, 'message': message, 'displaytime': int(time), + params = {'title': header, 'message': message, + 'displaytime': int(time), 'image': albumartpath} - request = self._sendjson(host, 'GUI.ShowNotification', params) + request = self._sendjson(host, 'GUI.ShowNotification', + params) if not request: raise Exception @@ -323,22 +336,28 @@ class Plex(object): url = host + '/xbmcCmds/xbmcHttp/?' + command if self.password: - response = request.request_response(url, auth=(self.username, self.password)) + response = request.request_response(url, auth=( + self.username, self.password)) else: response = request.request_response(url) return response def _sendjson(self, host, method, params={}): - data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}] + data = [ + {'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}] headers = {'Content-Type': 'application/json'} url = host + '/jsonrpc' if self.password: - response = request.request_json(url, method="post", data=json.dumps(data), - headers=headers, auth=(self.username, self.password)) + response = request.request_json( + url, method="post", + data=json.dumps(data), + headers=headers, auth=( + self.username, self.password)) else: - response = request.request_json(url, method="post", data=json.dumps(data), + response = request.request_json(url, method="post", + data=json.dumps(data), headers=headers) if response: @@ -346,13 +365,14 @@ class Plex(object): def update(self): - # From what I read you can't update the music library on a per directory or per path basis - # so need to update the whole thing + # From what I read you can't update the music library on a per + # directory or per path basis so need to update the whole thing hosts = [x.strip() for x in self.server_hosts.split(',')] for host in hosts: - logger.info('Sending library update command to Plex Media Server@ ' + host) + logger.info( + 'Sending library update command to Plex Media Server@ ' + host) url = "%s/library/sections" % host if self.token: params = {'X-Plex-Token': self.token} @@ -369,7 +389,8 @@ class Plex(object): for s in sections: if s.getAttribute('type') == "artist": - url = "%s/library/sections/%s/refresh" % (host, s.getAttribute('key')) + url = "%s/library/sections/%s/refresh" % ( + host, s.getAttribute('key')) request.request_response(url, params=params) def notify(self, artist, album, albumartpath): @@ -381,27 +402,35 @@ class Plex(object): time = "3000" # in ms for host in hosts: - logger.info('Sending notification command to Plex client @ ' + host) + logger.info( + 'Sending notification command to Plex client @ ' + host) try: version = self._sendjson(host, 'Application.GetProperties', - {'properties': ['version']})['version']['major'] + {'properties': ['version']})[ + 'version']['major'] if version < 12: # Eden - notification = header + "," + message + "," + time + "," + albumartpath + notification = header + "," + message + "," + time + \ + "," + albumartpath notifycommand = {'command': 'ExecBuiltIn', - 'parameter': 'Notification(' + notification + ')'} + 'parameter': 'Notification(' + + notification + ')'} request = self._sendhttp(host, notifycommand) else: # Frodo - params = {'title': header, 'message': message, 'displaytime': int(time), + params = {'title': header, 'message': message, + 'displaytime': int(time), 'image': albumartpath} - request = self._sendjson(host, 'GUI.ShowNotification', params) + request = self._sendjson(host, 'GUI.ShowNotification', + params) if not request: raise Exception except Exception: - logger.error('Error sending notification request to Plex client @ ' + host) + logger.error( + 'Error sending notification request to Plex client @ ' + + host) class NMA(object): @@ -419,7 +448,8 @@ class NMA(object): message = "Headphones has snatched: " + snatched else: event = artist + ' - ' + album + ' complete!' - message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']' + message = "Headphones has downloaded and postprocessed: " + \ + artist + ' [' + album + ']' logger.debug(u"NMA event: " + event) logger.debug(u"NMA message: " + message) @@ -433,7 +463,8 @@ class NMA(object): if len(keys) > 1: batch = True - response = p.push(title, event, message, priority=nma_priority, batch_mode=batch) + response = p.push(title, event, message, priority=nma_priority, + batch_mode=batch) if not response[api][u'code'] == u'200': logger.error(u'Could not send notification to NotifyMyAndroid') @@ -461,9 +492,11 @@ class PUSHBULLET(object): data['device_iden'] = self.deviceid headers = {'Content-type': "application/json", - 'Authorization': 'Bearer ' + headphones.CONFIG.PUSHBULLET_APIKEY} + 'Authorization': 'Bearer ' + + headphones.CONFIG.PUSHBULLET_APIKEY} - response = request.request_json(url, method="post", headers=headers, data=json.dumps(data)) + response = request.request_json(url, method="post", headers=headers, + data=json.dumps(data)) if response: logger.info(u"PushBullet notifications sent.") @@ -492,7 +525,9 @@ class PUSHALOT(object): http_handler.request("POST", "/api/sendmessage", - headers={'Content-type': "application/x-www-form-urlencoded"}, + headers={ + 'Content-type': + "application/x-www-form-urlencoded"}, body=urlencode(data)) response = http_handler.getresponse() request_status = response.status @@ -512,6 +547,49 @@ class PUSHALOT(object): return False +class JOIN(object): + def __init__(self): + + self.enabled = headphones.CONFIG.JOIN_ENABLED + self.apikey = headphones.CONFIG.JOIN_APIKEY + self.deviceid = headphones.CONFIG.JOIN_DEVICEID + self.url = 'https://joinjoaomgcd.appspot.com/_ah/' \ + 'api/messaging/v1/sendPush?apikey={apikey}' \ + '&title={title}&text={text}' \ + '&icon={icon}' + + def notify(self, message, event): + if not headphones.CONFIG.JOIN_ENABLED or \ + not headphones.CONFIG.JOIN_APIKEY: + return + + icon = "https://cdn.rawgit.com/Headphones/" \ + "headphones/develop/data/images/headphoneslogo.png" + + if not self.deviceid: + self.deviceid = "group.all" + l = [x.strip() for x in self.deviceid.split(',')] + if len(l) > 1: + self.url += '&deviceIds={deviceid}' + else: + self.url += '&deviceId={deviceid}' + + response = urllib2.urlopen(self.url.format(apikey=self.apikey, + title=quote_plus(event), + text=quote_plus( + message.encode( + "utf-8")), + icon=icon, + deviceid=self.deviceid)) + + if response: + logger.info(u"Join notifications sent.") + return True + else: + logger.error(u"Join notification failed.") + return False + + class Synoindex(object): def __init__(self, util_loc='/usr/syno/bin/synoindex'): self.util_loc = util_loc @@ -524,7 +602,8 @@ class Synoindex(object): if not self.util_exists(): logger.warn( - "Error sending notification: synoindex utility not found at %s" % self.util_loc) + "Error sending notification: synoindex utility " + "not found at %s" % self.util_loc) return if os.path.isfile(path): @@ -533,16 +612,19 @@ class Synoindex(object): cmd_arg = '-A' else: logger.warn( - "Error sending notification: Path passed to synoindex was not a file or folder.") + "Error sending notification: Path passed to synoindex " + "was not a file or folder.") return cmd = [self.util_loc, cmd_arg, path] logger.info("Calling synoindex command: %s" % str(cmd)) try: - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, cwd=headphones.PROG_DIR) out, error = p.communicate() - # synoindex never returns any codes other than '0', highly irritating + # synoindex never returns any codes other than '0', + # highly irritating except OSError, e: logger.warn("Error sending notification: %s" % str(e)) @@ -580,7 +662,8 @@ class PUSHOVER(object): headers = {'Content-type': "application/x-www-form-urlencoded"} - response = request.request_response(url, method="POST", headers=headers, data=data) + response = request.request_response(url, method="POST", + headers=headers, data=data) if response: logger.info(u"Pushover notifications sent.") @@ -614,20 +697,25 @@ class TwitterNotifier(object): def notify_snatch(self, title): if headphones.CONFIG.TWITTER_ONSNATCH: self._notifyTwitter( - common.notifyStrings[common.NOTIFY_SNATCH] + ': ' + title + ' at ' + helpers.now()) + common.notifyStrings[ + common.NOTIFY_SNATCH] + ': ' + title + ' at ' + + helpers.now()) def notify_download(self, title): if headphones.CONFIG.TWITTER_ENABLED: self._notifyTwitter(common.notifyStrings[ - common.NOTIFY_DOWNLOAD] + ': ' + title + ' at ' + helpers.now()) + common.NOTIFY_DOWNLOAD] + ': ' + + title + ' at ' + helpers.now()) def test_notify(self): return self._notifyTwitter( - "This is a test notification from Headphones at " + helpers.now(), force=True) + "This is a test notification from Headphones at " + helpers.now(), + force=True) def _get_authorization(self): - oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret) + oauth_consumer = oauth.Consumer(key=self.consumer_key, + secret=self.consumer_secret) oauth_client = oauth.Client(oauth_consumer) logger.info('Requesting temp token from Twitter') @@ -635,32 +723,42 @@ class TwitterNotifier(object): resp, content = oauth_client.request(self.REQUEST_TOKEN_URL, 'GET') if resp['status'] != '200': - logger.info('Invalid respond from Twitter requesting temp token: %s' % resp['status']) + logger.info( + 'Invalid respond from Twitter requesting temp token: %s' % + resp['status']) else: request_token = dict(parse_qsl(content)) headphones.CONFIG.TWITTER_USERNAME = request_token['oauth_token'] - headphones.CONFIG.TWITTER_PASSWORD = request_token['oauth_token_secret'] + headphones.CONFIG.TWITTER_PASSWORD = request_token[ + 'oauth_token_secret'] - return self.AUTHORIZATION_URL + "?oauth_token=" + request_token['oauth_token'] + return self.AUTHORIZATION_URL + "?oauth_token=" + request_token[ + 'oauth_token'] def _get_credentials(self, key): request_token = {} request_token['oauth_token'] = headphones.CONFIG.TWITTER_USERNAME - request_token['oauth_token_secret'] = headphones.CONFIG.TWITTER_PASSWORD + request_token[ + 'oauth_token_secret'] = headphones.CONFIG.TWITTER_PASSWORD request_token['oauth_callback_confirmed'] = 'true' - token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) + token = oauth.Token(request_token['oauth_token'], + request_token['oauth_token_secret']) token.set_verifier(key) - logger.info('Generating and signing request for an access token using key ' + key) + logger.info( + 'Generating and signing request for an access token using key ' + + key) - oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret) + oauth_consumer = oauth.Consumer(key=self.consumer_key, + secret=self.consumer_secret) logger.info('oauth_consumer: ' + str(oauth_consumer)) oauth_client = oauth.Client(oauth_consumer, token) logger.info('oauth_client: ' + str(oauth_client)) - resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', + resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, + method='POST', body='oauth_verifier=%s' % key) logger.info('resp, content: ' + str(resp) + ',' + str(content)) @@ -669,14 +767,18 @@ class TwitterNotifier(object): logger.info('resp[status] = ' + str(resp['status'])) if resp['status'] != '200': - logger.info('The request for a token with did not succeed: ' + str(resp['status']), + logger.info('The request for a token with did not succeed: ' + str( + resp['status']), logger.ERROR) return False else: - logger.info('Your Twitter Access Token key: %s' % access_token['oauth_token']) - logger.info('Access Token secret: %s' % access_token['oauth_token_secret']) + logger.info('Your Twitter Access Token key: %s' % access_token[ + 'oauth_token']) + logger.info( + 'Access Token secret: %s' % access_token['oauth_token_secret']) headphones.CONFIG.TWITTER_USERNAME = access_token['oauth_token'] - headphones.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret'] + headphones.CONFIG.TWITTER_PASSWORD = access_token[ + 'oauth_token_secret'] return True def _send_tweet(self, message=None): @@ -688,7 +790,8 @@ class TwitterNotifier(object): logger.info(u"Sending tweet: " + message) - api = twitter.Api(username, password, access_token_key, access_token_secret) + api = twitter.Api(username, password, access_token_key, + access_token_secret) try: api.PostUpdate(message) @@ -741,7 +844,8 @@ class OSX_NOTIFY(object): ) NSUserNotification = self.objc.lookUpClass('NSUserNotification') - NSUserNotificationCenter = self.objc.lookUpClass('NSUserNotificationCenter') + NSUserNotificationCenter = self.objc.lookUpClass( + 'NSUserNotificationCenter') NSAutoreleasePool = self.objc.lookUpClass('NSAutoreleasePool') if not NSUserNotification or not NSUserNotificationCenter: @@ -756,14 +860,17 @@ class OSX_NOTIFY(object): if text: notification.setInformativeText_(text) if sound: - notification.setSoundName_("NSUserNotificationDefaultSoundName") + notification.setSoundName_( + "NSUserNotificationDefaultSoundName") if image: - source_img = self.AppKit.NSImage.alloc().initByReferencingFile_(image) + source_img = self.AppKit.NSImage.alloc().\ + initByReferencingFile_(image) notification.setContentImage_(source_img) # notification.set_identityImage_(source_img) notification.setHasActionButton_(False) - notification_center = NSUserNotificationCenter.defaultUserNotificationCenter() + notification_center = NSUserNotificationCenter.\ + defaultUserNotificationCenter() notification_center.deliverNotification_(notification) del pool @@ -784,7 +891,8 @@ class BOXCAR(object): def notify(self, title, message, rgid=None): try: if rgid: - message += '

MusicBrainz' % rgid + message += '

MusicBrainz' % rgid data = urllib.urlencode({ 'user_credentials': headphones.CONFIG.BOXCAR_TOKEN, @@ -820,8 +928,9 @@ class SubSonicNotifier(object): self.host = self.host + "/" # Invoke request - request.request_response(self.host + "musicFolderSettings.view?scanNow", - auth=(self.username, self.password)) + request.request_response( + self.host + "musicFolderSettings.view?scanNow", + auth=(self.username, self.password)) class Email(object): @@ -829,13 +938,15 @@ class Email(object): message = MIMEText(message, 'plain', "utf-8") message['Subject'] = subject - message['From'] = email.utils.formataddr(('Headphones', headphones.CONFIG.EMAIL_FROM)) + message['From'] = email.utils.formataddr( + ('Headphones', headphones.CONFIG.EMAIL_FROM)) message['To'] = headphones.CONFIG.EMAIL_TO try: if headphones.CONFIG.EMAIL_SSL: - mailserver = smtplib.SMTP_SSL(headphones.CONFIG.EMAIL_SMTP_SERVER, - headphones.CONFIG.EMAIL_SMTP_PORT) + mailserver = smtplib.SMTP_SSL( + headphones.CONFIG.EMAIL_SMTP_SERVER, + headphones.CONFIG.EMAIL_SMTP_PORT) else: mailserver = smtplib.SMTP(headphones.CONFIG.EMAIL_SMTP_SERVER, headphones.CONFIG.EMAIL_SMTP_PORT) @@ -849,7 +960,8 @@ class Email(object): mailserver.login(headphones.CONFIG.EMAIL_SMTP_USER, headphones.CONFIG.EMAIL_SMTP_PASSWORD) - mailserver.sendmail(headphones.CONFIG.EMAIL_FROM, headphones.CONFIG.EMAIL_TO, + mailserver.sendmail(headphones.CONFIG.EMAIL_FROM, + headphones.CONFIG.EMAIL_TO, message.as_string()) mailserver.quit() return True @@ -860,7 +972,6 @@ class Email(object): class TELEGRAM(object): - def notify(self, message, status): if not headphones.CONFIG.TELEGRAM_ENABLED: return @@ -878,14 +989,18 @@ class TELEGRAM(object): # Send message to user using Telegram's Bot API try: - response = requests.post(TELEGRAM_API % (token, "sendMessage"), data=payload) + response = requests.post(TELEGRAM_API % (token, "sendMessage"), + data=payload) except Exception, e: logger.info(u'Telegram notify failed: ' + str(e)) # Error logging sent_successfuly = True if not response.status_code == 200: - logger.info(u'Could not send notification to TelegramBot (token=%s). Response: [%s]', (token, response.text)) + logger.info( + u'Could not send notification to TelegramBot ' + u'(token=%s). Response: [%s]', + (token, response.text)) sent_successfuly = False logger.info(u"Telegram notifications sent.") @@ -893,7 +1008,6 @@ class TELEGRAM(object): class SLACK(object): - def notify(self, message, status): if not headphones.CONFIG.SLACK_ENABLED: return @@ -904,7 +1018,8 @@ class SLACK(object): channel = headphones.CONFIG.SLACK_CHANNEL emoji = headphones.CONFIG.SLACK_EMOJI - payload = {'channel': channel, 'text': status + ': ' + message, 'icon_emoji': emoji} + payload = {'channel': channel, 'text': status + ': ' + message, + 'icon_emoji': emoji} try: response = requests.post(SLACK_URL, json=payload) @@ -913,7 +1028,9 @@ class SLACK(object): sent_successfuly = True if not response.status_code == 200: - logger.info(u'Could not send notification to Slack. Response: [%s]', (response.text)) + logger.info( + u'Could not send notification to Slack. Response: [%s]', + (response.text)) sent_successfuly = False logger.info(u"Slack notifications sent.") diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 38e4bba5..ed3c136b 100755 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -574,6 +574,11 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, pushbullet = notifiers.PUSHBULLET() pushbullet.notify(pushmessage, statusmessage) + if headphones.CONFIG.JOIN_ENABLED: + logger.info(u"Join request") + join = notifiers.JOIN() + join.notify(pushmessage, statusmessage) + if headphones.CONFIG.TELEGRAM_ENABLED: logger.info(u"Telegram request") telegram = notifiers.TELEGRAM() diff --git a/headphones/searcher.py b/headphones/searcher.py index b0652f7f..e3c3f591 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -32,6 +32,7 @@ import re from pygazelle import api as gazelleapi from pygazelle import encoding as gazelleencoding from pygazelle import format as gazelleformat +from pygazelle import release_type as gazellerelease_type import headphones from headphones.common import USER_AGENT from headphones import logger, db, helpers, classes, sab, nzbget, request @@ -1076,6 +1077,10 @@ def send_to_downloader(data, bestqual, album): logger.info(u"Sending PushBullet notification") pushbullet = notifiers.PUSHBULLET() pushbullet.notify(name, "Download started") + if headphones.CONFIG.JOIN_ENABLED and headphones.CONFIG.JOIN_ONSNATCH: + logger.info(u"Sending Join notification") + join = notifiers.JOIN() + join.notify(name, "Download started") if headphones.CONFIG.SLACK_ENABLED and headphones.CONFIG.SLACK_ONSNATCH: logger.info(u"Sending Slack notification") slack = notifiers.SLACK() @@ -1564,16 +1569,45 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None, if apolloobj and apolloobj.logged_in(): logger.info(u"Searching %s..." % provider) all_torrents = [] + + # Specify release types to filter by + if album['Type'] == 'Album': + album_type = [gazellerelease_type.ALBUM] + if album['Type'] == 'Soundtrack': + album_type = [gazellerelease_type.SOUNDTRACK] + if album['Type'] == 'EP': + album_type = [gazellerelease_type.EP] + # No musicbrainz match for this type + #if album['Type'] == 'Anthology': + # album_type = [gazellerelease_type.ANTHOLOGY] + if album['Type'] == 'Compilation': + album_type = [gazellerelease_type.COMPILATION] + if album['Type'] == 'DJ-mix': + album_type = [gazellerelease_type.DJ_MIX] + if album['Type'] == 'Single': + album_type = [gazellerelease_type.SINGLE] + if album['Type'] == 'Live': + album_type = [gazellerelease_type.LIVE_ALBUM] + if album['Type'] == 'Remix': + album_type = [gazellerelease_type.REMIX] + if album['Type'] == 'Bootleg': + album_type = [gazellerelease_type.BOOTLEG] + if album['Type'] == 'Interview': + album_type = [gazellerelease_type.INTERVIEW] + if album['Type'] == 'Mixtape/Street': + album_type = [gazellerelease_type.MIXTAPE] + for search_format in search_formats: if usersearchterm: all_torrents.extend( apolloobj.search_torrents(searchstr=usersearchterm, format=search_format, - encoding=bitrate_string)['results']) + encoding=bitrate_string, releasetype=album_type)['results']) else: all_torrents.extend(apolloobj.search_torrents(artistname=semi_clean_artist_term, groupname=semi_clean_album_term, format=search_format, - encoding=bitrate_string)['results']) + encoding=bitrate_string, + releasetype=album_type)['results']) # filter on format, size, and num seeders logger.info(u"Filtering torrents by format, maximum size, and minimum seeders...") diff --git a/headphones/webserve.py b/headphones/webserve.py index ee353fe2..6d95712a 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -537,7 +537,7 @@ class WebInterface(object): myDB = db.DBConnection() upcoming = myDB.select( "SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate ASC") - wanted = myDB.select("SELECT * from albums WHERE Status='Wanted'") + wanted = myDB.select("SELECT * from albums WHERE Status='Wanted' order by ReleaseDate ASC") return serve_template(templatename="upcoming.html", title="Upcoming", upcoming=upcoming, wanted=wanted) @@ -1412,7 +1412,11 @@ class WebInterface(object): "slack_url": headphones.CONFIG.SLACK_URL, "slack_channel": headphones.CONFIG.SLACK_CHANNEL, "slack_emoji": headphones.CONFIG.SLACK_EMOJI, - "slack_onsnatch": checked(headphones.CONFIG.SLACK_ONSNATCH) + "slack_onsnatch": checked(headphones.CONFIG.SLACK_ONSNATCH), + "join_enabled": checked(headphones.CONFIG.JOIN_ENABLED), + "join_onsnatch": checked(headphones.CONFIG.JOIN_ONSNATCH), + "join_apikey": headphones.CONFIG.JOIN_APIKEY, + "join_deviceid": headphones.CONFIG.JOIN_DEVICEID } for k, v in config.iteritems(): @@ -1480,7 +1484,8 @@ class WebInterface(object): "osx_notify_enabled", "osx_notify_onsnatch", "boxcar_enabled", "boxcar_onsnatch", "songkick_enabled", "songkick_filter_enabled", "mpc_enabled", "email_enabled", "email_ssl", "email_tls", "email_onsnatch", - "customauth", "idtag", "deluge_paused" + "customauth", "idtag", "deluge_paused", + "join_enabled", "join_onsnatch" ] for checked_config in checked_configs: if checked_config not in kwargs: @@ -1749,6 +1754,12 @@ class WebInterface(object): telegram = notifiers.TELEGRAM() telegram.notify("it works!", "lazers pew pew") + @cherrypy.expose + def testJoin(self): + logger.info("Testing Join notifications") + join = notifiers.JOIN() + join.notify("it works!", "Test message") + class Artwork(object): @cherrypy.expose diff --git a/lib/cherrypy/scaffold/static/made_with_cherrypy_small.png b/lib/cherrypy/scaffold/static/made_with_cherrypy_small.png index c3aafeed..724f9d72 100644 Binary files a/lib/cherrypy/scaffold/static/made_with_cherrypy_small.png and b/lib/cherrypy/scaffold/static/made_with_cherrypy_small.png differ