From: Moritz Venn Date: Sat, 16 Jan 2010 20:32:59 +0000 (+0000) Subject: add prowl send support and experimental (as in untested) snarl send/receive support X-Git-Url: http://code.vuplus.com/gitweb/?p=vuplus_dvbapp-plugin;a=commitdiff_plain;h=9ace1f6fd38382b616b54f22a1513ea1f219d95b add prowl send support and experimental (as in untested) snarl send/receive support warning: if you use prowl your api key may end up in a crashlog, so try not to have crashes :D --- diff --git a/growlee/src/plugin.py b/growlee/src/plugin.py index bbf5869..ad4ddc0 100644 --- a/growlee/src/plugin.py +++ b/growlee/src/plugin.py @@ -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