diff --git a/CHANGES.md b/CHANGES.md index 8715e8a5..25f512b6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,6 +30,7 @@ * Allow episode status "Skipped" to be changed to "Downloaded" * Allow found "Skipped" episode files to be set "Unknown" quality * Add CPU throttling preset "Disabled" to config/General/Advanced Settings +* Add Emby notifier ### 0.11.5 (2016-02-01 19:40:00 UTC) diff --git a/gui/slick/css/fonts/sgicons.eot b/gui/slick/css/fonts/sgicons.eot index 7e046292..dd64fd40 100644 Binary files a/gui/slick/css/fonts/sgicons.eot and b/gui/slick/css/fonts/sgicons.eot differ diff --git a/gui/slick/css/fonts/sgicons.svg b/gui/slick/css/fonts/sgicons.svg index c3a6f563..36968466 100644 --- a/gui/slick/css/fonts/sgicons.svg +++ b/gui/slick/css/fonts/sgicons.svg @@ -42,4 +42,5 @@ + \ No newline at end of file diff --git a/gui/slick/css/fonts/sgicons.ttf b/gui/slick/css/fonts/sgicons.ttf index 595bb99e..c6ce308a 100644 Binary files a/gui/slick/css/fonts/sgicons.ttf and b/gui/slick/css/fonts/sgicons.ttf differ diff --git a/gui/slick/css/fonts/sgicons.woff b/gui/slick/css/fonts/sgicons.woff index cf791cac..d8b09ab3 100644 Binary files a/gui/slick/css/fonts/sgicons.woff and b/gui/slick/css/fonts/sgicons.woff differ diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index 81af0fdf..b6cae860 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -126,14 +126,14 @@ fonts font-style:italic } -/* Droid Sans */ +/* SickGear Icons */ @font-face{ font-family:'sgicons'; - src:url('fonts/sgicons.eot'); - src:url('fonts/sgicons.eot?#iefix') format('embedded-opentype'), - url('fonts/sgicons.woff') format('woff'), - url('fonts/sgicons.ttf') format('truetype'), - url('fonts/sgicons.svg#sgicons') format('svg'); + src:url('fonts/sgicons.eot?v1'); + src:url('fonts/sgicons.eot?v1#iefix') format('embedded-opentype'), + url('fonts/sgicons.woff?v1') format('woff'), + url('fonts/sgicons.ttf?v1') format('truetype'), + url('fonts/sgicons.svg?v1#sgicons') format('svg'); font-weight:normal; font-style:normal } @@ -499,6 +499,10 @@ inc_top.tmpl content:"\e621" } +.sgicon-emby:before { + content: "\e900" +} + /* ======================================================================= inc_bottom.tmpl ========================================================================== */ diff --git a/gui/slick/images/notifiers/emby.png b/gui/slick/images/notifiers/emby.png new file mode 100644 index 00000000..3b9ce269 Binary files /dev/null and b/gui/slick/images/notifiers/emby.png differ diff --git a/gui/slick/interfaces/default/config_notifications.tmpl b/gui/slick/interfaces/default/config_notifications.tmpl index 1de8e626..661f4429 100644 --- a/gui/slick/interfaces/default/config_notifications.tmpl +++ b/gui/slick/interfaces/default/config_notifications.tmpl @@ -401,6 +401,47 @@ +
+
+ +

Emby

+

Emby is a home media server built using other popular open source technologies.

+
+
+
+ +
+
+
+ +
+
+ +
+
Click below to test.
+ + +
+
+
diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl index 02d2c25f..582deea3 100644 --- a/gui/slick/interfaces/default/inc_top.tmpl +++ b/gui/slick/interfaces/default/inc_top.tmpl @@ -134,6 +134,9 @@ #if $sickbeard.USE_KODI and $sickbeard.KODI_HOST != ''
  • Update Kodi
  • #end if +#if $sickbeard.USE_EMBY and $sickbeard.EMBY_HOST != '' and $sickbeard.EMBY_APIKEY != '' +
  • Update Emby
  • +#end if #if $sickbeard.USE_FAILED_DOWNLOADS
  • Failed Downloads
  • #end if diff --git a/gui/slick/js/configNotifications.js b/gui/slick/js/configNotifications.js index d616e3c3..38870882 100644 --- a/gui/slick/js/configNotifications.js +++ b/gui/slick/js/configNotifications.js @@ -123,6 +123,33 @@ }); }); + $('#testEMBY').click(function () { + var emby_host = $('#emby_host').val(); + var emby_apikey = $('#emby_apikey').val(); + if (!emby_host || !emby_apikey) { + $('#testEMBY-result').html('Please fill out the necessary fields above.'); + if (!emby_host) { + $('#emby_host').addClass('warning'); + } else { + $('#emby_host').removeClass('warning'); + } + if (!emby_apikey) { + $('#emby_apikey').addClass('warning'); + } else { + $('#emby_apikey').removeClass('warning'); + } + return; + } + $('#emby_host, #emby_apikey').removeClass('warning'); + $(this).prop('disabled', true); + $('#testEMBY-result').html(loading); + $.get(sbRoot + '/home/testEMBY', {'host': emby_host, 'emby_apikey': emby_apikey}) + .done(function (data) { + $('#testEMBY-result').html(data); + $('#testEMBY').prop('disabled', false); + }); + }); + $('#testBoxcar2').click(function () { var boxcar2_accesstoken = $.trim($('#boxcar2_accesstoken').val()); var boxcar2_sound = $('#boxcar2_sound').val() || 'default'; diff --git a/gui/slick/js/inc_top.js b/gui/slick/js/inc_top.js index 2af9b287..200eae99 100644 --- a/gui/slick/js/inc_top.js +++ b/gui/slick/js/inc_top.js @@ -31,6 +31,7 @@ function initActions() { $('#SubMenu a[href$="/home/updateXBMC/"]').addClass('btn').html('Update XBMC'); $('#SubMenu a:contains("Update show in Kodi")').addClass('btn').html('Update show in Kodi'); $('#SubMenu a[href$="/home/updateKODI/"]').addClass('btn').html('Update Kodi'); + $('#SubMenu a[href$="/home/updateEMBY/"]').addClass('btn').html('Update Emby'); } $(document).ready(function(){ diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index cb67f6ef..183a7c21 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -287,6 +287,10 @@ PLEX_HOST = None PLEX_USERNAME = None PLEX_PASSWORD = None +USE_EMBY = False +EMBY_HOST = None +EMBY_APIKEY = None + USE_GROWL = False GROWL_NOTIFY_ONSNATCH = False GROWL_NOTIFY_ONDOWNLOAD = False @@ -492,8 +496,8 @@ def initialize(consoleLogging=True): XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, BACKLOG_FREQUENCY, \ USE_KODI, KODI_ALWAYS_ON, KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, KODI_UPDATE_LIBRARY, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, \ USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_USE_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \ - USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, \ - PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, MAX_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \ + USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, \ + USE_EMBY, EMBY_HOST, EMBY_APIKEY, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, MAX_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \ showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, HOME_SEARCH_FOCUS, SORT_ARTICLE, showList, loadingShowList, UPDATE_SHOWS_ON_START, SHOW_UPDATE_HOUR, ALLOW_INCOMPLETE_SHOWDATA, \ NEWZNAB_DATA, INDEXER_DEFAULT, INDEXER_TIMEOUT, USENET_RETENTION, TORRENT_DIR, \ QUALITY_DEFAULT, FLATTEN_FOLDERS_DEFAULT, SUBTITLES_DEFAULT, STATUS_DEFAULT, WANTED_BEGIN_DEFAULT, WANTED_LATEST_DEFAULT, RECENTSEARCH_STARTUP, \ @@ -538,6 +542,7 @@ def initialize(consoleLogging=True): CheckSection(CFG, 'XBMC') CheckSection(CFG, 'Kodi') CheckSection(CFG, 'PLEX') + CheckSection(CFG, 'Emby') CheckSection(CFG, 'Growl') CheckSection(CFG, 'Prowl') CheckSection(CFG, 'Twitter') @@ -815,6 +820,10 @@ def initialize(consoleLogging=True): PLEX_USERNAME = check_setting_str(CFG, 'Plex', 'plex_username', '') PLEX_PASSWORD = check_setting_str(CFG, 'Plex', 'plex_password', '') + USE_EMBY = bool(check_setting_int(CFG, 'Emby', 'use_emby', 0)) + EMBY_HOST = check_setting_str(CFG, 'Emby', 'emby_host', '') + EMBY_APIKEY = check_setting_str(CFG, 'Emby', 'emby_apikey', '') + USE_GROWL = bool(check_setting_int(CFG, 'Growl', 'use_growl', 0)) GROWL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Growl', 'growl_notify_onsnatch', 0)) GROWL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Growl', 'growl_notify_ondownload', 0)) @@ -1628,6 +1637,11 @@ def save_config(): new_config['Plex']['plex_username'] = PLEX_USERNAME new_config['Plex']['plex_password'] = helpers.encrypt(PLEX_PASSWORD, ENCRYPTION_VERSION) + new_config['Emby'] = {} + new_config['Emby']['use_emby'] = int(USE_EMBY) + new_config['Emby']['emby_host'] = EMBY_HOST + new_config['Emby']['emby_apikey'] = EMBY_APIKEY + new_config['Growl'] = {} new_config['Growl']['use_growl'] = int(USE_GROWL) new_config['Growl']['growl_notify_onsnatch'] = int(GROWL_NOTIFY_ONSNATCH) diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index ee7dbf8c..653a4a7d 100644 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -21,6 +21,7 @@ import sickbeard import xbmc import kodi import plex +import emby import nmj import nmjv2 import synoindex @@ -47,6 +48,7 @@ from sickbeard.common import * xbmc_notifier = xbmc.XBMCNotifier() kodi_notifier = kodi.KODINotifier() plex_notifier = plex.PLEXNotifier() +emby_notifier = emby.EmbyNotifier() nmj_notifier = nmj.NMJNotifier() nmjv2_notifier = nmjv2.NMJv2Notifier() synoindex_notifier = synoindex.synoIndexNotifier() diff --git a/sickbeard/notifiers/emby.py b/sickbeard/notifiers/emby.py new file mode 100644 index 00000000..c9ace10e --- /dev/null +++ b/sickbeard/notifiers/emby.py @@ -0,0 +1,99 @@ +# Author: Nic Wolfe +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickGear. +# +# SickGear is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickGear. If not, see . + +import urllib +import urllib2 + +import sickbeard +from sickbeard import logger +from sickbeard.exceptions import ex + +try: + import json +except ImportError: + import simplejson as json + +class EmbyNotifier: + + def _notify_emby(self, message, host=None, emby_apikey=None): + """Handles notifying Emby Server host via HTTP API + + Returns: True if the request succeeded, False otherwise + + """ + + # fill in omitted parameters + if not host: + host = sickbeard.EMBY_HOST + if not emby_apikey: + emby_apikey = sickbeard.EMBY_APIKEY + + url = 'http://%s/emby/Notifications/Admin' % (host) + values = {'Name': 'SickGear', 'Description': message, 'ImageUrl': 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/apple-touch-icon-precomposed.png'} + data = json.dumps(values) + + try: + req = urllib2.Request(url, data) + req.add_header('X-MediaBrowser-Token', emby_apikey) + req.add_header('Content-Type', 'application/json') + + response = urllib2.urlopen(req) + response.close() + + except (urllib2.URLError, IOError) as e: + logger.log(u'EMBY: Warning: Couldn\'t contact Emby Server at ' + url + ' ' + ex(e), logger.WARNING) + return False + + logger.log(u'EMBY: Notification successful.', logger.MESSAGE) + return True + + def test_notify(self, host, emby_apikey): + return self._notify_emby('This is a test notification from SickGear', host, emby_apikey) + + def update_library(self): + """Handles updating the Emby Server host via HTTP API + + Returns: True if the request succeeded, False otherwise + + """ + + if sickbeard.USE_EMBY: + + if not sickbeard.EMBY_HOST: + logger.log(u'EMBY: No host specified, check your settings', logger.DEBUG) + return False + + url = 'http://%s/emby/Library/Series/Updated' % (sickbeard.EMBY_HOST) + values = {} + data = urllib.urlencode(values) + + try: + req = urllib2.Request(url, data) + req.add_header('X-MediaBrowser-Token', sickbeard.EMBY_APIKEY) + + response = urllib2.urlopen(req) + response.close() + + except (urllib2.URLError, IOError) as e: + logger.log(u'EMBY: Warning: Couldn\'t contact Emby Server at ' + url + ' ' + ex(e), logger.WARNING) + return False + + logger.log(u'EMBY: Updating library on host: %s' % sickbeard.EMBY_HOST, logger.MESSAGE) + return True + +notifier = EmbyNotifier diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 06e21278..404b1a04 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -1044,6 +1044,9 @@ class PostProcessor(object): # do the library update for Plex notifiers.plex_notifier.update_library(ep_obj) + # do the library update for Emby + notifiers.emby_notifier.update_library() + # do the library update for NMJ # nmj_notifier kicks off its library update when the notify_download is issued (inside notifiers) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 6d7b6280..45ab8f93 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -535,6 +535,7 @@ class Home(MainHandler): {'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': self.haveXBMC}, {'title': 'Update Kodi', 'path': 'home/updateKODI/', 'requires': self.haveKODI}, {'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': self.havePLEX}, + {'title': 'Update Emby', 'path': 'home/updateEMBY/', 'requires': self.haveEMBY}, {'title': 'Restart', 'path': 'home/restart/?pid=' + str(sickbeard.PID), 'confirm': True}, {'title': 'Shutdown', 'path': 'home/shutdown/?pid=' + str(sickbeard.PID), 'confirm': True}, ] @@ -551,6 +552,10 @@ class Home(MainHandler): def havePLEX(): return sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY + @staticmethod + def haveEMBY(): + return sickbeard.USE_EMBY + @staticmethod def _getEpisode(show, season=None, episode=None, absolute=None): if show is None: @@ -844,6 +849,19 @@ class Home(MainHandler): return finalResult + def testEMBY(self, host=None, emby_apikey=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + if None is not emby_apikey and starify(emby_apikey, True): + emby_apikey = sickbeard.EMBY_APIKEY + + host = config.clean_host(host) + result = notifiers.emby_notifier.test_notify(urllib.unquote_plus(host), emby_apikey) + if result: + return 'Test Emby notice sent successfully to ' + urllib.unquote_plus(host) + else: + return 'Test Emby notice failed to ' + urllib.unquote_plus(host) + def testLibnotify(self, *args, **kwargs): self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') @@ -1660,6 +1678,15 @@ class Home(MainHandler): ui.notifications.error('Unable to contact', 'Plex Media Server host(s): ' + result.replace(',', ', ')) self.redirect('/home/') + def updateEMBY(self): + + if notifiers.emby_notifier.update_library(): + ui.notifications.message( + 'Library update command sent to Emby host: ' + sickbeard.EMBY_HOST) + else: + ui.notifications.error('Unable to contact Emby host: ' + sickbeard.EMBY_HOST) + self.redirect('/home/') + def setStatus(self, show=None, eps=None, status=None, direct=False): if show is None or eps is None or status is None: @@ -4880,6 +4907,7 @@ class ConfigNotifications(Config): use_plex=None, plex_notify_onsnatch=None, plex_notify_ondownload=None, plex_notify_onsubtitledownload=None, plex_update_library=None, plex_server_host=None, plex_host=None, plex_username=None, plex_password=None, + use_emby=None, emby_host=None, emby_apikey=None, use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None, growl_notify_onsubtitledownload=None, growl_host=None, growl_password=None, use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, @@ -4955,6 +4983,12 @@ class ConfigNotifications(Config): if set('*') != set(plex_password): sickbeard.PLEX_PASSWORD = plex_password + sickbeard.USE_EMBY = config.checkbox_to_value(use_emby) + sickbeard.EMBY_HOST = config.clean_host(emby_host) + key = emby_apikey.strip() + if not starify(key, True): + sickbeard.EMBY_APIKEY = key + sickbeard.USE_GROWL = config.checkbox_to_value(use_growl) sickbeard.GROWL_NOTIFY_ONSNATCH = config.checkbox_to_value(growl_notify_onsnatch) sickbeard.GROWL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(growl_notify_ondownload)