1 from Plugins.Plugin import PluginDescriptor
3 from Tools import Notifications
4 from netgrowl import GrowlRegistrationPacket, GrowlNotificationPacket, \
5 GROWL_UDP_PORT, md5_constructor
6 from twisted.internet.protocol import DatagramProtocol
7 from twisted.internet import reactor
8 from twisted.web.client import getPage
9 from struct import unpack
10 from socket import gaierror
11 from urllib import urlencode
13 from Screens.Setup import SetupSummary
14 from Screens.Screen import Screen
15 from Screens.MessageBox import MessageBox
16 from Components.ActionMap import ActionMap
17 from Components.config import config, getConfigListEntry, ConfigSubsection, \
18 ConfigText, ConfigPassword, ConfigYesNo, ConfigSelection, ConfigSet
19 from Components.ConfigList import ConfigListScreen
20 from Components.Sources.StaticText import StaticText
22 config.plugins.growlee = ConfigSubsection()
23 config.plugins.growlee.enable_incoming = ConfigYesNo(default=False)
24 config.plugins.growlee.enable_outgoing = ConfigYesNo(default=False)
25 config.plugins.growlee.address = ConfigText(fixed_size=False)
26 config.plugins.growlee.password = ConfigPassword()
27 config.plugins.growlee.prowl_api_key = ConfigText(fixed_size=False)
28 config.plugins.growlee.protocol = ConfigSelection(default="growl", choices = [("growl", "Growl"), ("snarl", "Snarl"), ("prowl", "Prowl")])
29 config.plugins.growlee.blacklist = ConfigSet(choices = [])
31 NOTIFICATIONID = 'GrowleeReceivedNotification'
33 class GrowleeConfiguration(Screen, ConfigListScreen):
34 def __init__(self, session):
35 Screen.__init__(self, session)
36 self.skinName = [ "GrowleeConfiguration", "Setup" ]
39 self["key_red"] = StaticText(_("Cancel"))
40 self["key_green"] = StaticText(_("OK"))
43 self.setup_title = "Growlee Configuration"
44 self.onChangedEntry = []
47 self["actions"] = ActionMap(["SetupActions"],
49 "cancel": self.keyCancel,
54 config.plugins.growlee.protocol.addNotifier(self.setupList, initial_call=False)
55 ConfigListScreen.__init__(
59 on_change=self.changed
67 for x in self.onChangedEntry:
70 def setupList(self, *args):
72 getConfigListEntry(_("Type"), config.plugins.growlee.protocol),
73 getConfigListEntry(_("Send Notifications?"), config.plugins.growlee.enable_outgoing),
76 if config.plugins.growlee.protocol.value == "prowl":
77 l.append(getConfigListEntry(_("API Key"), config.plugins.growlee.prowl_api_key))
80 getConfigListEntry(_("Receive Notifications?"), config.plugins.growlee.enable_incoming),
81 getConfigListEntry(_("Address"), config.plugins.growlee.address),
82 getConfigListEntry(_("Password"), config.plugins.growlee.password),
85 self["config"].list = l
87 def getCurrentEntry(self):
88 cur = self["config"].getCurrent()
91 def getCurrentValue(self):
92 cur = self["config"].getCurrent()
93 return cur and str(cur[1].getText())
95 def createSummary(self):
99 if self["config"].isChanged():
102 def maybeConnect(*args, **kwargs):
103 if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
106 d = port.stopListening()
108 d.addCallback(maybeConnect).addErrback(emergencyDisable)
111 elif config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
118 config.plugins.growlee.protocol.notifiers.remove(self.setupList)
121 def configuration(session, **kwargs):
122 session.open(GrowleeConfiguration)
126 if config.plugins.growlee.protocol.value == "snarl":
127 port = reactor.listenTCP(GROWL_UDP_PORT, growlProtocolOneWrapper)
129 port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
131 def emergencyDisable(*args, **kwargs):
137 if gotNotification in Notifications.notificationAdded:
138 Notifications.notificationAdded.remove(gotNotification)
139 Notifications.AddPopup(
140 _("Network error.\nDisabling Growlee!"),
141 MessageBox.TYPE_ERROR,
145 class GrowlProtocolOneWrapper(DatagramProtocol):
146 def startProtocol(self):
147 proto = config.plugins.growlee.protocol.value
148 if config.plugins.growlee.enable_outgoing.value and not proto == "prowl":
149 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
151 p = GrowlRegistrationPacket(application="growlee", password=config.plugins.growlee.password.value)
153 payload = p.payload()
154 else: #proto == "snarl":
155 payload = "type=SNP#?version=1.0#?action=register#?app=growlee\r\n"
157 self.transport.write(payload, addr)
162 if config.plugins.growlee.enable_outgoing.value and config.plugins.growlee.protocol.value == "snarl":
163 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
164 payload = "type=SNP#?version=1.0#?action=unregister#?app=growlee\r\n"
166 self.transport.write(payload, addr)
169 DatagramProtocol.doStop(self)
171 def sendNotification(self, *args, **kwargs):
172 if not self.transport or not config.plugins.growlee.enable_outgoing.value:
175 proto = config.plugins.growlee.protocol.value
177 headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'}
179 'apikey': config.plugins.growlee.prowl_api_key.value,
180 'application': "growlee",
181 'event': kwargs.get('title', 'No title.'),
182 'description': kwargs.get('description', 'No message.'),
183 'priority': kwargs.get('priority', 0),
186 getPage('https://prowl.weks.net/publicapi/add/', method = 'POST', headers = headers, postdata = urlencode(data)).addErrback(emergencyDisable)
188 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
190 # map timeout -> sticky
191 if "timeout" in kwargs:
192 if kwargs["timeout"] == -1:
193 kwargs["sticky"] = True
194 del kwargs["timeout"]
196 p = GrowlNotificationPacket(*args, application="growlee", **kwargs)
197 payload = p.payload()
198 else: #proto == "snarl":
199 title = kwargs.get('title', 'No title.')
200 text = kwargs.get('description', 'No message.')
201 timeout = kwargs.get('timeout', -1)
203 # NOTE: timeout = 0 means sticky, so add one second to map -1 to 0 and make 0 non-sticky
207 payload = "type=SNP#?version=1.0#?action=notification#?app=growlee#?class=growleeClass#?title=%s#?text=%s#?timeout=%d\r\n" % (title, text, timeout)
209 self.transport.write(payload, addr)
213 def datagramReceived(self, data, addr):
214 proto = config.plugins.growlee.protocol.value
215 if proto == "prowl" or not config.plugins.growlee.enable_incoming.value:
224 password = config.plugins.growlee.password.value
225 checksum = md5_constructor()
226 checksum.update(data[:-16])
228 checksum.update(password)
229 if digest != checksum.digest():
233 if data[1] == '\x01':
234 nlen, tlen, dlen, alen = unpack("!HHHH",str(data[4:12]))
235 notification, title, description = unpack("%ds%ds%ds" % (nlen, tlen, dlen), data[12:Len-alen-16])
237 Notifications.AddNotificationWithID(
240 text = title + '\n' + description,
241 type = MessageBox.TYPE_INFO,
243 close_on_any_key = True,
246 # TODO: do we want to handle register packets? :-)
247 else: #proto == "snarl":
248 if Len < 23 or not data[:23] == "type=SNP#?version=1.0#?":
251 items = data[23:].split('#?')
257 key, value = item.split('=')
259 if value != "notification":
260 # NOTE: we pretend to handle&accept pretty much everything one throws at us
261 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
262 payload = "SNP/1.0/0/OK\r\n"
264 self.transport.write(payload, addr)
272 elif key == "timeout":
275 Notifications.AddNotificationWithID(
278 text = title + '\n' + description,
279 type = MessageBox.TYPE_INFO,
281 close_on_any_key = True,
285 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
286 payload = "SNP/1.0/0/OK\r\n"
288 self.transport.write(payload, addr)
293 growlProtocolOneWrapper = GrowlProtocolOneWrapper()
296 def gotNotification():
297 notifications = Notifications.notifications
299 _, screen, args, kwargs, id = notifications[-1]
300 if screen is MessageBox and id != NOTIFICATIONID and id not in config.plugins.growlee.blacklist.value:
302 type = kwargs.get("type", 0)
303 timeout = kwargs.get("timeout", -1)
306 description = kwargs["text"]
308 description = args[0]
309 description = description.decode('utf-8')
311 # NOTE: priority is in [-2; 2] but type is [0; 3] so map it
312 # XXX: maybe priority==type-2 would be more appropriate
313 growlProtocolOneWrapper.sendNotification(title="Dreambox", description=description, password=config.plugins.growlee.password.value, priority=type-1, timeout=timeout)
315 def autostart(**kwargs):
316 if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
319 # NOTE: we need to be the first one to be notified since other listeners
320 # may remove the notifications from the list for good
321 Notifications.notificationAdded.insert(0, gotNotification)
323 def Plugins(**kwargs):
326 where=PluginDescriptor.WHERE_SESSIONSTART,
331 description=_("Configure Growlee"),
332 where=PluginDescriptor.WHERE_PLUGINMENU,