mirror of
https://github.com/rembo10/headphones.git
synced 2026-07-21 16:34:01 +01:00
Boxcar2, OS X Notifications
- OS X Notifications require an application bundle, allow user to create a dummy app in a specified location and then use bundle when notifying - Boxcar2 - Moved snatched notify logic to searcher so we can pass extra info incl provider
This commit is contained in:
@@ -690,8 +690,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<h3>Pushalot</h3>
|
<h3>Pushalot</h3>
|
||||||
<div class="checkbox row">
|
<div class="checkbox row">
|
||||||
@@ -708,6 +707,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<h3>Synology NAS</h3>
|
<h3>Synology NAS</h3>
|
||||||
<div class="checkbox row">
|
<div class="checkbox row">
|
||||||
@@ -776,7 +779,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</td>
|
|
||||||
|
<fieldset>
|
||||||
|
<h3>OS X</h3>
|
||||||
|
<div class="row checkbox">
|
||||||
|
<input type="checkbox" name="osx_notify_enabled" id="osx_notify" value="1" ${config['osx_notify_enabled']} /><label>Enable OS X Notifications</label>
|
||||||
|
</div>
|
||||||
|
<div id="osx_notify_options">
|
||||||
|
<div class="row">
|
||||||
|
<small>Enter the path/application name to be registered with the Notification Center, default is /Applications/Headphones</small>
|
||||||
|
<input type="text" id="osx_notify_reg" name="osx_notify_app" value="${config['osx_notify_app']}" size="50"><label>Register Notify App</label>
|
||||||
|
<input type="button" value="Register" id="osxnotifyregister">
|
||||||
|
</div>
|
||||||
|
<div class="row checkbox">
|
||||||
|
<input type="checkbox" name="osx_notify_onsnatch" value="1" ${config['osx_notify_onsnatch']} /><label>Notify on snatch?</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<h3>Boxcar2</h3>
|
||||||
|
<div class="row checkbox">
|
||||||
|
<input type="checkbox" name="boxcar_enabled" id="boxcar" value="1" ${config['boxcar_enabled']} /><label>Enable Boxcar2 Notifications</label>
|
||||||
|
</div>
|
||||||
|
<div id="boxcar_options">
|
||||||
|
<div class="row">
|
||||||
|
<label>Access Token</label><input type="text" name="boxcar_token" value="${config['boxcar_token']}" size="35">
|
||||||
|
</div>
|
||||||
|
<div class="row checkbox">
|
||||||
|
<input type="checkbox" name="boxcar_onsnatch" value="1" ${config['boxcar_onsnatch']} /><label>Notify on snatch?</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<input type="button" class="configsubmit" value="Save Changes" onclick="doAjaxCall('configUpdate',$(this),'tabs',true);return false;" data-success="Changes saved successfully">
|
<input type="button" class="configsubmit" value="Save Changes" onclick="doAjaxCall('configUpdate',$(this),'tabs',true);return false;" data-success="Changes saved successfully">
|
||||||
@@ -1458,7 +1494,47 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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();
|
$("#songkickoptions").show();
|
||||||
}
|
}
|
||||||
@@ -1671,6 +1747,12 @@
|
|||||||
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
$('#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("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||||
|
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|||||||
+27
-1
@@ -270,6 +270,12 @@ TWITTER_ONSNATCH = False
|
|||||||
TWITTER_USERNAME = None
|
TWITTER_USERNAME = None
|
||||||
TWITTER_PASSWORD = None
|
TWITTER_PASSWORD = None
|
||||||
TWITTER_PREFIX = 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"]
|
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
||||||
MIRROR = None
|
MIRROR = None
|
||||||
CUSTOMHOST = None
|
CUSTOMHOST = None
|
||||||
@@ -349,7 +355,7 @@ def initialize():
|
|||||||
INTERFACE, FOLDER_PERMISSIONS, FILE_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \
|
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, \
|
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, \
|
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, \
|
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, \
|
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
|
||||||
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
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, \
|
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||||
@@ -386,6 +392,8 @@ def initialize():
|
|||||||
CheckSection('Pushalot')
|
CheckSection('Pushalot')
|
||||||
CheckSection('Synoindex')
|
CheckSection('Synoindex')
|
||||||
CheckSection('Twitter')
|
CheckSection('Twitter')
|
||||||
|
CheckSection('OSX_Notify')
|
||||||
|
CheckSection('Boxcar')
|
||||||
CheckSection('Songkick')
|
CheckSection('Songkick')
|
||||||
CheckSection('Advanced')
|
CheckSection('Advanced')
|
||||||
|
|
||||||
@@ -610,6 +618,14 @@ def initialize():
|
|||||||
TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '')
|
TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '')
|
||||||
TWITTER_PREFIX = check_setting_str(CFG, 'Twitter', 'twitter_prefix', 'Headphones')
|
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_ENABLED = bool(check_setting_int(CFG, 'Songkick', 'songkick_enabled', 1))
|
||||||
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
|
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
|
||||||
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
|
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
|
||||||
@@ -1025,6 +1041,16 @@ def config_write():
|
|||||||
new_config['Twitter']['twitter_password'] = TWITTER_PASSWORD
|
new_config['Twitter']['twitter_password'] = TWITTER_PASSWORD
|
||||||
new_config['Twitter']['twitter_prefix'] = TWITTER_PREFIX
|
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'] = {}
|
||||||
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
|
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
|
||||||
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
|
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
|
||||||
|
|||||||
+96
-13
@@ -238,6 +238,7 @@ class XBMC:
|
|||||||
|
|
||||||
except:
|
except:
|
||||||
logger.warn('Error sending notification request to XBMC')
|
logger.warn('Error sending notification request to XBMC')
|
||||||
|
|
||||||
class LMS:
|
class LMS:
|
||||||
|
|
||||||
#Class for updating a Logitech Media Server
|
#Class for updating a Logitech Media Server
|
||||||
@@ -271,9 +272,9 @@ class LMS:
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
|
||||||
#Send the ["rescan"] command to an LMS server.
|
#Send the ["rescan"] command to an LMS server.
|
||||||
#Note that the command must be prefixed with the 'player' that the command is aimed at,
|
#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 ""
|
#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(',')]
|
hosts = [x.strip() for x in self.hosts.split(',')]
|
||||||
|
|
||||||
@@ -361,12 +362,12 @@ class Plex:
|
|||||||
for host in hosts:
|
for host in hosts:
|
||||||
logger.info('Sending notification command to Plex Media Server @ '+host)
|
logger.info('Sending notification command to Plex Media Server @ '+host)
|
||||||
try:
|
try:
|
||||||
notification = header + "," + message + "," + time + "," + albumartpath
|
notification = header + "," + message + "," + time + "," + albumartpath
|
||||||
notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification('+notification+')'}
|
notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification('+notification+')'}
|
||||||
request = self._sendhttp(host, notifycommand)
|
request = self._sendhttp(host, notifycommand)
|
||||||
|
|
||||||
if not request:
|
if not request:
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
except:
|
except:
|
||||||
logger.warn('Error sending notification request to Plex Media Server')
|
logger.warn('Error sending notification request to Plex Media Server')
|
||||||
@@ -454,18 +455,17 @@ class PUSHBULLET:
|
|||||||
|
|
||||||
self.notify('Main Screen Activate', 'Test Message')
|
self.notify('Main Screen Activate', 'Test Message')
|
||||||
|
|
||||||
|
|
||||||
class PUSHALOT:
|
class PUSHALOT:
|
||||||
|
|
||||||
def notify(self, message, event):
|
def notify(self, message, event):
|
||||||
if not headphones.PUSHALOT_ENABLED:
|
if not headphones.PUSHALOT_ENABLED:
|
||||||
return
|
return
|
||||||
|
|
||||||
pushalot_authorizationtoken = headphones.PUSHALOT_APIKEY
|
pushalot_authorizationtoken = headphones.PUSHALOT_APIKEY
|
||||||
|
|
||||||
logger.debug(u"Pushalot event: " + event)
|
logger.debug(u"Pushalot event: " + event)
|
||||||
logger.debug(u"Pushalot message: " + message)
|
logger.debug(u"Pushalot message: " + message)
|
||||||
logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
|
logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
|
||||||
|
|
||||||
http_handler = HTTPSConnection("pushalot.com")
|
http_handler = HTTPSConnection("pushalot.com")
|
||||||
|
|
||||||
@@ -529,6 +529,7 @@ class Synoindex:
|
|||||||
if isinstance(path_list, list):
|
if isinstance(path_list, list):
|
||||||
for path in path_list:
|
for path in path_list:
|
||||||
self.notify(path)
|
self.notify(path)
|
||||||
|
|
||||||
class PUSHOVER:
|
class PUSHOVER:
|
||||||
|
|
||||||
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
||||||
@@ -694,3 +695,85 @@ class TwitterNotifier:
|
|||||||
return self._send_tweet(prefix+": "+message)
|
return self._send_tweet(prefix+": "+message)
|
||||||
|
|
||||||
notifier = TwitterNotifier
|
notifier = TwitterNotifier
|
||||||
|
|
||||||
|
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 __init__(self):
|
||||||
|
|
||||||
|
self.url = 'https://new.boxcar.io/api/notifications'
|
||||||
|
|
||||||
|
def notify(self, title, message):
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = urllib.urlencode({
|
||||||
|
'user_credentials': headphones.BOXCAR_TOKEN,
|
||||||
|
'notification[title]': title.encode('utf-8'),
|
||||||
|
'notification[long_message]': message.encode('utf-8'),
|
||||||
|
'notification[sound]': "done"
|
||||||
|
})
|
||||||
|
|
||||||
|
req = urllib2.Request(self.url)
|
||||||
|
handle = urllib2.urlopen(req, data)
|
||||||
|
handle.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
except urllib2.URLError, e:
|
||||||
|
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
||||||
|
return False
|
||||||
|
|||||||
@@ -474,6 +474,17 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
twitter = notifiers.TwitterNotifier()
|
twitter = notifiers.TwitterNotifier()
|
||||||
twitter.notify_download(pushmessage)
|
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")
|
||||||
|
|
||||||
def embedAlbumArt(artwork, downloaded_track_list):
|
def embedAlbumArt(artwork, downloaded_track_list):
|
||||||
logger.info('Embedding album art')
|
logger.info('Embedding album art')
|
||||||
|
|
||||||
|
|||||||
@@ -119,35 +119,6 @@ def sendNZB(nzb):
|
|||||||
|
|
||||||
if sabText == "ok":
|
if sabText == "ok":
|
||||||
logger.info(u"NZB sent to SAB successfully")
|
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.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
|
return True
|
||||||
elif sabText == "Missing authentication":
|
elif sabText == "Missing authentication":
|
||||||
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
||||||
|
|||||||
+57
-6
@@ -31,7 +31,7 @@ import subprocess
|
|||||||
import headphones
|
import headphones
|
||||||
from headphones.common import USER_AGENT
|
from headphones.common import USER_AGENT
|
||||||
from headphones import logger, db, helpers, classes, sab, nzbget, request
|
from headphones import logger, db, helpers, classes, sab, nzbget, request
|
||||||
from headphones import transmission
|
from headphones import transmission, notifiers
|
||||||
|
|
||||||
import lib.bencode as bencode
|
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")
|
logger.info("Album type is audiobook/spokenword. Using audiobook category")
|
||||||
|
|
||||||
for newznab_host in newznab_hosts:
|
for newznab_host in newznab_hosts:
|
||||||
|
|
||||||
|
provider = newznab_host[0]
|
||||||
|
|
||||||
# Add a little mod for kere.ws
|
# Add a little mod for kere.ws
|
||||||
if newznab_host[0] == "http://kere.ws":
|
if newznab_host[0] == "http://kere.ws":
|
||||||
if categories == "3040":
|
if categories == "3040":
|
||||||
@@ -573,14 +576,16 @@ def send_to_downloader(data, bestqual, album):
|
|||||||
nzb = classes.NZBDataSearchResult()
|
nzb = classes.NZBDataSearchResult()
|
||||||
nzb.extraInfo.append(data)
|
nzb.extraInfo.append(data)
|
||||||
nzb.name = folder_name
|
nzb.name = folder_name
|
||||||
nzbget.sendNZB(nzb)
|
if not nzbget.sendNZB(nzb):
|
||||||
|
return
|
||||||
|
|
||||||
elif headphones.NZB_DOWNLOADER == 0:
|
elif headphones.NZB_DOWNLOADER == 0:
|
||||||
|
|
||||||
nzb = classes.NZBDataSearchResult()
|
nzb = classes.NZBDataSearchResult()
|
||||||
nzb.extraInfo.append(data)
|
nzb.extraInfo.append(data)
|
||||||
nzb.name = folder_name
|
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
|
# 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()
|
(replace_spaces, replace_dots) = sab.checkConfig()
|
||||||
@@ -695,6 +700,52 @@ def send_to_downloader(data, bestqual, album):
|
|||||||
myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [album['AlbumID']])
|
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])
|
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]
|
||||||
|
album = album[2]
|
||||||
|
title = artist + ' - ' + album
|
||||||
|
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, album, 'Snatched: ' + provider + '. ' + name)
|
||||||
|
if headphones.BOXCAR_ENABLED and headphones.BOXCAR_ONSNATCH:
|
||||||
|
logger.info(u"Sending Boxcar2 notification")
|
||||||
|
boxcar = notifiers.BOXCAR()
|
||||||
|
boxcar.notify('Headphones snatched: ' + title, 'From ' + provider + '. ' + name)
|
||||||
|
|
||||||
def verifyresult(title, artistterm, term, lossless):
|
def verifyresult(title, artistterm, term, lossless):
|
||||||
|
|
||||||
title = re.sub('[\.\-\/\_]', ' ', title)
|
title = re.sub('[\.\-\/\_]', ' ', title)
|
||||||
@@ -862,7 +913,7 @@ def searchTorrent(album, new=False, losslessOnly=False):
|
|||||||
minimumseeders = int(headphones.NUMBEROFSEEDERS) - 1
|
minimumseeders = int(headphones.NUMBEROFSEEDERS) - 1
|
||||||
|
|
||||||
if headphones.KAT:
|
if headphones.KAT:
|
||||||
provider = "Kick Ass Torrent"
|
provider = "Kick Ass Torrents"
|
||||||
providerurl = url_fix("http://kickass.to/usearch/" + term)
|
providerurl = url_fix("http://kickass.to/usearch/" + term)
|
||||||
if headphones.PREFERRED_QUALITY == 3 or losslessOnly:
|
if headphones.PREFERRED_QUALITY == 3 or losslessOnly:
|
||||||
categories = "7" #music
|
categories = "7" #music
|
||||||
@@ -1324,7 +1375,7 @@ def preprocess(resultlist):
|
|||||||
#Get out of here if we're using Transmission or uTorrent
|
#Get out of here if we're using Transmission or uTorrent
|
||||||
if headphones.TORRENT_DOWNLOADER != 0:
|
if headphones.TORRENT_DOWNLOADER != 0:
|
||||||
return True, result
|
return True, result
|
||||||
# get outta here if rutracker or piratebay
|
# get outta here if rutracker
|
||||||
if result[3] == 'rutracker.org':
|
if result[3] == 'rutracker.org':
|
||||||
return True, result
|
return True, result
|
||||||
# Get out of here if it's a magnet link
|
# Get out of here if it's a magnet link
|
||||||
@@ -1334,7 +1385,7 @@ def preprocess(resultlist):
|
|||||||
# Download the torrent file
|
# Download the torrent file
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
||||||
if result[3] == 'Kick Ass Torrent':
|
if result[3] == 'Kick Ass Torrents':
|
||||||
headers['Referer'] = 'http://kat.ph/'
|
headers['Referer'] = 'http://kat.ph/'
|
||||||
elif result[3] == 'What.cd':
|
elif result[3] == 'What.cd':
|
||||||
headers['User-Agent'] = 'Headphones'
|
headers['User-Agent'] = 'Headphones'
|
||||||
|
|||||||
@@ -49,31 +49,6 @@ def addTorrent(link):
|
|||||||
retid = False
|
retid = False
|
||||||
|
|
||||||
logger.info(u"Torrent sent to Transmission successfully")
|
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
|
return retid
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
+33
-4
@@ -1059,6 +1059,12 @@ class WebInterface(object):
|
|||||||
"pushbullet_deviceid": headphones.PUSHBULLET_DEVICEID,
|
"pushbullet_deviceid": headphones.PUSHBULLET_DEVICEID,
|
||||||
"twitter_enabled": checked(headphones.TWITTER_ENABLED),
|
"twitter_enabled": checked(headphones.TWITTER_ENABLED),
|
||||||
"twitter_onsnatch": checked(headphones.TWITTER_ONSNATCH),
|
"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_list": headphones.MIRRORLIST,
|
||||||
"mirror": headphones.MIRROR,
|
"mirror": headphones.MIRROR,
|
||||||
"customhost": headphones.CUSTOMHOST,
|
"customhost": headphones.CUSTOMHOST,
|
||||||
@@ -1105,10 +1111,11 @@ class WebInterface(object):
|
|||||||
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0,
|
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,
|
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,
|
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, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0, mirror=None, customhost=None, customport=None,
|
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,
|
||||||
customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None,
|
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,
|
||||||
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,
|
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_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):
|
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_HOST = http_host
|
||||||
headphones.HTTP_PORT = http_port
|
headphones.HTTP_PORT = http_port
|
||||||
@@ -1268,6 +1275,15 @@ class WebInterface(object):
|
|||||||
headphones.SONGKICK_FILTER_ENABLED = songkick_filter_enabled
|
headphones.SONGKICK_FILTER_ENABLED = songkick_filter_enabled
|
||||||
headphones.TWITTER_ENABLED = twitter_enabled
|
headphones.TWITTER_ENABLED = twitter_enabled
|
||||||
headphones.TWITTER_ONSNATCH = twitter_onsnatch
|
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.MIRROR = mirror
|
||||||
headphones.CUSTOMHOST = customhost
|
headphones.CUSTOMHOST = customhost
|
||||||
headphones.CUSTOMPORT = customport
|
headphones.CUSTOMPORT = customport
|
||||||
@@ -1435,6 +1451,19 @@ class WebInterface(object):
|
|||||||
return "Error sending tweet"
|
return "Error sending tweet"
|
||||||
testTwitter.exposed = True
|
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):
|
class Artwork(object):
|
||||||
def index(self):
|
def index(self):
|
||||||
return "Artwork"
|
return "Artwork"
|
||||||
|
|||||||
Executable
Executable
BIN
Binary file not shown.
@@ -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("""<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>main.py</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>%s</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>appIcon.icns</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>%s</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>%s</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>%s</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>%s</string>
|
||||||
|
<key>NSAppleScriptEnabled</key>
|
||||||
|
<string>YES</string>
|
||||||
|
<key>NSMainNibFile</key>
|
||||||
|
<string>MainMenu</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
""" % (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)
|
||||||
Reference in New Issue
Block a user