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

Comments are closed.