diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html
index 103864d3..34721d31 100644
--- a/data/interfaces/default/config.html
+++ b/data/interfaces/default/config.html
@@ -690,8 +690,7 @@
-
-
+
+
+ |
+
+
- |
+
+
+
+
+
+
@@ -1493,8 +1529,48 @@
$("#twitteroptions").slideUp();
}
});
-
- if ($("#songkick").is(":checked"))
+
+ if ($("#osx_notify").is(":checked"))
+ {
+ $("#osx_notify_options").show();
+ }
+ else
+ {
+ $("#osx_notify_options").hide();
+ }
+
+ $("#osx_notify").click(function(){
+ if ($("#osx_notify").is(":checked"))
+ {
+ $("#osx_notify_options").slideDown();
+ }
+ else
+ {
+ $("#osx_notify_options").slideUp();
+ }
+ });
+
+ if ($("#boxcar").is(":checked"))
+ {
+ $("#boxcar_options").show();
+ }
+ else
+ {
+ $("#boxcar_options").hide();
+ }
+
+ $("#boxcar").click(function(){
+ if ($("#boxcar").is(":checked"))
+ {
+ $("#boxcar_options").slideDown();
+ }
+ else
+ {
+ $("#boxcar_options").slideUp();
+ }
+ });
+
+ if ($("#songkick").is(":checked"))
{
$("#songkickoptions").show();
}
@@ -1707,6 +1783,12 @@
function (data) { $('#ajaxMsg').html(""+data+"
"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
+
+ $('#osxnotifyregister').click(function () {
+ var osx_notify_app = $("#osx_notify_reg").val();
+ $.get("/osxnotifyregister", {'app': osx_notify_app}, function (data) { $('#ajaxMsg').html(""+data+"
"); });
+ $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut()
+ })
}
$(document).ready(function() {
diff --git a/headphones/__init__.py b/headphones/__init__.py
index 7cb9a92f..7e2a6201 100644
--- a/headphones/__init__.py
+++ b/headphones/__init__.py
@@ -271,6 +271,12 @@ TWITTER_ONSNATCH = False
TWITTER_USERNAME = None
TWITTER_PASSWORD = None
TWITTER_PREFIX = None
+OSX_NOTIFY_ENABLED = False
+OSX_NOTIFY_ONSNATCH = False
+OSX_NOTIFY_APP = None
+BOXCAR_ENABLED = False
+BOXCAR_ONSNATCH = False
+BOXCAR_TOKEN = None
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
MIRROR = None
CUSTOMHOST = None
@@ -350,8 +356,7 @@ def initialize():
INTERFACE, FOLDER_PERMISSIONS, FILE_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \
MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, ENCODER_MULTICORE, ENCODER_MULTICORE_COUNT, DELETE_LOSSLESS_FILES, \
GROWL_ENABLED, GROWL_HOST, GROWL_PASSWORD, GROWL_ONSNATCH, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, PUSHOVER_ENABLED, PUSHOVER_PRIORITY, PUSHOVER_KEYS, PUSHOVER_ONSNATCH, PUSHOVER_APITOKEN, MIRRORLIST, \
- TWITTER_ENABLED, TWITTER_ONSNATCH, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \
- BOXCAR_ENABLED, BOXCAR_USERNAME, BOXCAR_ONSNATCH, \
+ TWITTER_ENABLED, TWITTER_ONSNATCH, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, OSX_NOTIFY_ENABLED, OSX_NOTIFY_ONSNATCH, OSX_NOTIFY_APP, BOXCAR_ENABLED, BOXCAR_ONSNATCH, BOXCAR_TOKEN, \
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
@@ -388,6 +393,8 @@ def initialize():
CheckSection('Pushalot')
CheckSection('Synoindex')
CheckSection('Twitter')
+ CheckSection('OSX_Notify')
+ CheckSection('Boxcar')
CheckSection('Songkick')
CheckSection('Advanced')
@@ -615,7 +622,15 @@ def initialize():
TWITTER_USERNAME = check_setting_str(CFG, 'Twitter', 'twitter_username', '')
TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '')
TWITTER_PREFIX = check_setting_str(CFG, 'Twitter', 'twitter_prefix', 'Headphones')
-
+
+ OSX_NOTIFY_ENABLED = bool(check_setting_int(CFG, 'OSX_Notify', 'osx_notify_enabled', 0))
+ OSX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'OSX_Notify', 'osx_notify_onsnatch', 0))
+ OSX_NOTIFY_APP = check_setting_str(CFG, 'OSX_Notify', 'osx_notify_app', '/Applications/Headphones')
+
+ BOXCAR_ENABLED = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_enabled', 0))
+ BOXCAR_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_onsnatch', 0))
+ BOXCAR_TOKEN = check_setting_str(CFG, 'Boxcar', 'boxcar_token', '')
+
SONGKICK_ENABLED = bool(check_setting_int(CFG, 'Songkick', 'songkick_enabled', 1))
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
@@ -1036,6 +1051,16 @@ def config_write():
new_config['Twitter']['twitter_password'] = TWITTER_PASSWORD
new_config['Twitter']['twitter_prefix'] = TWITTER_PREFIX
+ new_config['OSX_Notify'] = {}
+ new_config['OSX_Notify']['osx_notify_enabled'] = int(OSX_NOTIFY_ENABLED)
+ new_config['OSX_Notify']['osx_notify_onsnatch'] = int(OSX_NOTIFY_ONSNATCH)
+ new_config['OSX_Notify']['osx_notify_app'] = OSX_NOTIFY_APP
+
+ new_config['Boxcar'] = {}
+ new_config['Boxcar']['boxcar_enabled'] = int(BOXCAR_ENABLED)
+ new_config['Boxcar']['boxcar_onsnatch'] = int(BOXCAR_ONSNATCH)
+ new_config['Boxcar']['boxcar_token'] = BOXCAR_TOKEN
+
new_config['Songkick'] = {}
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
diff --git a/headphones/notifiers.py b/headphones/notifiers.py
index e7f4f5fe..cd0f6265 100644
--- a/headphones/notifiers.py
+++ b/headphones/notifiers.py
@@ -133,7 +133,7 @@ class PROWL:
return
http_handler = HTTPSConnection("api.prowlapp.com")
-
+
data = {'apikey': headphones.PROWL_KEYS,
'application': 'Headphones',
'event': event,
@@ -168,7 +168,7 @@ class PROWL:
self.priority = priority
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
-
+
class XBMC:
def __init__(self):
@@ -208,10 +208,10 @@ class XBMC:
for host in hosts:
logger.info('Sending library update command to XBMC @ '+host)
request = self._sendjson(host, 'AudioLibrary.Scan')
-
+
if not request:
logger.warn('Error sending update request to XBMC')
-
+
def notify(self, artist, album, albumartpath):
hosts = [x.strip() for x in self.hosts.split(',')]
@@ -239,14 +239,15 @@ class XBMC:
except:
logger.warn('Error sending notification request to XBMC')
+
class LMS:
#Class for updating a Logitech Media Server
def __init__(self):
-
+
self.hosts = headphones.LMS_HOST
-
+
def _sendjson(self, host):
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]} #Had a lot of trouble with simplejson, but this works.
data = simplejson.JSONEncoder().encode(data)
@@ -263,7 +264,7 @@ class LMS:
response = simplejson.JSONDecoder().decode(handle.read())
server_result = simplejson.dumps(response)
-
+
try:
return response[0]['result']
except:
@@ -271,24 +272,24 @@ class LMS:
return
def update(self):
-
- #Send the ["rescan"] command to an LMS server.
- #Note that the command must be prefixed with the 'player' that the command is aimed at,
- #But with this being a request for the server to update its library, the player is blank, so ""
+
+ #Send the ["rescan"] command to an LMS server.
+ #Note that the command must be prefixed with the 'player' that the command is aimed at,
+ #But with this being a request for the server to update its library, the player is blank, so ""
hosts = [x.strip() for x in self.hosts.split(',')]
for host in hosts:
logger.info('Sending library rescan command to LMS @ '+host)
request = self._sendjson(host)
-
+
if not request:
logger.warn('Error sending rescan request to LMS')
class Plex:
def __init__(self):
-
+
self.server_hosts = headphones.PLEX_SERVER_HOST
self.client_hosts = headphones.PLEX_CLIENT_HOST
self.username = headphones.PLEX_USERNAME
@@ -298,31 +299,31 @@ class Plex:
username = self.username
password = self.password
-
+
url_command = urllib.urlencode(command)
-
+
url = host + '/xbmcCmds/xbmcHttp/?' + url_command
-
+
req = urllib2.Request(url)
-
+
if password:
base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
req.add_header("Authorization", "Basic %s" % base64string)
-
+
logger.info('Plex url: %s' % url)
-
+
try:
handle = urllib2.urlopen(req)
except Exception, e:
logger.warn('Error opening Plex url: %s' % e)
return
-
+
response = handle.read().decode(headphones.SYS_ENCODING)
-
+
return response
-
+
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
@@ -350,7 +351,7 @@ class Plex:
except Exception, e:
logger.warn("Error updating library section for Plex Media Server: %s" % e)
return False
-
+
def notify(self, artist, album, albumartpath):
hosts = [x.strip() for x in self.client_hosts.split(',')]
@@ -362,12 +363,12 @@ class Plex:
for host in hosts:
logger.info('Sending notification command to Plex Media Server @ '+host)
try:
- notification = header + "," + message + "," + time + "," + albumartpath
- notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification('+notification+')'}
- request = self._sendhttp(host, notifycommand)
+ notification = header + "," + message + "," + time + "," + albumartpath
+ notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification('+notification+')'}
+ request = self._sendhttp(host, notifycommand)
- if not request:
- raise Exception
+ if not request:
+ raise Exception
except:
logger.warn('Error sending notification request to Plex Media Server')
@@ -375,30 +376,30 @@ class Plex:
class NMA:
def __init__(self):
-
+
self.apikey = headphones.NMA_APIKEY
self.priority = headphones.NMA_PRIORITY
-
+
def _send(self, data):
return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data)
-
+
def notify(self, artist=None, album=None, snatched_nzb=None):
-
+
apikey = self.apikey
priority = self.priority
-
+
if snatched_nzb:
event = snatched_nzb + " snatched!"
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
else:
event = artist + ' - ' + album + ' complete!'
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
-
+
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority}
logger.info('Sending notification request to NotifyMyAndroid')
request = self._send(data)
-
+
if not request:
logger.warn('Error sending notification request to NotifyMyAndroid')
@@ -416,7 +417,7 @@ class PUSHBULLET:
return
http_handler = HTTPSConnection("api.pushbullet.com")
-
+
data = {'device_iden': headphones.PUSHBULLET_DEVICEID,
'type': "note",
'title': "Headphones",
@@ -455,21 +456,20 @@ class PUSHBULLET:
self.notify('Main Screen Activate', 'Test Message')
-
class PUSHALOT:
def notify(self, message, event):
if not headphones.PUSHALOT_ENABLED:
return
- pushalot_authorizationtoken = headphones.PUSHALOT_APIKEY
+ pushalot_authorizationtoken = headphones.PUSHALOT_APIKEY
- logger.debug(u"Pushalot event: " + event)
- logger.debug(u"Pushalot message: " + message)
- logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
+ logger.debug(u"Pushalot event: " + event)
+ logger.debug(u"Pushalot message: " + message)
+ logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
http_handler = HTTPSConnection("pushalot.com")
-
+
data = {'AuthorizationToken': pushalot_authorizationtoken,
'Title': event.encode('utf-8'),
'Body': message.encode("utf-8") }
@@ -530,6 +530,7 @@ class Synoindex:
if isinstance(path_list, list):
for path in path_list:
self.notify(path)
+
class PUSHOVER:
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
@@ -552,7 +553,7 @@ class PUSHOVER:
return
http_handler = HTTPSConnection("api.pushover.net")
-
+
data = {'token': self.application_token,
'user': headphones.PUSHOVER_KEYS,
'title': event,
@@ -590,17 +591,17 @@ class PUSHOVER:
self.priority = priority
self.notify('Main Screen Activate', 'Test Message')
-
+
class TwitterNotifier:
consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
-
+
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
-
+
def notify_snatch(self, title):
if headphones.TWITTER_ONSNATCH:
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+title+' at '+helpers.now())
@@ -613,37 +614,37 @@ class TwitterNotifier:
return self._notifyTwitter("This is a test notification from Headphones at "+helpers.now(), force=True)
def _get_authorization(self):
-
+
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
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')
-
+
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'])
else:
request_token = dict(parse_qsl(content))
-
+
headphones.TWITTER_USERNAME = request_token['oauth_token']
headphones.TWITTER_PASSWORD = request_token['oauth_token_secret']
-
+
return self.AUTHORIZATION_URL+"?oauth_token="+ request_token['oauth_token']
-
+
def _get_credentials(self, key):
request_token = {}
-
+
request_token['oauth_token'] = headphones.TWITTER_USERNAME
request_token['oauth_token_secret'] = headphones.TWITTER_PASSWORD
request_token['oauth_callback_confirmed'] = 'true'
-
+
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)
-
+
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
logger.info('oauth_consumer: '+str(oauth_consumer))
@@ -651,10 +652,10 @@ class TwitterNotifier:
logger.info('oauth_client: '+str(oauth_client))
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key)
logger.info('resp, content: '+str(resp)+','+str(content))
-
+
access_token = dict(parse_qsl(content))
logger.info('access_token: '+str(access_token))
-
+
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.ERROR)
@@ -665,147 +666,118 @@ class TwitterNotifier:
headphones.TWITTER_USERNAME = access_token['oauth_token']
headphones.TWITTER_PASSWORD = access_token['oauth_token_secret']
return True
-
-
+
+
def _send_tweet(self, message=None):
-
+
username=self.consumer_key
password=self.consumer_secret
access_token_key=headphones.TWITTER_USERNAME
access_token_secret=headphones.TWITTER_PASSWORD
-
+
logger.info(u"Sending tweet: "+message)
-
+
api = twitter.Api(username, password, access_token_key, access_token_secret)
-
+
try:
api.PostUpdate(message)
except Exception, e:
logger.info(u"Error Sending Tweet: %s" % e)
return False
-
+
return True
-
+
def _notifyTwitter(self, message='', force=False):
prefix = headphones.TWITTER_PREFIX
-
+
if not headphones.TWITTER_ENABLED and not force:
return False
-
+
return self._send_tweet(prefix+": "+message)
notifier = TwitterNotifier
-API_URL = "https://boxcar.io/devices/providers/WqbewHpV8ZATnawpCsr4/notifications"
+class OSX_NOTIFY:
+
+ objc = None
+
+ def __init__(self):
+ try:
+ self.objc = __import__("objc")
+ except:
+ return False
+
+ def swizzle(self, cls, SEL, func):
+ old_IMP = cls.instanceMethodForSelector_(SEL)
+ def wrapper(self, *args, **kwargs):
+ return func(self, old_IMP, *args, **kwargs)
+ new_IMP = self.objc.selector(wrapper, selector=old_IMP.selector,
+ signature=old_IMP.signature)
+ self.objc.classAddMethod(cls, SEL, new_IMP)
+
+ def notify(self, title, subtitle=None, text=None, sound=True):
+
+ try:
+ self.swizzle(self.objc.lookUpClass('NSBundle'),
+ b'bundleIdentifier',
+ self.swizzled_bundleIdentifier)
+
+ NSUserNotification = self.objc.lookUpClass('NSUserNotification')
+ NSUserNotificationCenter = self.objc.lookUpClass('NSUserNotificationCenter')
+ NSAutoreleasePool = self.objc.lookUpClass('NSAutoreleasePool')
+
+ if not NSUserNotification or not NSUserNotificationCenter:
+ return False
+
+ pool = NSAutoreleasePool.alloc().init()
+
+ notification = NSUserNotification.alloc().init()
+ notification.setTitle_(title)
+ if subtitle:
+ notification.setSubtitle_(subtitle)
+ if text:
+ notification.setInformativeText_(text)
+ if sound:
+ notification.setSoundName_("NSUserNotificationDefaultSoundName")
+ notification.setHasActionButton_(False)
+
+ notification_center = NSUserNotificationCenter.defaultUserNotificationCenter()
+ notification_center.deliverNotification_(notification)
+
+ del pool
+ return True
+
+ except Exception, e:
+ logger.warn('Error sending OS X Notification: %s' % e)
+ return False
+
+ def swizzled_bundleIdentifier(self, original, swizzled):
+ return 'ade.headphones.osxnotify'
class BOXCAR:
- def test_notify(self, email, title="Test"):
- return self._sendBoxcar("This is a test notification from Headphones", title, email)
+ def __init__(self):
- def _sendBoxcar(self, msg, title, email, subscribe=False):
- """
- Sends a boxcar notification to the address provided
+ self.url = 'https://new.boxcar.io/api/notifications'
- msg: The message to send (unicode)
- title: The title of the message
- email: The email address to send the message to (or to subscribe with)
- subscribe: If true then instead of sending a message this function will send a subscription notificat$
+ def notify(self, title, message, rgid=None):
- returns: True if the message succeeded, False otherwise
- """
+ try:
+ if rgid:
+ message += '
MusicBrainz' % rgid
- # build up the URL and parameters
- msg = msg.strip()
- curUrl = API_URL
- # if this is a subscription notification then act accordingly
- if subscribe:
- data = urllib.urlencode({'email': email})
- curUrl = curUrl + "/subscribe"
- # for normal requests we need all these parameters
- else:
data = urllib.urlencode({
- 'email': email,
- 'notification[from_screen_name]': title,
- 'notification[message]': msg.encode('utf-8'),
- 'notification[from_remote_service_id]': int(time.time())
+ 'user_credentials': headphones.BOXCAR_TOKEN,
+ 'notification[title]': title.encode('utf-8'),
+ 'notification[long_message]': message.encode('utf-8'),
+ 'notification[sound]': "done"
})
- logger.info(data)
- # send the request to boxcar
- try:
- req = urllib2.Request(curUrl)
+ req = urllib2.Request(self.url)
handle = urllib2.urlopen(req, data)
handle.close()
+ return True
except urllib2.URLError, 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.error("Boxcar notification failed." + ex(e))
- return False
- else:
- logger.error("Boxcar notification failed. Error code: " + str(e.code))
-
- # HTTP status 404 if the provided email address isn't a Boxcar user.
- if e.code == 404:
- logger.error("Username is wrong/not a boxcar email. Boxcar will send an email to it")
- 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$
- elif e.code == 401:
-
- # If the user has already added your service, we'll return an HTTP status code of 401.
- if subscribe:
- logger.error("Already subscribed to service")
- # i dont know if this is true or false ... its neither but i also dont know how we got here in the first $
- return False
-
- #HTTP status 401 if the user doesn't have the service added
- else:
- subscribeNote = self._sendBoxcar(msg, title, email, True)
- if subscribeNote:
- logger.info("Subscription send")
- return True
- else:
- logger.info("Subscription could not be send")
- 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.info("Wrong data sent to boxcar")
- logger.info('data:' + data)
- return False
-
- logger.fdebug("Boxcar notification successful.")
- return True
-
- def notify(self, artist=None, album=None, snatched_nzb=None, username=None, force=False):
- """
- Sends a boxcar notification based on the provided info or SB config
-
- title: The title of the notification to send
- message: The message string to send
- username: The username to send the notification to (optional, defaults to the username in the config)
- force: If True then the notification will be sent even if Boxcar is disabled in the config
- """
- if not headphones.BOXCAR_ENABLED and not force:
- logger.fdebug("Notification for Boxcar not enabled, skipping this notification")
- return False
-
- # if no username was given then use the one from the config
- if not username:
- username = headphones.BOXCAR_USERNAME
-
- if snatched_nzb:
- title = "Headphones. Sucessfully Snatched!"
- message = u"Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
- else:
- title = "Headphones. Successfully Downloaded & Post-Processed!"
- message = u"Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
-
- logger.info("Sending notification to Boxcar")
-
- self._sendBoxcar(message, title, username)
- return True
-
-
+ logger.warn('Error sending Boxcar2 Notification: %s' % e)
+ return False
\ No newline at end of file
diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py
index c0bfa2e2..684c2f70 100644
--- a/headphones/postprocessor.py
+++ b/headphones/postprocessor.py
@@ -427,7 +427,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
librarysync.libraryScan(dir=albumpath, append=True, ArtistID=release['ArtistID'], ArtistName=release['ArtistName'])
logger.info(u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
-
+
if headphones.GROWL_ENABLED:
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
logger.info(u"Growl request")
@@ -496,7 +496,18 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
logger.info(u"Sending Twitter notification")
twitter = notifiers.TwitterNotifier()
twitter.notify_download(pushmessage)
-
+
+ if headphones.OSX_NOTIFY_ENABLED:
+ logger.info(u"Sending OS X notification")
+ osx_notify = notifiers.OSX_NOTIFY()
+ osx_notify.notify(release['ArtistName'], release['AlbumTitle'], "Download and Postprocessing completed")
+
+ if headphones.BOXCAR_ENABLED:
+ pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
+ logger.info(u"Sending Boxcar2 notification")
+ boxcar = notifiers.BOXCAR()
+ boxcar.notify('Headphones processed: ' + pushmessage, "Download and Postprocessing completed", release['AlbumID'])
+
def embedAlbumArt(artwork, downloaded_track_list):
logger.info('Embedding album art')
diff --git a/headphones/sab.py b/headphones/sab.py
index 0bdb584a..52d8e3e1 100644
--- a/headphones/sab.py
+++ b/headphones/sab.py
@@ -119,39 +119,6 @@ def sendNZB(nzb):
if sabText == "ok":
logger.info(u"NZB sent to SAB successfully")
- if headphones.GROWL_ENABLED and headphones.GROWL_ONSNATCH:
- logger.info(u"Sending Growl notification")
- growl = notifiers.GROWL()
- growl.notify(nzb.name,"Download started")
- if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
- logger.info(u"Sending Prowl notification")
- prowl = notifiers.PROWL()
- prowl.notify(nzb.name,"Download started")
- if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
- logger.info(u"Sending Pushover notification")
- prowl = notifiers.PUSHOVER()
- prowl.notify(nzb.name,"Download started")
- if headphones.BOXCAR_ENABLED and headphones.BOXCAR_ONSNATCH:
- logger.info(u"Sending Boxcar notification")
- boxcar = notifiers.BOXCAR()
- boxcar.notify(snatched_nzb=nzb.name)
- if headphones.PUSHBULLET_ENABLED and headphones.PUSHBULLET_ONSNATCH:
- logger.info(u"Sending PushBullet notification")
- pushbullet = notifiers.PUSHBULLET()
- pushbullet.notify(nzb.name + " has been snatched!", "Download started")
- if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
- logger.info(u"Sending Twitter notification")
- twitter = notifiers.TwitterNotifier()
- twitter.notify_snatch(nzb.name)
- if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
- logger.debug(u"Sending NMA notification")
- nma = notifiers.NMA()
- nma.notify(snatched_nzb=nzb.name)
- if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
- logger.info(u"Sending Pushalot notification")
- pushalot = notifiers.PUSHALOT()
- pushalot.notify(nzb.name,"Download started")
-
return True
elif sabText == "Missing authentication":
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
diff --git a/headphones/searcher.py b/headphones/searcher.py
index 9bf7bbfc..c1aee71a 100644
--- a/headphones/searcher.py
+++ b/headphones/searcher.py
@@ -31,7 +31,7 @@ import subprocess
import headphones
from headphones.common import USER_AGENT
from headphones import logger, db, helpers, classes, sab, nzbget, request
-from headphones import transmission
+from headphones import transmission, notifiers
import lib.bencode as bencode
@@ -359,6 +359,9 @@ def searchNZB(album, new=False, losslessOnly=False):
logger.info("Album type is audiobook/spokenword. Using audiobook category")
for newznab_host in newznab_hosts:
+
+ provider = newznab_host[0]
+
# Add a little mod for kere.ws
if newznab_host[0] == "http://kere.ws":
if categories == "3040":
@@ -573,14 +576,16 @@ def send_to_downloader(data, bestqual, album):
nzb = classes.NZBDataSearchResult()
nzb.extraInfo.append(data)
nzb.name = folder_name
- nzbget.sendNZB(nzb)
+ if not nzbget.sendNZB(nzb):
+ return
elif headphones.NZB_DOWNLOADER == 0:
nzb = classes.NZBDataSearchResult()
nzb.extraInfo.append(data)
nzb.name = folder_name
- sab.sendNZB(nzb)
+ if not sab.sendNZB(nzb):
+ return
# If we sent the file to sab, we can check how it was renamed and insert that into the snatched table
(replace_spaces, replace_dots) = sab.checkConfig()
@@ -695,6 +700,54 @@ def send_to_downloader(data, bestqual, album):
myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [album['AlbumID']])
myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Snatched", folder_name, kind])
+ # notify
+ artist = album[1]
+ albumname = album[2]
+ rgid = album[6]
+ title = artist + ' - ' + albumname
+ provider = bestqual[3]
+ if provider.startswith(("http://", "https://")):
+ provider = provider.split("//")[1]
+ name = folder_name if folder_name else None
+
+ if headphones.GROWL_ENABLED and headphones.GROWL_ONSNATCH:
+ logger.info(u"Sending Growl notification")
+ growl = notifiers.GROWL()
+ growl.notify(name,"Download started")
+ if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
+ logger.info(u"Sending Prowl notification")
+ prowl = notifiers.PROWL()
+ prowl.notify(name,"Download started")
+ if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
+ logger.info(u"Sending Pushover notification")
+ prowl = notifiers.PUSHOVER()
+ prowl.notify(name,"Download started")
+ if headphones.PUSHBULLET_ENABLED and headphones.PUSHBULLET_ONSNATCH:
+ logger.info(u"Sending PushBullet notification")
+ pushbullet = notifiers.PUSHBULLET()
+ pushbullet.notify(name + " has been snatched!", "Download started")
+ if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
+ logger.info(u"Sending Twitter notification")
+ twitter = notifiers.TwitterNotifier()
+ twitter.notify_snatch(name)
+ if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
+ logger.info(u"Sending NMA notification")
+ nma = notifiers.NMA()
+ nma.notify(snatched_nzb=name)
+ if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
+ logger.info(u"Sending Pushalot notification")
+ pushalot = notifiers.PUSHALOT()
+ pushalot.notify(name,"Download started")
+ if headphones.OSX_NOTIFY_ENABLED and headphones.OSX_NOTIFY_ONSNATCH:
+ logger.info(u"Sending OS X notification")
+ osx_notify = notifiers.OSX_NOTIFY()
+ osx_notify.notify(artist, albumname, 'Snatched: ' + provider + '. ' + name)
+ if headphones.BOXCAR_ENABLED and headphones.BOXCAR_ONSNATCH:
+ logger.info(u"Sending Boxcar2 notification")
+ b2msg = 'From ' + provider + '
' + name
+ boxcar = notifiers.BOXCAR()
+ boxcar.notify('Headphones snatched: ' + title, b2msg, rgid)
+
def verifyresult(title, artistterm, term, lossless):
title = re.sub('[\.\-\/\_]', ' ', title)
@@ -862,7 +915,7 @@ def searchTorrent(album, new=False, losslessOnly=False):
minimumseeders = int(headphones.NUMBEROFSEEDERS) - 1
if headphones.KAT:
- provider = "Kick Ass Torrent"
+ provider = "Kick Ass Torrents"
providerurl = url_fix("http://kickass.to/usearch/" + term)
if headphones.PREFERRED_QUALITY == 3 or losslessOnly:
categories = "7" #music
@@ -1324,7 +1377,7 @@ def preprocess(resultlist):
#Get out of here if we're using Transmission or uTorrent
if headphones.TORRENT_DOWNLOADER != 0:
return True, result
- # get outta here if rutracker or piratebay
+ # get outta here if rutracker
if result[3] == 'rutracker.org':
return True, result
# Get out of here if it's a magnet link
@@ -1334,7 +1387,7 @@ def preprocess(resultlist):
# Download the torrent file
headers = {}
- if result[3] == 'Kick Ass Torrent':
+ if result[3] == 'Kick Ass Torrents':
headers['Referer'] = 'http://kat.ph/'
elif result[3] == 'What.cd':
headers['User-Agent'] = 'Headphones'
diff --git a/headphones/transmission.py b/headphones/transmission.py
index 012153ef..e2e321b3 100644
--- a/headphones/transmission.py
+++ b/headphones/transmission.py
@@ -49,31 +49,6 @@ def addTorrent(link):
retid = False
logger.info(u"Torrent sent to Transmission successfully")
- if headphones.GROWL_ENABLED and headphones.GROWL_ONSNATCH:
- logger.info(u"Sending Growl notification")
- growl = notifiers.GROWL()
- growl.notify(name,"Download started")
- if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
- logger.info(u"Sending Prowl notification")
- prowl = notifiers.PROWL()
- prowl.notify(name,"Download started")
- if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
- logger.info(u"Sending Pushover notification")
- pushover = notifiers.PUSHOVER()
- pushover.notify(name,"Download started")
- if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
- logger.info(u"Sending Twitter notification")
- twitter = notifiers.TwitterNotifier()
- twitter.notify_snatch(nzb.name)
- if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
- logger.info(u"Sending NMA notification")
- nma = notifiers.NMA()
- nma.notify(snatched_nzb=name)
- if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
- logger.info(u"Sending Pushalot notification")
- pushalot = notifiers.PUSHALOT()
- pushalot.notify(name,"Download started")
-
return retid
else:
diff --git a/headphones/webserve.py b/headphones/webserve.py
index 52d4f21b..fb27305f 100644
--- a/headphones/webserve.py
+++ b/headphones/webserve.py
@@ -1068,6 +1068,12 @@ class WebInterface(object):
"pushbullet_deviceid": headphones.PUSHBULLET_DEVICEID,
"twitter_enabled": checked(headphones.TWITTER_ENABLED),
"twitter_onsnatch": checked(headphones.TWITTER_ONSNATCH),
+ "osx_notify_enabled": checked(headphones.OSX_NOTIFY_ENABLED),
+ "osx_notify_onsnatch": checked(headphones.OSX_NOTIFY_ONSNATCH),
+ "osx_notify_app": headphones.OSX_NOTIFY_APP,
+ "boxcar_enabled": checked(headphones.BOXCAR_ENABLED),
+ "boxcar_onsnatch": checked(headphones.BOXCAR_ONSNATCH),
+ "boxcar_token": headphones.BOXCAR_TOKEN,
"mirror_list": headphones.MIRRORLIST,
"mirror": headphones.MIRROR,
"customhost": headphones.CUSTOMHOST,
@@ -1114,10 +1120,11 @@ class WebInterface(object):
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0,
delete_lossless_files=0, growl_enabled=0, growl_onsnatch=0, growl_host=None, growl_password=None, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None,
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
- pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, boxcar_enabled=0, boxcar_username=None, boxcar_onsnatch=0, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0, mirror=None, customhost=None, customport=None,
- customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None,
- enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None,
- plex_password=None, plex_update=0, plex_notify=0, songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, **kwargs):
+ pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
+ osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
+ preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None,
+ plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
+ songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, **kwargs):
headphones.HTTP_HOST = http_host
headphones.HTTP_PORT = http_port
@@ -1280,6 +1287,15 @@ class WebInterface(object):
headphones.SONGKICK_FILTER_ENABLED = songkick_filter_enabled
headphones.TWITTER_ENABLED = twitter_enabled
headphones.TWITTER_ONSNATCH = twitter_onsnatch
+
+ headphones.OSX_NOTIFY_ENABLED = osx_notify_enabled
+ headphones.OSX_NOTIFY_ONSNATCH = osx_notify_onsnatch
+ headphones.OSX_NOTIFY_APP = osx_notify_app
+
+ headphones.BOXCAR_ENABLED = boxcar_enabled
+ headphones.BOXCAR_ONSNATCH = boxcar_onsnatch
+ headphones.BOXCAR_TOKEN = boxcar_token
+
headphones.MIRROR = mirror
headphones.CUSTOMHOST = customhost
headphones.CUSTOMPORT = customport
@@ -1447,6 +1463,19 @@ class WebInterface(object):
return "Error sending tweet"
testTwitter.exposed = True
+ def osxnotifyregister(self, app):
+ cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
+ from lib.osxnotify import registerapp as osxnotify
+ result, msg = osxnotify.registerapp(app)
+ if result:
+ osx_notify = notifiers.OSX_NOTIFY()
+ osx_notify.notify('Registered', result, 'Success :-)')
+ logger.info('Registered %s, to re-register a different app, delete this app first' % result)
+ else:
+ logger.warn(msg)
+ return msg
+ osxnotifyregister.exposed = True
+
class Artwork(object):
def index(self):
return "Artwork"
diff --git a/lib/osxnotify/__init__.py b/lib/osxnotify/__init__.py
new file mode 100755
index 00000000..e69de29b
diff --git a/lib/osxnotify/appIcon.icns b/lib/osxnotify/appIcon.icns
new file mode 100755
index 00000000..caf5b293
Binary files /dev/null and b/lib/osxnotify/appIcon.icns differ
diff --git a/lib/osxnotify/registerapp.py b/lib/osxnotify/registerapp.py
new file mode 100644
index 00000000..25a1bb7f
--- /dev/null
+++ b/lib/osxnotify/registerapp.py
@@ -0,0 +1,135 @@
+#!/usr/bin/python
+
+import shutil
+import os
+import stat
+import platform
+import subprocess
+
+def registerapp(app):
+
+ # don't do any of this unless >= 10.8
+ v, _, _ = platform.mac_ver()
+ v = float('.'.join(v.split('.')[:2]))
+ if v < 10.8:
+ return None, 'Registering requires OS X version >= 10.8'
+
+ app_path = None
+
+ # check app bundle doesn't already exist
+ app_path = subprocess.check_output(['/usr/bin/mdfind', 'kMDItemCFBundleIdentifier == "ade.headphones.osxnotify"']).strip()
+ if app_path:
+ return app_path, 'App previously registered'
+
+ # check app doesn't already exist
+ app = app.strip()
+ if not app:
+ return None, 'Path/Application not entered'
+ if os.path.splitext(app)[1] == ".app":
+ app_path = app
+ else:
+ app_path = app + '.app'
+ if os.path.exists(app_path):
+ return None, 'App %s already exists, choose a different name' % app_path
+
+ # generate app
+ try:
+ os.mkdir(app_path)
+ os.mkdir(app_path + "/Contents")
+ os.mkdir(app_path + "/Contents/MacOS")
+ os.mkdir(app_path + "/Contents/Resources")
+ shutil.copy(os.path.join(os.path.dirname(__file__), "appIcon.icns"), app_path + "/Contents/Resources/")
+
+ version = "1.0.0"
+ bundleName = "OSXNotify"
+ bundleIdentifier = "ade.headphones.osxnotify"
+
+ f = open(app_path + "/Contents/Info.plist", "w")
+ f.write("""
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ main.py
+ CFBundleGetInfoString
+ %s
+ CFBundleIconFile
+ appIcon.icns
+ CFBundleIdentifier
+ %s
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ %s
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ %s
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ %s
+ NSAppleScriptEnabled
+ YES
+ NSMainNibFile
+ MainMenu
+ NSPrincipalClass
+ NSApplication
+
+
+""" % (bundleName + " " + version, bundleIdentifier, bundleName, bundleName + " " + version, version))
+ f.close()
+
+ f = open(app_path + "/Contents/PkgInfo", "w")
+ f.write("APPL????")
+ f.close()
+
+ f = open(app_path + "/Contents/MacOS/main.py", "w")
+ f.write("""#!/usr/bin/python
+
+objc = None
+
+def swizzle(cls, SEL, func):
+ old_IMP = cls.instanceMethodForSelector_(SEL)
+ def wrapper(self, *args, **kwargs):
+ return func(self, old_IMP, *args, **kwargs)
+ new_IMP = objc.selector(wrapper, selector=old_IMP.selector,
+ signature=old_IMP.signature)
+ objc.classAddMethod(cls, SEL, new_IMP)
+
+def notify(title, subtitle=None, text=None, sound=True):
+ global objc
+ objc = __import__("objc")
+ swizzle(objc.lookUpClass('NSBundle'),
+ b'bundleIdentifier',
+ swizzled_bundleIdentifier)
+ NSUserNotification = objc.lookUpClass('NSUserNotification')
+ NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
+ NSAutoreleasePool = objc.lookUpClass('NSAutoreleasePool')
+ pool = NSAutoreleasePool.alloc().init()
+ notification = NSUserNotification.alloc().init()
+ notification.setTitle_(title)
+ notification.setSubtitle_(subtitle)
+ notification.setInformativeText_(text)
+ notification.setSoundName_("NSUserNotificationDefaultSoundName")
+ notification_center = NSUserNotificationCenter.defaultUserNotificationCenter()
+ notification_center.deliverNotification_(notification)
+ del pool
+
+def swizzled_bundleIdentifier(self, original):
+ return 'ade.headphones.osxnotify'
+
+if __name__ == '__main__':
+ notify('Half Man Half Biscuit', 'Back in the DHSS', '99% Of Gargoyles Look Like Bob Todd')
+""")
+ f.close()
+
+ oldmode = os.stat(app_path + "/Contents/MacOS/main.py").st_mode
+ os.chmod(app_path + "/Contents/MacOS/main.py", oldmode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
+
+ return app_path, 'App registered'
+
+ except Exception, e:
+ return None, 'Error creating App %s. %s' % (app_path, e)
\ No newline at end of file