MacOS Notifications

replaced unreliable method
This commit is contained in:
AdeHub
2025-08-06 15:42:39 +12:00
parent 0ac76fbebc
commit b039492072
9 changed files with 13 additions and 245 deletions

View File

@@ -1145,18 +1145,9 @@
<fieldset>
<div class="row checkbox left">
<input type="checkbox" class="bigcheck" name="osx_notify_enabled" id="osx_notify" value="1" ${config['osx_notify_enabled']} /><label for="osx_notify"><span class="option">OS X</span></label>
<input type="checkbox" class="bigcheck" name="osx_notify_enabled" id="osx_notify" value="1" ${config['osx_notify_enabled']} /><label for="osx_notify"><span class="option">MacOS</span></label>
</div>
<div id="osx_notify_options">
<div class="row">
<input type="text" id="osx_notify_reg" name="osx_notify_app" value="${config['osx_notify_app']}" size="50"><label>Register Notify App</label>
</div>
<div class="row">
<small>Enter the path/application name to be registered with the Notification Center, default is /Applications/Headphones</small>
</div>
<div class="row">
<input type="button" value="Register" id="osxnotifyregister"><label></label>
</div>
<div class="row checkbox">
<input type="checkbox" name="osx_notify_onsnatch" value="1" ${config['osx_notify_onsnatch']} /><label>Notify on snatch?</label>
</div>

View File

@@ -197,7 +197,6 @@ _CONFIG_DEFINITIONS = {
'OMGWTFNZBS_UID': (str, 'omgwtfnzbs', ''),
'OPEN_MAGNET_LINKS': (int, 'General', 0), # 0: Ignore, 1: Open, 2: Convert, 3: Embed (rtorrent)
'MAGNET_LINKS': (int, 'General', 0),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/Headphones'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ONSNATCH': (int, 'OSX_Notify', 0),
'PIRATEBAY': (int, 'Piratebay', 0),

View File

@@ -840,78 +840,14 @@ class TwitterNotifier(object):
class OSX_NOTIFY(object):
def __init__(self):
def notify(self, title, subtitle):
try:
self.objc = __import__("objc")
self.AppKit = __import__("AppKit")
except:
logger.warn('OS X Notification: Cannot import objc or AppKit')
pass
def swizzle(self, cls, SEL, func):
old_IMP = getattr(cls, SEL, None)
if old_IMP is None:
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.encode(), new_IMP)
def notify(self, title, subtitle=None, text=None, sound=True, image=None):
try:
self.swizzle(
self.objc.lookUpClass('NSBundle'),
'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")
if image:
source_img = self.AppKit.NSImage.alloc().\
initByReferencingFile_(image)
notification.setContentImage_(source_img)
# notification.set_identityImage_(source_img)
notification.setHasActionButton_(False)
notification_center = NSUserNotificationCenter.\
defaultUserNotificationCenter()
notification_center.deliverNotification_(notification)
del pool
return True
script = f'display notification "{subtitle}" with title "{title}"'
subprocess.run(["osascript", "-e", script])
except Exception as e:
logger.warn('Error sending OS X Notification: %s' % e)
logger.warn(f"Error sending MacOS Notification: {e}")
return False
def swizzled_bundleIdentifier(self, original, swizzled):
return 'ade.headphones.osxnotify'
class BOXCAR(object):
def __init__(self):

View File

@@ -623,15 +623,9 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
#twitter.notify_download(pushmessage)
if headphones.CONFIG.OSX_NOTIFY_ENABLED:
from headphones import cache
c = cache.Cache()
album_art = c.get_artwork_from_cache(None, release['AlbumID'])
logger.info("Sending OS X notification")
osx_notify = notifiers.OSX_NOTIFY()
osx_notify.notify(release['ArtistName'],
release['AlbumTitle'],
statusmessage,
image=album_art)
logger.info("Sending MacOS notification")
osx = notifiers.OSX_NOTIFY()
osx.notify(f"Headphones Processed", f"{pushmessage}\n{statusmessage}")
if headphones.CONFIG.BOXCAR_ENABLED:
logger.info("Sending Boxcar2 notification")

View File

@@ -1228,16 +1228,12 @@ def send_to_downloader(data, result, album):
logger.info("Sending Pushalot notification")
pushalot = notifiers.PUSHALOT()
pushalot.notify(name, "Download started")
if headphones.CONFIG.OSX_NOTIFY_ENABLED and headphones.CONFIG.OSX_NOTIFY_ONSNATCH:
from headphones import cache
c = cache.Cache()
album_art = c.get_artwork_from_cache(None, rgid)
logger.info("Sending OS X notification")
osx_notify = notifiers.OSX_NOTIFY()
osx_notify.notify(artist,
albumname,
'Snatched: ' + provider + '. ' + name,
image=album_art)
logger.info("Sending MacOS notification")
osx = notifiers.OSX_NOTIFY()
osx.notify(f"Headphones Snatched", f"{artist} - {albumname}\nFrom {provider}, {name}")
if headphones.CONFIG.BOXCAR_ENABLED and headphones.CONFIG.BOXCAR_ONSNATCH:
logger.info("Sending Boxcar2 notification")
b2msg = 'From ' + provider + '<br></br>' + name

View File

@@ -1369,7 +1369,6 @@ class WebInterface(object):
"twitter_onsnatch": checked(headphones.CONFIG.TWITTER_ONSNATCH),
"osx_notify_enabled": checked(headphones.CONFIG.OSX_NOTIFY_ENABLED),
"osx_notify_onsnatch": checked(headphones.CONFIG.OSX_NOTIFY_ONSNATCH),
"osx_notify_app": headphones.CONFIG.OSX_NOTIFY_APP,
"boxcar_enabled": checked(headphones.CONFIG.BOXCAR_ENABLED),
"boxcar_onsnatch": checked(headphones.CONFIG.BOXCAR_ONSNATCH),
"boxcar_token": headphones.CONFIG.BOXCAR_TOKEN,
@@ -1718,20 +1717,6 @@ class WebInterface(object):
else:
return "Error sending tweet"
@cherrypy.expose
def osxnotifyregister(self, app):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
from 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
@cherrypy.expose
def testPushover(self):
logger.info("Sending Pushover notification")

Binary file not shown.

View File

@@ -1,133 +0,0 @@
#!/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
if not [int(n) for n in platform.mac_ver()[0].split('.')] >= [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 as e:
return None, 'Error creating App %s. %s' % (app_path, e)