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, ConfigSet
from Components.ConfigList import ConfigListScreen
from Components.Sources.StaticText import StaticText
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")])
+config.plugins.growlee.blacklist = ConfigSet(choices = [])
NOTIFICATIONID = 'GrowleeReceivedNotification'
}
)
+ 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
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:
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:
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(application="growlee", 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
+ DatagramProtocol.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': kwargs.get('priority', 0),
+ }
- def datagramReceived(self, data, addr):
- Len = len(data)
- if Len < 16 or not config.plugins.growlee.enable_incoming.value:
- return
+ 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":
+ # map timeout -> sticky
+ if "timeout" in kwargs:
+ if kwargs["timeout"] == -1:
+ kwargs["sticky"] = True
+ del kwargs["timeout"]
+
+ p = GrowlNotificationPacket(*args, application="growlee", **kwargs)
+ payload = p.payload()
+ else: #proto == "snarl":
+ title = kwargs.get('title', 'No title.')
+ text = kwargs.get('description', 'No message.')
+ timeout = kwargs.get('timeout', -1)
+
+ # NOTE: timeout = 0 means sticky, so add one second to map -1 to 0 and make 0 non-sticky
+ if timeout < 2:
+ timeout += 1
+
+ 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 pretend to handle&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? :-)
+ # return ok
+ 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()
+
growlProtocolOneWrapper = GrowlProtocolOneWrapper()
port = None
notifications = Notifications.notifications
if notifications:
_, screen, args, kwargs, id = notifications[-1]
- if screen is MessageBox and id != NOTIFICATIONID:
+ if screen is MessageBox and id != NOTIFICATIONID and id not in config.plugins.growlee.blacklist.value:
+
+ type = kwargs.get("type", 0)
+ timeout = kwargs.get("timeout", -1)
if "text" in kwargs:
description = kwargs["text"]
description = args[0]
description = description.decode('utf-8')
- growlProtocolOneWrapper.sendNotification(title="Dreambox", description=description, password=config.plugins.growlee.password.value)
+ # NOTE: priority is in [-2; 2] but type is [0; 3] so map it
+ # XXX: maybe priority==type-2 would be more appropriate
+ growlProtocolOneWrapper.sendNotification(title="Dreambox", description=description, password=config.plugins.growlee.password.value, priority=type-1, timeout=timeout)
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