diff --git a/CHANGES.md b/CHANGES.md index f062d368..6ee3b63b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ * Change authentication credentials to display more securely on config pages * Add a "Use as default home page" selector to General Config/Interface/User Interface * Add Kodi notifier and metadata +* Add priority, device, and sound support to Pushover notifier (port from midgetspy/sickbeard) [develop changelog] diff --git a/gui/slick/interfaces/default/config_notifications.tmpl b/gui/slick/interfaces/default/config_notifications.tmpl index 4833b9d7..c1d8d8f8 100644 --- a/gui/slick/interfaces/default/config_notifications.tmpl +++ b/gui/slick/interfaces/default/config_notifications.tmpl @@ -977,11 +977,76 @@ + +
+ + + +
+
+ + +
+
+ +
Click below to test.
- + diff --git a/gui/slick/js/configNotifications.js b/gui/slick/js/configNotifications.js index e3c70c2f..e6469a3e 100644 --- a/gui/slick/js/configNotifications.js +++ b/gui/slick/js/configNotifications.js @@ -132,8 +132,11 @@ $(document).ready(function(){ }); $('#testPushover').click(function () { - var pushover_userkey = $('#pushover_userkey').val(); - var pushover_apikey = $('#pushover_apikey').val(); + var pushover_userkey = $.trim($('#pushover_userkey').val()); + var pushover_apikey = $.trim($('#pushover_apikey').val()); + var pushover_priority = $("#pushover_priority").val(); + var pushover_device = $("#pushover_device").val(); + var pushover_sound = $("#pushover_sound").val(); if (!pushover_userkey || !pushover_apikey) { $('#testPushover-result').html('Please fill out the necessary fields above.'); if (!pushover_userkey) { @@ -151,13 +154,71 @@ $(document).ready(function(){ $('#pushover_userkey,#pushover_apikey').removeClass('warning'); $(this).prop('disabled', true); $('#testPushover-result').html(loading); - $.get(sbRoot + '/home/testPushover', {'userKey': pushover_userkey, 'apiKey': pushover_apikey}) + $.get(sbRoot + '/home/testPushover', {'userKey': pushover_userkey, 'apiKey': pushover_apikey, 'priority': pushover_priority, 'device': pushover_device, 'sound': pushover_sound}) .done(function (data) { $('#testPushover-result').html(data); $('#testPushover').prop('disabled', false); }); }); + function get_pushover_devices (msg) { + var pushover_userkey = $.trim($('#pushover_userkey').val()); + var pushover_apikey = $.trim($('#pushover_apikey').val()); + if (!pushover_userkey || !pushover_apikey) { + $('#testPushover-result').html('Please fill out the necessary fields above.'); + if (!pushover_userkey) { + $('#pushover_userkey').addClass('warning'); + } else { + $('#pushover_userkey').removeClass('warning'); + } + if (!pushover_apikey) { + $('#pushover_apikey').addClass('warning'); + } else { + $('#pushover_apikey').removeClass('warning'); + } + return; + } + $(this).prop('disabled', true); + if (msg) { + $('#testPushover-result').html(loading); + } + var current_pushover_device = $('#pushover_device').val(); + $.get(sbRoot + "/home/getPushoverDevices", {'userKey': pushover_userkey, 'apiKey': pushover_apikey}) + .done(function (data) { + var devices = jQuery.parseJSON(data || '{}').devices; + $('#pushover_device_list').html(''); + // add default option to send to all devices + $('#pushover_device_list').append(''); + if (devices) { + for (var i = 0; i < devices.length; i++) { + // if a device in the list matches our current iden, select it + if (current_pushover_device == devices[i]) { + $('#pushover_device_list').append(''); + } else { + $('#pushover_device_list').append(''); + } + } + } + $('#getPushoverDevices').prop('disabled', false); + if (msg) { + $('#testPushover-result').html(msg); + } + }); + + $('#pushover_device_list').change(function () { + $('#pushover_device').val($('#pushover_device_list').val()); + $('#testPushover-result').html('Don\'t forget to save your new Pushover settings.'); + }); + } + + $('#getPushoverDevices').click(function () { + get_pushover_devices('Device list updated. Select specific device to use.'); + }); + + if ($('#use_pushover').prop('checked')) { + get_pushover_devices(); + } + $('#testLibnotify').click(function() { $('#testLibnotify-result').html(loading); $.get(sbRoot + '/home/testLibnotify', diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index f350f77e..8bc8344f 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -338,6 +338,9 @@ PUSHOVER_NOTIFY_ONDOWNLOAD = False PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = False PUSHOVER_USERKEY = None PUSHOVER_APIKEY = None +PUSHOVER_PRIORITY = 0 +PUSHOVER_DEVICE = None +PUSHOVER_SOUND = None USE_LIBNOTIFY = False LIBNOTIFY_NOTIFY_ONSNATCH = False @@ -505,7 +508,7 @@ def initialize(consoleLogging=True): WOMBLE, OMGWTFNZBS, OMGWTFNZBS_USERNAME, OMGWTFNZBS_APIKEY, providerList, newznabProviderList, torrentRssProviderList, \ EXTRA_SCRIPTS, USE_TWITTER, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, RECENTSEARCH_FREQUENCY, \ USE_BOXCAR2, BOXCAR2_ACCESSTOKEN, BOXCAR2_NOTIFY_ONDOWNLOAD, BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD, BOXCAR2_NOTIFY_ONSNATCH, BOXCAR2_SOUND, \ - USE_PUSHOVER, PUSHOVER_USERKEY, PUSHOVER_APIKEY, PUSHOVER_NOTIFY_ONDOWNLOAD, PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD, PUSHOVER_NOTIFY_ONSNATCH, \ + USE_PUSHOVER, PUSHOVER_USERKEY, PUSHOVER_APIKEY, PUSHOVER_NOTIFY_ONDOWNLOAD, PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD, PUSHOVER_NOTIFY_ONSNATCH, PUSHOVER_PRIORITY, PUSHOVER_DEVICE, PUSHOVER_SOUND, \ USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_NMJv2, NMJv2_HOST, NMJv2_DATABASE, NMJv2_DBLOC, USE_SYNOINDEX, \ USE_SYNOLOGYNOTIFIER, SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH, SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD, SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD, \ USE_EMAIL, EMAIL_HOST, EMAIL_PORT, EMAIL_TLS, EMAIL_USER, EMAIL_PASSWORD, EMAIL_FROM, EMAIL_NOTIFY_ONSNATCH, EMAIL_NOTIFY_ONDOWNLOAD, EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD, EMAIL_LIST, \ @@ -836,6 +839,10 @@ def initialize(consoleLogging=True): check_setting_int(CFG, 'Pushover', 'pushover_notify_onsubtitledownload', 0)) PUSHOVER_USERKEY = check_setting_str(CFG, 'Pushover', 'pushover_userkey', '') PUSHOVER_APIKEY = check_setting_str(CFG, 'Pushover', 'pushover_apikey', '') + PUSHOVER_PRIORITY = check_setting_int(CFG, 'Pushover', 'pushover_priority', 0) + PUSHOVER_DEVICE = check_setting_str(CFG, 'Pushover', 'pushover_device', 'all') + PUSHOVER_SOUND = check_setting_str(CFG, 'Pushover', 'pushover_sound', 'pushover') + USE_LIBNOTIFY = bool(check_setting_int(CFG, 'Libnotify', 'use_libnotify', 0)) LIBNOTIFY_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_onsnatch', 0)) LIBNOTIFY_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_ondownload', 0)) @@ -1696,6 +1703,9 @@ def save_config(): new_config['Pushover']['pushover_notify_onsubtitledownload'] = int(PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD) new_config['Pushover']['pushover_userkey'] = PUSHOVER_USERKEY new_config['Pushover']['pushover_apikey'] = PUSHOVER_APIKEY + new_config['Pushover']['pushover_priority'] = int(PUSHOVER_PRIORITY) + new_config['Pushover']['pushover_device'] = PUSHOVER_DEVICE + new_config['Pushover']['pushover_sound'] = PUSHOVER_SOUND new_config['Libnotify'] = {} new_config['Libnotify']['use_libnotify'] = int(USE_LIBNOTIFY) diff --git a/sickbeard/notifiers/pushover.py b/sickbeard/notifiers/pushover.py index 15bc1d33..f48a763d 100644 --- a/sickbeard/notifiers/pushover.py +++ b/sickbeard/notifiers/pushover.py @@ -17,23 +17,50 @@ # # You should have received a copy of the GNU General Public License # along with SickGear. If not, see . -import httplib -import urllib, urllib2 + +import urllib +import urllib2 import time +import socket +import base64 import sickbeard from sickbeard import logger from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD, NOTIFY_SUBTITLE_DOWNLOAD, NOTIFY_GIT_UPDATE, NOTIFY_GIT_UPDATE_TEXT from sickbeard.exceptions import ex -API_URL = "https://api.pushover.net/1/messages.json" +API_URL = 'https://api.pushover.net/1/messages.json' +DEVICE_URL = 'https://api.pushover.net/1/users/validate.json' class PushoverNotifier: - def test_notify(self, userKey=None, apiKey=None): - return self._notifyPushover("This is a test notification from SickGear", 'Test', userKey, apiKey, force=True) - def _sendPushover(self, msg, title, userKey=None, apiKey=None): + def get_devices(self, userKey=None, apiKey=None): + # fill in omitted parameters + if not userKey: + userKey = sickbeard.PUSHOVER_USERKEY + if not apiKey: + apiKey = sickbeard.PUSHOVER_APIKEY + + data = urllib.urlencode({ + 'token': apiKey, + 'user': userKey + }) + + # get devices from pushover + try: + req = urllib2.Request(DEVICE_URL) + handle = urllib2.urlopen(req, data) + if handle: + result = handle.read() + handle.close() + return result + except urllib2.URLError: + return None + except socket.timeout: + return None + + def _sendPushover(self, title, msg, userKey, apiKey, priority, device, sound): """ Sends a pushover notification to the address provided @@ -44,91 +71,71 @@ class PushoverNotifier: returns: True if the message succeeded, False otherwise """ - if userKey == None: + # fill in omitted parameters + if not userKey: userKey = sickbeard.PUSHOVER_USERKEY - - if apiKey == None: + if not apiKey: apiKey = sickbeard.PUSHOVER_APIKEY - logger.log("Pushover API KEY in use: " + apiKey, logger.DEBUG) - # build up the URL and parameters msg = msg.strip() + data = urllib.urlencode({ + 'token': apiKey, + 'title': title, + 'user': userKey, + 'message': msg.encode('utf-8'), + 'priority': priority, + 'device': device, + 'sound': sound, + 'timestamp': int(time.time()) + }) + # send the request to pushover try: - conn = httplib.HTTPSConnection("api.pushover.net:443") - conn.request("POST", "/1/messages.json", - urllib.urlencode({ - "token": apiKey, - "user": userKey, - "title": title.encode('utf-8'), - "message": msg.encode('utf-8'), - 'timestamp': int(time.time()), - "retry": 60, - "expire": 3600, - }), {"Content-type": "application/x-www-form-urlencoded"}) - - except urllib2.HTTPError, e: - # if we get an error back that doesn't have an error code then who knows what's really happening - if not hasattr(e, 'code'): - logger.log("Pushover notification failed." + ex(e), logger.ERROR) - return False - else: - logger.log("Pushover notification failed. Error code: " + str(e.code), logger.ERROR) + req = urllib2.Request(API_URL) + handle = urllib2.urlopen(req, data) + handle.close() + except urllib2.URLError, e: # HTTP status 404 if the provided email address isn't a Pushover user. if e.code == 404: - logger.log("Username is wrong/not a pushover email. Pushover will send an email to it", logger.WARNING) + logger.log(u'PUSHOVER: Username is wrong/not a Pushover email. Pushover will send an email to it', logger.WARNING) return False # For HTTP status code 401's, it is because you are passing in either an invalid token, or the user has not added your service. elif e.code == 401: - #HTTP status 401 if the user doesn't have the service added - subscribeNote = self._sendPushover(msg, title, userKey, apiKey) + # HTTP status 401 if the user doesn't have the service added + subscribeNote = self._sendPushover(title, msg, userKey) if subscribeNote: - logger.log("Subscription send", logger.DEBUG) + logger.log(u'PUSHOVER: Subscription sent', logger.DEBUG) return True else: - logger.log("Subscription could not be send", logger.ERROR) + logger.log(u'PUSHOVER: Subscription could not be sent', logger.ERROR) return False # If you receive an HTTP status code of 400, it is because you failed to send the proper parameters elif e.code == 400: - logger.log("Wrong data sent to pushover", logger.ERROR) + logger.log(u'PUSHOVER: Wrong data sent to Pushover', logger.ERROR) return False # If you receive a HTTP status code of 429, it is because the message limit has been reached (free limit is 7,500) elif e.code == 429: - logger.log("Pushover API message limit reached - try a different API key", logger.ERROR) + logger.log(u'PUSHOVER: API message limit reached - try a different API key', logger.ERROR) return False - logger.log("Pushover notification successful.", logger.MESSAGE) + # If you receive a HTTP status code of 500, service is unavailable + elif e.code == 500: + logger.log(u'PUSHOVER: Unable to connect to API, service unavailable', logger.ERROR) + return False + + logger.log(u'PUSHOVER: Notification successful.', logger.MESSAGE) return True - def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]): - if sickbeard.PUSHOVER_NOTIFY_ONSNATCH: - self._notifyPushover(title, ep_name) - - - def notify_download(self, ep_name, title=notifyStrings[NOTIFY_DOWNLOAD]): - if sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD: - self._notifyPushover(title, ep_name) - - def notify_subtitle_download(self, ep_name, lang, title=notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD]): - if sickbeard.PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD: - self._notifyPushover(title, ep_name + ": " + lang) - - def notify_git_update(self, new_version = "??"): - if sickbeard.USE_PUSHOVER: - update_text=notifyStrings[NOTIFY_GIT_UPDATE_TEXT] - title=notifyStrings[NOTIFY_GIT_UPDATE] - self._notifyPushover(title, update_text + new_version) - - def _notifyPushover(self, title, message, userKey=None, apiKey=None, force=False): + def _notifyPushover(self, title, message, userKey=None, apiKey=None, priority=None, device=None, sound=None, force=False): """ - Sends a pushover notification based on the provided info or SB config + Sends a pushover notification based on the provided info or SG config title: The title of the notification to send message: The message string to send @@ -136,14 +143,46 @@ class PushoverNotifier: force: Enforce sending, for instance for testing """ + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_PUSHOVER and not force: - logger.log("Notification for Pushover not enabled, skipping this notification", logger.DEBUG) + logger.log(u'PUSHOVER: Notifications not enabled, skipping this notification', logger.DEBUG) return False - logger.log("Sending notification for " + message, logger.DEBUG) + # fill in omitted parameters + if not userKey: + userKey = sickbeard.PUSHOVER_USERKEY + if not apiKey: + apiKey = sickbeard.PUSHOVER_APIKEY + if not priority: + priority = sickbeard.PUSHOVER_PRIORITY + if not device: + device = sickbeard.PUSHOVER_DEVICE + if not sound: + sound = sickbeard.PUSHOVER_SOUND - # self._sendPushover(message, title, userKey, apiKey) - return self._sendPushover(message, title, userKey, apiKey) + logger.log(u'PUSHOVER: Sending notice with details: %s - %s, priority: %s, device: %s, sound: %s' % (title, message, priority, device, sound), logger.DEBUG) + return self._sendPushover(title, message, userKey, apiKey, priority, device, sound) + + def test_notify(self, userKey, apiKey, priority, device, sound): + return self._notifyPushover('Test', 'This is a test notification from SickGear', userKey, apiKey, priority, device, sound, force=True) + + def notify_snatch(self, ep_name): + if sickbeard.PUSHOVER_NOTIFY_ONSNATCH: + self._notifyPushover(notifyStrings[NOTIFY_SNATCH], ep_name) + + def notify_download(self, ep_name): + if sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD: + self._notifyPushover(notifyStrings[NOTIFY_DOWNLOAD], ep_name) + + def notify_subtitle_download(self, ep_name, lang): + if sickbeard.PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD: + self._notifyPushover(notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD], ep_name + ': ' + lang) + + def notify_git_update(self, new_version = '??'): + if sickbeard.USE_PUSHOVER: + update_text=notifyStrings[NOTIFY_GIT_UPDATE_TEXT] + title=notifyStrings[NOTIFY_GIT_UPDATE] + self._notifyPushover(title, update_text + new_version) notifier = PushoverNotifier diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 7264285d..6b7b05c7 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -662,7 +662,7 @@ class Home(MainHandler): else: return 'Error sending Boxcar2 notification' - def testPushover(self, userKey=None, apiKey=None): + def testPushover(self, userKey=None, apiKey=None, priority=None, device=None, sound=None): self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') if None is not userKey and starify(userKey, True): @@ -671,12 +671,27 @@ class Home(MainHandler): if None is not apiKey and starify(apiKey, True): apiKey = sickbeard.PUSHOVER_APIKEY - result = notifiers.pushover_notifier.test_notify(userKey, apiKey) + result = notifiers.pushover_notifier.test_notify(userKey, apiKey, priority, device, sound) if result: return 'Pushover notification succeeded. Check your Pushover clients to make sure it worked' else: return 'Error sending Pushover notification' + def getPushoverDevices(self, userKey=None, apiKey=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + if None is not userKey and starify(userKey, True): + userKey = sickbeard.PUSHOVER_USERKEY + + if None is not apiKey and starify(apiKey, True): + apiKey = sickbeard.PUSHOVER_APIKEY + + result = notifiers.pushover_notifier.get_devices(userKey, apiKey) + if result: + return result + else: + return "{}" + def twitterStep1(self, *args, **kwargs): self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') @@ -4176,6 +4191,7 @@ class ConfigNotifications(Config): boxcar2_notify_onsubtitledownload=None, boxcar2_accesstoken=None, boxcar2_sound=None, use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None, pushover_notify_onsubtitledownload=None, pushover_userkey=None, pushover_apikey=None, + pushover_priority=None, pushover_device=None, pushover_sound=None, pushover_device_list=None, use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None, libnotify_notify_onsubtitledownload=None, use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None, @@ -4281,6 +4297,9 @@ class ConfigNotifications(Config): key = pushover_apikey.strip() if not starify(key, True): sickbeard.PUSHOVER_APIKEY = key + sickbeard.PUSHOVER_PRIORITY = pushover_priority + sickbeard.PUSHOVER_DEVICE = pushover_device + sickbeard.PUSHOVER_SOUND = pushover_sound sickbeard.USE_LIBNOTIFY = config.checkbox_to_value(use_libnotify) sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH = config.checkbox_to_value(libnotify_notify_onsnatch)