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:])