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
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")])
30 NOTIFICATIONID = 'GrowleeReceivedNotification'
32 class GrowleeConfiguration(Screen, ConfigListScreen):
33 def __init__(self, session):
34 Screen.__init__(self, session)
35 self.skinName = [ "GrowleeConfiguration", "Setup" ]
38 self["key_red"] = StaticText(_("Cancel"))
39 self["key_green"] = StaticText(_("OK"))
42 self.setup_title = "Growlee Configuration"
43 self.onChangedEntry = []
46 self["actions"] = ActionMap(["SetupActions"],
48 "cancel": self.keyCancel,
53 config.plugins.growlee.protocol.addNotifier(self.setupList, initial_call=False)
54 ConfigListScreen.__init__(
58 on_change=self.changed
66 for x in self.onChangedEntry:
69 def setupList(self, *args):
71 getConfigListEntry(_("Type"), config.plugins.growlee.protocol),
72 getConfigListEntry(_("Send Notifications?"), config.plugins.growlee.enable_outgoing),
75 if config.plugins.growlee.protocol.value == "prowl":
76 l.append(getConfigListEntry(_("API Key"), config.plugins.growlee.prowl_api_key))
79 getConfigListEntry(_("Receive Notifications?"), config.plugins.growlee.enable_incoming),
80 getConfigListEntry(_("Address"), config.plugins.growlee.address),
81 getConfigListEntry(_("Password"), config.plugins.growlee.password),
84 self["config"].list = l
86 def getCurrentEntry(self):
87 cur = self["config"].getCurrent()
90 def getCurrentValue(self):
91 cur = self["config"].getCurrent()
92 return cur and str(cur[1].getText())
94 def createSummary(self):
98 if self["config"].isChanged():
101 def maybeConnect(*args, **kwargs):
102 if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
105 d = port.stopListening()
107 d.addCallback(maybeConnect).addErrback(emergencyDisable)
110 elif config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
117 config.plugins.growlee.protocol.notifiers.remove(self.setupList)
120 def configuration(session, **kwargs):
121 session.open(GrowleeConfiguration)
125 if config.plugins.growlee.protocol.value == "snarl":
126 port = reactor.listenTCP(GROWL_UDP_PORT, growlProtocolOneWrapper)
128 port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
130 def emergencyDisable(*args, **kwargs):
136 if gotNotification in Notifications.notificationAdded:
137 Notifications.notificationAdded.remove(gotNotification)
138 Notifications.AddPopup(
139 _("Network error.\nDisabling Growlee!"),
140 MessageBox.TYPE_ERROR,
144 class GrowlProtocolOneWrapper(DatagramProtocol):
145 def startProtocol(self):
146 proto = config.plugins.growlee.protocol.value
147 if config.plugins.growlee.enable_outgoing.value and not proto == "prowl":
148 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
150 p = GrowlRegistrationPacket(password=config.plugins.growlee.password.value)
152 payload = p.payload()
153 else: #proto == "snarl":
154 payload = "type=SNP#?version=1.0#?action=register#?app=growlee\r\n"
156 self.transport.write(payload, addr)
161 if config.plugins.growlee.enable_outgoing.value and config.plugins.growlee.protocol.value == "snarl":
162 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
163 payload = "type=SNP#?version=1.0#?action=unregister#?app=growlee\r\n"
165 self.transport.write(payload, addr)
168 DaragramProtocol.doStop(self)
170 def sendNotification(self, *args, **kwargs):
171 if not self.transport or not config.plugins.growlee.enable_outgoing.value:
174 proto = config.plugins.growlee.protocol.value
176 headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'}
178 'apikey': config.plugins.growlee.prowl_api_key.value,
179 'application': "growlee",
180 'event': kwargs.get('title', 'No title.'),
181 'description': kwargs.get('description', 'No message.'),
185 print urlencode(data)
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 p = GrowlNotificationPacket(*args, **kwargs)
191 payload = p.payload()
192 else: #proto == "snarl":
193 title = kwargs.get('title', 'No title.')
194 text = kwargs.get('description', 'No message.')
195 timeout = kwargs.get('timeout', 10)
196 payload = "type=SNP#?version=1.0#?action=notification#?app=growlee#?class=growleeClass#?title=%s#?text=%s#?timeout=%d\r\n" % (title, text, timeout)
198 self.transport.write(payload, addr)
202 def datagramReceived(self, data, addr):
203 proto = config.plugins.growlee.protocol.value
204 if proto == "prowl" or not config.plugins.growlee.enable_incoming.value:
213 password = config.plugins.growlee.password.value
214 checksum = md5_constructor()
215 checksum.update(data[:-16])
217 checksum.update(password)
218 if digest != checksum.digest():
222 if data[1] == '\x01':
223 nlen, tlen, dlen, alen = unpack("!HHHH",str(data[4:12]))
224 notification, title, description = unpack("%ds%ds%ds" % (nlen, tlen, dlen), data[12:Len-alen-16])
226 Notifications.AddNotificationWithID(
229 text = title + '\n' + description,
230 type = MessageBox.TYPE_INFO,
232 close_on_any_key = True,
235 # TODO: do we want to handle register packets? :-)
236 else: #proto == "snarl":
237 if Len < 23 or not data[:23] == "type=SNP#?version=1.0#?":
240 items = data[23:].split('#?')
246 key, value = item.split('=')
248 if value != "notification":
249 # NOTE: we pretent to accept pretty much everything one throws at us
250 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
251 payload = "SNP/1.0/0/OK\r\n"
253 self.transport.write(payload, addr)
261 elif key == "timeout":
264 Notifications.AddNotificationWithID(
267 text = title + '\n' + description,
268 type = MessageBox.TYPE_INFO,
270 close_on_any_key = True,
273 growlProtocolOneWrapper = GrowlProtocolOneWrapper()
276 def gotNotification():
277 notifications = Notifications.notifications
279 _, screen, args, kwargs, id = notifications[-1]
280 if screen is MessageBox and id != NOTIFICATIONID:
283 description = kwargs["text"]
285 description = args[0]
286 description = description.decode('utf-8')
288 growlProtocolOneWrapper.sendNotification(title="Dreambox", description=description, password=config.plugins.growlee.password.value)
290 def autostart(**kwargs):
291 if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
294 # NOTE: we need to be the first one to be notified since other listeners
295 # may remove the notifications from the list for good
296 Notifications.notificationAdded.insert(0, gotNotification)
298 def Plugins(**kwargs):
301 where=PluginDescriptor.WHERE_SESSIONSTART,
306 description=_("Configure Growlee"),
307 where=PluginDescriptor.WHERE_PLUGINMENU,