6-6-12 – This script is really old and may not work with current versions of Growl. There are some good libraries to choose from and I’d recommend looking at them instead.
Updated 12-4-10 – minor code updates
I have yet another script. This one is actually my first one in Python, which is kind of sad after all the good things I’ve heard about the language. This time I didn’t actually write the entire script – I simply added to one that had already been written to give it more functionality.
The original script was found here: http://the.taoofmac.com/space/Projects/netgrowl and almost all credit goes to it’s author, as I only added a little bit more to it.
What this version does is send a Growl message over UDP to a Mac (running Growl and configured to listen for incoming notifications) using Python. I thought this was really cool – I could script my remote Linux machines (or whatever OS) to send messages to my main Mac.
What I added was the ability to have command line arguments so that it would be easier to change options on the fly and also easier to script.
Running ./netgrowl.py -h
will give you the following:
#!/usr/bin/env python # Updated 12-4-2010 # Altered 1-17-2010 - Tanner Stokes - www.tannr.com # Added support for command line arguments # ORIGINAL CREDITS # """Growl 0.6 Network Protocol Client for Python""" # __version__ = "0.6.3" # __author__ = "Rui Carmo (http://the.taoofmac.com)" # __copyright__ = "(C) 2004 Rui Carmo. Code under BSD License." # __contributors__ = "Ingmar J Stein (Growl Team), John Morrissey (hashlib patch)" try: import hashlib md5_constructor = hashlib.md5 except ImportError: import md5 md5_constructor = md5.new import struct # needed for command line arguments import sys import getopt from socket import AF_INET, SOCK_DGRAM, socket GROWL_UDP_PORT=9887 GROWL_PROTOCOL_VERSION=1 GROWL_TYPE_REGISTRATION=0 GROWL_TYPE_NOTIFICATION=1 def main(argv): # default to sending to localhost host = "localhost" # default title title = "Title" # default description description = "Description" # default priority priority = 0 # default stickiness sticky = False # if no arguments are given, show usage if(len(argv) < 1): usage() try: opts, args = getopt.getopt(argv, "hH:t:d:p:s") except getopt.GetoptError: usage() for opt, arg in opts: if opt in ("-h"): usage() elif opt in ("-H"): host = arg elif opt in ("-t"): title = arg elif opt in ("-d"): description = arg elif opt in ("-p"): # acceptable values: -2 to 2 priority = int(arg) elif opt in ("-s"): sticky = True # connect up to Growl server machine addr = (host, GROWL_UDP_PORT) s = socket(AF_INET,SOCK_DGRAM) # register application with remote Growl p = GrowlRegistrationPacket() p.addNotification() # send registration packet s.sendto(p.payload(), addr) # assemble notification packet p = GrowlNotificationPacket(title=title, description=description, priority=priority, sticky=sticky) # send notification packet s.sendto(p.payload(), addr) s.close() def usage(): print """Usage: ./netgrowl.py [-hs] [-H hostname] [-t title] [-d description] [-p priority] Send Growl messages over UDP -h help -H specify host -t title -d description -p priority [-2 to 2] -s make sticky""" sys.exit(1) class GrowlRegistrationPacket: """Builds a Growl Network Registration packet. Defaults to emulating the command-line growlnotify utility.""" def __init__(self, application="NetGrowl", password = None ): self.notifications = [] self.defaults = [] # array of indexes into notifications self.application = application.encode("utf-8") self.password = password # end def def addNotification(self, notification="Command-Line Growl Notification", enabled=True): """Adds a notification type and sets whether it is enabled on the GUI""" self.notifications.append(notification) if enabled: self.defaults.append(len(self.notifications)-1) # end def def payload(self): """Returns the packet payload.""" self.data = struct.pack( "!BBH", GROWL_PROTOCOL_VERSION, GROWL_TYPE_REGISTRATION, len(self.application) ) self.data += struct.pack( "BB", len(self.notifications), len(self.defaults) ) self.data += self.application for notification in self.notifications: encoded = notification.encode("utf-8") self.data += struct.pack("!H", len(encoded)) self.data += encoded for default in self.defaults: self.data += struct.pack("B", default) self.checksum = md5_constructor() self.checksum.update(self.data) if self.password: self.checksum.update(self.password) self.data += self.checksum.digest() return self.data # end def # end class class GrowlNotificationPacket: """Builds a Growl Network Notification packet. Defaults to emulating the command-line growlnotify utility.""" def __init__(self, application="NetGrowl", notification="Command-Line Growl Notification", title="Title", description="Description", priority = 0, sticky = False, password = None ): self.application = application.encode("utf-8") self.notification = notification.encode("utf-8") self.title = title.encode("utf-8") self.description = description.encode("utf-8") flags = (priority & 0x07) * 2 if priority < 0: flags |= 0x08 if sticky: flags = flags | 0x0100 self.data = struct.pack( "!BBHHHHH", GROWL_PROTOCOL_VERSION, GROWL_TYPE_NOTIFICATION, flags, len(self.notification), len(self.title), len(self.description), len(self.application) ) self.data += self.notification self.data += self.title self.data += self.description self.data += self.application self.checksum = md5_constructor() self.checksum.update(self.data) if password: self.checksum.update(password) self.data += self.checksum.digest() # end def def payload(self): """Returns the packet payload.""" return self.data # end def # end class if __name__ == '__main__': # send command line arguments to main() function main(sys.argv[1:])