1# -*- coding: utf-8 -*- 2# 3# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com> 4# All rights reserved. 5# 6# This code is licensed under the MIT License. 7# 8# Permission is hereby granted, free of charge, to any person obtaining a copy 9# of this software and associated documentation files(the "Software"), to deal 10# in the Software without restriction, including without limitation the rights 11# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 12# copies of the Software, and to permit persons to whom the Software is 13# furnished to do so, subject to the following conditions : 14# 15# The above copyright notice and this permission notice shall be included in 16# all copies or substantial portions of the Software. 17# 18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24# THE SOFTWARE. 25 26from __future__ import absolute_import 27from __future__ import print_function 28 29import platform 30import subprocess 31import os 32 33from .NotifyBase import NotifyBase 34from ..common import NotifyImageSize 35from ..common import NotifyType 36from ..utils import parse_bool 37from ..AppriseLocale import gettext_lazy as _ 38 39# Default our global support flag 40NOTIFY_MACOSX_SUPPORT_ENABLED = False 41 42if platform.system() == 'Darwin': 43 # Check this is Mac OS X 10.8, or higher 44 major, minor = platform.mac_ver()[0].split('.')[:2] 45 46 # Toggle our enabled flag if verion is correct and executable 47 # found. This is done in such a way to provide verbosity to the 48 # end user so they know why it may or may not work for them. 49 NOTIFY_MACOSX_SUPPORT_ENABLED = \ 50 (int(major) > 10 or (int(major) == 10 and int(minor) >= 8)) 51 52 53class NotifyMacOSX(NotifyBase): 54 """ 55 A wrapper for the MacOS X terminal-notifier tool 56 57 Source: https://github.com/julienXX/terminal-notifier 58 """ 59 60 # Set our global enabled flag 61 enabled = NOTIFY_MACOSX_SUPPORT_ENABLED 62 63 requirements = { 64 # Define our required packaging in order to work 65 'details': _( 66 'Only works with Mac OS X 10.8 and higher. Additionally ' 67 ' requires that /usr/local/bin/terminal-notifier is locally ' 68 'accessible.') 69 } 70 71 # The default descriptive name associated with the Notification 72 service_name = _('MacOSX Notification') 73 74 # The services URL 75 service_url = 'https://github.com/julienXX/terminal-notifier' 76 77 # The default protocol 78 protocol = 'macosx' 79 80 # A URL that takes you to the setup/help of the specific protocol 81 setup_url = 'https://github.com/caronc/apprise/wiki/Notify_macosx' 82 83 # Allows the user to specify the NotifyImageSize object 84 image_size = NotifyImageSize.XY_128 85 86 # Disable throttle rate for MacOSX requests since they are normally 87 # local anyway 88 request_rate_per_sec = 0 89 90 # Limit results to just the first 10 line otherwise there is just to much 91 # content to display 92 body_max_line_count = 10 93 94 # The path to the terminal-notifier 95 notify_path = '/usr/local/bin/terminal-notifier' 96 97 # Define object templates 98 templates = ( 99 '{schema}://', 100 ) 101 102 # Define our template arguments 103 template_args = dict(NotifyBase.template_args, **{ 104 'image': { 105 'name': _('Include Image'), 106 'type': 'bool', 107 'default': True, 108 'map_to': 'include_image', 109 }, 110 # Play the NAME sound when the notification appears. 111 # Sound names are listed in Sound Preferences. 112 # Use 'default' for the default sound. 113 'sound': { 114 'name': _('Sound'), 115 'type': 'string', 116 }, 117 }) 118 119 def __init__(self, sound=None, include_image=True, **kwargs): 120 """ 121 Initialize MacOSX Object 122 """ 123 124 super(NotifyMacOSX, self).__init__(**kwargs) 125 126 # Track whether or not we want to send an image with our notification 127 # or not. 128 self.include_image = include_image 129 130 # Set sound object (no q/a for now) 131 self.sound = sound 132 return 133 134 def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): 135 """ 136 Perform MacOSX Notification 137 """ 138 139 if not os.access(self.notify_path, os.X_OK): 140 self.logger.warning( 141 "MacOSX Notifications require '{}' to be in place." 142 .format(self.notify_path)) 143 return False 144 145 # Start with our notification path 146 cmd = [ 147 self.notify_path, 148 '-message', body, 149 ] 150 151 # Title is an optional switch 152 if title: 153 cmd.extend(['-title', title]) 154 155 # The sound to play 156 if self.sound: 157 cmd.extend(['-sound', self.sound]) 158 159 # Support any defined images if set 160 image_path = None if not self.include_image \ 161 else self.image_url(notify_type) 162 if image_path: 163 cmd.extend(['-appIcon', image_path]) 164 165 # Always call throttle before any remote server i/o is made 166 self.throttle() 167 168 # Capture some output for helpful debugging later on 169 self.logger.debug('MacOSX CMD: {}'.format(' '.join(cmd))) 170 171 # Send our notification 172 output = subprocess.Popen( 173 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 174 175 # Wait for process to complete 176 output.wait() 177 178 if output.returncode: 179 self.logger.warning('Failed to send MacOSX notification.') 180 self.logger.exception('MacOSX Exception') 181 return False 182 183 self.logger.info('Sent MacOSX notification.') 184 return True 185 186 def url(self, privacy=False, *args, **kwargs): 187 """ 188 Returns the URL built dynamically based on specified arguments. 189 """ 190 191 # Define any URL parametrs 192 params = { 193 'image': 'yes' if self.include_image else 'no', 194 } 195 196 # Extend our parameters 197 params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) 198 199 if self.sound: 200 # Store our sound 201 params['sound'] = self.sound 202 203 return '{schema}://_/?{params}'.format( 204 schema=self.protocol, 205 params=NotifyMacOSX.urlencode(params), 206 ) 207 208 @staticmethod 209 def parse_url(url): 210 """ 211 There are no parameters nessisary for this protocol; simply having 212 gnome:// is all you need. This function just makes sure that 213 is in place. 214 215 """ 216 217 results = NotifyBase.parse_url(url, verify_host=False) 218 219 # Include images with our message 220 results['include_image'] = \ 221 parse_bool(results['qsd'].get('image', True)) 222 223 # Support 'sound' 224 if 'sound' in results['qsd'] and len(results['qsd']['sound']): 225 results['sound'] = NotifyMacOSX.unquote(results['qsd']['sound']) 226 227 return results 228