add prowl send support and experimental (as in untested) snarl send/receive support
authorMoritz Venn <ritzmo@users.schwerkraft.elitedvb.net>
Sat, 16 Jan 2010 20:32:59 +0000 (20:32 +0000)
committerMoritz Venn <ritzmo@users.schwerkraft.elitedvb.net>
Sat, 16 Jan 2010 20:32:59 +0000 (20:32 +0000)
warning: if you use prowl your api key may end up in a crashlog, so try not to have crashes :D

growlee/src/plugin.py

index bbf5869..ad4ddc0 100644 (file)
@@ -5,15 +5,17 @@ from netgrowl import GrowlRegistrationPacket, GrowlNotificationPacket, \
                GROWL_UDP_PORT, md5_constructor
 from twisted.internet.protocol import DatagramProtocol
 from twisted.internet import reactor
+from twisted.web.client import getPage
 from struct import unpack
 from socket import gaierror
+from urllib import urlencode
 
 from Screens.Setup import SetupSummary
 from Screens.Screen import Screen
 from Screens.MessageBox import MessageBox
 from Components.ActionMap import ActionMap
 from Components.config import config, getConfigListEntry, ConfigSubsection, \
-               ConfigText, ConfigPassword, ConfigYesNo
+               ConfigText, ConfigPassword, ConfigYesNo, ConfigSelection
 from Components.ConfigList import ConfigListScreen
 from Components.Sources.StaticText import StaticText
 
@@ -22,6 +24,8 @@ config.plugins.growlee.enable_incoming = ConfigYesNo(default=False)
 config.plugins.growlee.enable_outgoing = ConfigYesNo(default=False)
 config.plugins.growlee.address = ConfigText(fixed_size=False)
 config.plugins.growlee.password = ConfigPassword()
+config.plugins.growlee.prowl_api_key = ConfigText(fixed_size=False)
+config.plugins.growlee.protocol = ConfigSelection(default="growl", choices = [("growl", "Growl"), ("snarl", "Snarl"), ("prowl", "Prowl")])
 
 NOTIFICATIONID = 'GrowleeReceivedNotification'
 
@@ -46,30 +50,46 @@ class GrowleeConfiguration(Screen, ConfigListScreen):
                        }
                )
 
+               config.plugins.growlee.protocol.addNotifier(self.setupList, initial_call=False)
                ConfigListScreen.__init__(
                        self,
-                       [
-                               getConfigListEntry(_("Receive Notifications?"), config.plugins.growlee.enable_incoming),
-                               getConfigListEntry(_("Send Notifications?"), config.plugins.growlee.enable_outgoing),
-                               getConfigListEntry(_("Address"), config.plugins.growlee.address),
-                               getConfigListEntry(_("Password"), config.plugins.growlee.password),
-                       ],
+                       [],
                        session=session,
                        on_change=self.changed
                )
 
                # Trigger change
+               self.setupList()
                self.changed()
 
        def changed(self):
                for x in self.onChangedEntry:
                        x()
 
+       def setupList(self, *args):
+               l = [
+                       getConfigListEntry(_("Type"), config.plugins.growlee.protocol),
+                       getConfigListEntry(_("Send Notifications?"), config.plugins.growlee.enable_outgoing),
+               ]
+
+               if config.plugins.growlee.protocol.value == "prowl":
+                       l.append(getConfigListEntry(_("API Key"), config.plugins.growlee.prowl_api_key))
+               else:
+                       l.extend((
+                               getConfigListEntry(_("Receive Notifications?"), config.plugins.growlee.enable_incoming),
+                               getConfigListEntry(_("Address"), config.plugins.growlee.address),
+                               getConfigListEntry(_("Password"), config.plugins.growlee.password),
+                       ))
+
+               self["config"].list = l
+
        def getCurrentEntry(self):
-               return self["config"].getCurrent()[0]
+               cur = self["config"].getCurrent()
+               return cur and cur[0]
 
        def getCurrentValue(self):
-               return str(self["config"].getCurrent()[1].getText())
+               cur = self["config"].getCurrent()
+               return cur and str(cur[1].getText())
 
        def createSummary(self):
                return SetupSummary
@@ -80,8 +100,7 @@ class GrowleeConfiguration(Screen, ConfigListScreen):
                        if port:
                                def maybeConnect(*args, **kwargs):
                                        if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
-                                               global port
-                                               port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
+                                               doConnect()
 
                                d = port.stopListening()
                                if d is not None:
@@ -89,14 +108,25 @@ class GrowleeConfiguration(Screen, ConfigListScreen):
                                else:
                                        maybeConnect()
                        elif config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
-                               port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
+                               doConnect()
 
                self.saveAll()
                self.close()
 
+       def close(self):
+               config.plugins.growlee.protocol.notifiers.remove(self.setupList)
+               Screen.close(self)
+
 def configuration(session, **kwargs):
        session.open(GrowleeConfiguration)
 
+def doConnect():
+       global port
+       if config.plugins.growlee.protocol.value == "snarl":
+               port = reactor.listenTCP(GROWL_UDP_PORT, growlProtocolOneWrapper)
+       else:
+               port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
+
 def emergencyDisable(*args, **kwargs):
        global port
        if port:
@@ -113,56 +143,133 @@ def emergencyDisable(*args, **kwargs):
 
 class GrowlProtocolOneWrapper(DatagramProtocol):
        def startProtocol(self):
-               if config.plugins.growlee.enable_outgoing.value:
+               proto = config.plugins.growlee.protocol.value
+               if config.plugins.growlee.enable_outgoing.value and not proto == "prowl":
                        addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
-                       p = GrowlRegistrationPacket(password=config.plugins.growlee.password.value)
-                       p.addNotification()
+                       if proto == "growl":
+                               p = GrowlRegistrationPacket(password=config.plugins.growlee.password.value)
+                               p.addNotification()
+                               payload = p.payload()
+                       else: #proto == "snarl":
+                               payload = "type=SNP#?version=1.0#?action=register#?app=growlee\r\n"
                        try:
-                               self.transport.write(p.payload(), addr)
+                               self.transport.write(payload, addr)
                        except gaierror:
                                emergencyDisable()
 
+       def doStop(self):
+               if config.plugins.growlee.enable_outgoing.value and config.plugins.growlee.protocol.value == "snarl":
+                       addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
+                       payload = "type=SNP#?version=1.0#?action=unregister#?app=growlee\r\n"
+                       try:
+                               self.transport.write(payload, addr)
+                       except gaierror:
+                               pass
+               DaragramProtocol.doStop(self)
+
        def sendNotification(self, *args, **kwargs):
                if not self.transport or not config.plugins.growlee.enable_outgoing.value:
                        return
 
-               addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
-               p = GrowlNotificationPacket(*args, **kwargs)
-               try:
-                       self.transport.write(p.payload(), addr)
-               except gaierror:
-                       emergencyDisable()
+               proto = config.plugins.growlee.protocol.value
+               if proto == "prowl":
+                       headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'}
+                       data = {
+                               'apikey': config.plugins.growlee.prowl_api_key.value,
+                               'application': "growlee",
+                               'event': kwargs.get('title', 'No title.'),
+                               'description': kwargs.get('description', 'No message.'),
+                               'priority': 0
+                       }
 
-       def datagramReceived(self, data, addr):
-               Len = len(data)
-               if Len < 16 or not config.plugins.growlee.enable_incoming.value:
-                       return
+                       print urlencode(data)
+                       getPage('https://prowl.weks.net/publicapi/add/', method = 'POST', headers = headers, postdata = urlencode(data)).addErrback(emergencyDisable)
+               else:
+                       addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
+                       if proto == "growl":
+                               p = GrowlNotificationPacket(*args, **kwargs)
+                               payload = p.payload()
+                       else: #proto == "snarl":
+                               title = kwargs.get('title', 'No title.')
+                               text = kwargs.get('description', 'No message.')
+                               timeout = kwargs.get('timeout', 10)
+                               payload = "type=SNP#?version=1.0#?action=notification#?app=growlee#?class=growleeClass#?title=%s#?text=%s#?timeout=%d\r\n" % (title, text, timeout)
+                       try:
+                               self.transport.write(payload, addr)
+                       except gaierror:
+                               emergencyDisable()
 
-               digest = data[-16:]
-               password = config.plugins.growlee.password.value
-               checksum = md5_constructor()
-               checksum.update(data[:-16])
-               if password:
-                       checksum.update(password)
-               if digest != checksum.digest():
+       def datagramReceived(self, data, addr):
+               proto = config.plugins.growlee.protocol.value
+               if proto == "prowl" or not config.plugins.growlee.enable_incoming.value:
                        return
 
-               # notify packet
-               if data[1] == '\x01':
-                       nlen, tlen, dlen, alen = unpack("!HHHH",str(data[4:12]))
-                       notification, title, description = unpack(("%ds%ds%ds") % (nlen, tlen, dlen), data[12:Len-alen-16])
+               Len = len(data)
+               if proto == "growl":
+                       if Len < 16:
+                               return
+
+                       digest = data[-16:]
+                       password = config.plugins.growlee.password.value
+                       checksum = md5_constructor()
+                       checksum.update(data[:-16])
+                       if password:
+                               checksum.update(password)
+                       if digest != checksum.digest():
+                               return
+
+                       # notify packet
+                       if data[1] == '\x01':
+                               nlen, tlen, dlen, alen = unpack("!HHHH",str(data[4:12]))
+                               notification, title, description = unpack("%ds%ds%ds" % (nlen, tlen, dlen), data[12:Len-alen-16])
+
+                               Notifications.AddNotificationWithID(
+                                       NOTIFICATIONID,
+                                       MessageBox,
+                                       text = title + '\n' + description,
+                                       type = MessageBox.TYPE_INFO,
+                                       timeout = 5,
+                                       close_on_any_key = True,
+                               )
+
+                       # TODO: do we want to handle register packets? :-)
+               else: #proto == "snarl":
+                       if Len < 23 or not data[:23] == "type=SNP#?version=1.0#?":
+                               return
+
+                       items = data[23:].split('#?')
+
+                       title = ''
+                       description = ''
+                       timeout = 5
+                       for item in items:
+                               key, value = item.split('=')
+                               if key == "action":
+                                       if value != "notification":
+                                               # NOTE: we pretent to accept pretty much everything one throws at us
+                                               addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
+                                               payload = "SNP/1.0/0/OK\r\n"
+                                               try:
+                                                       self.transport.write(payload, addr)
+                                               except gaierror:
+                                                       emergencyDisable()
+                                               return
+                               elif key == "title":
+                                       title = value
+                               elif key == "text":
+                                       description = value
+                               elif key == "timeout":
+                                       timeout = int(value)
 
                        Notifications.AddNotificationWithID(
                                NOTIFICATIONID,
                                MessageBox,
                                text = title + '\n' + description,
                                type = MessageBox.TYPE_INFO,
-                               timeout = 5,
+                               timeout = timeout,
                                close_on_any_key = True,
                        )
 
-               # TODO: do we want to handle register packets? :-)
-
 growlProtocolOneWrapper = GrowlProtocolOneWrapper()
 port = None
 
@@ -182,8 +289,7 @@ def gotNotification():
 
 def autostart(**kwargs):
        if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
-               global port
-               port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
+               doConnect()
 
        # NOTE: we need to be the first one to be notified since other listeners
        # may remove the notifications from the list for good