1 # -*- coding: utf-8 -*-
2 from Screens.Screen import Screen
3 from Screens.MessageBox import MessageBox
5 from Components.ActionMap import ActionMap
6 from Components.Label import Label
7 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigIP, ConfigEnableDisable, getConfigListEntry, ConfigText, ConfigInteger
8 from Components.ConfigList import ConfigListScreen
10 from Plugins.Plugin import PluginDescriptor
11 from Tools import Notifications
13 from twisted.internet import reactor
14 from twisted.internet.protocol import ReconnectingClientFactory
15 from twisted.protocols.basic import LineReceiver
16 from twisted.web.client import getPage
18 from os import path as os_path
19 from urllib import urlencode
23 my_global_session = None
25 config.plugins.FritzCall = ConfigSubsection()
26 config.plugins.FritzCall.enable = ConfigEnableDisable(default = False)
27 config.plugins.FritzCall.hostname = ConfigIP(default = [192, 168, 178, 1])
28 config.plugins.FritzCall.filter = ConfigEnableDisable(default = False)
29 config.plugins.FritzCall.filtermsn = ConfigText(default = "", fixed_size = False)
30 config.plugins.FritzCall.showOutgoing = ConfigEnableDisable(default = False)
31 config.plugins.FritzCall.timeout = ConfigInteger(default = 15, limits = (0,60))
32 config.plugins.FritzCall.lookup = ConfigEnableDisable(default = False)
33 config.plugins.FritzCall.internal = ConfigEnableDisable(default = False)
34 config.plugins.FritzCall.fritzphonebook = ConfigEnableDisable(default = False)
35 config.plugins.FritzCall.phonebook = ConfigEnableDisable(default = False)
36 config.plugins.FritzCall.addcallers = ConfigEnableDisable(default = False)
37 config.plugins.FritzCall.phonebookLocation = ConfigSelection(choices = [("/media/usb/PhoneBook.txt", _("USB Stick")), ("/media/cf/PhoneBook.txt", _("CF Drive")), ("/media/hdd/PhoneBook.txt", _("Harddisk"))])
38 config.plugins.FritzCall.password = ConfigText(default = "", fixed_size = False)
39 config.plugins.FritzCall.prefix = ConfigText(default = "", fixed_size = False)
41 class FritzCallPhonebook:
46 def notify(self, text):
47 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
51 f = open(config.plugins.FritzCall.phonebookLocation.value, 'w')
52 f.write("01234567890#Name, Street, Location (Keep the Spaces!!!)\n");
58 def error(self, error):
59 if self.event == "LOGIN":
60 text = _("Fritz!Box Login failed! - Error: %s") %error
62 elif self.event == "LOAD":
63 text = _("Could not load phonebook from Fritz!Box - Error: %s") %error
66 def loadFritzBoxPhonebook(self):
67 print "[FritzCallPhonebook] loadFritzBoxPhonebook"
70 host = "%d.%d.%d.%d" %tuple(config.plugins.FritzCall.hostname.value)
71 uri = "/cgi-bin/webcm"# % tuple(config.plugins.FritzCall.hostname.value)
72 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de','var:pagename':'fonbuch','var:menu':'fon'})
74 url = "http://%s%s?%s" %(host, uri, parms)
76 getPage(url).addCallback(self._gotPage).addErrback(self.error)
78 def parseFritzBoxPhonebook(self, html):
79 found = re.match('.*<table id="tList".*?</tr>\n(.*?)</table>', html, re.S)
82 table = found.group(1)
83 text = re.sub("<.*?>", "", table)
84 text = text.split('\n')
87 if line.strip() != "":
89 line = line.replace("\"", "")
90 line = line.split(", ")
93 name = name.replace("ß", "?").replace("ä", "?").replace("ö", "?").replace("ü", "?").replace("Ä", "?").replace("Ö", "?").replace("Ü", "?")
94 print "[FritzCallPhonebook] Adding '''%s''' with '''%s''' from Fritz!Box Phonebook!" %(name, number)
95 self.phonebook[number.strip()] = name.strip()
98 print "[FritzCallPhonebook] Could not parse Fritz!Box Phonebook entry"
100 def _gotPage(self, html):
101 # print "[FritzCallPhonebook] _gotPage"
102 # workaround: exceptions in gotPage-callback were ignored
104 if self.event == "LOGIN":
105 self.verifyLogin(html)
106 if self.event == "LOAD":
107 self.parseFritzBoxPhonebook(html)
109 import traceback, sys
110 traceback.print_exc(file=sys.stdout)
114 print "[FritzCallPhonebook] Login"
117 host = "%d.%d.%d.%d" %tuple(config.plugins.FritzCall.hostname.value)
118 uri = "/cgi-bin/webcm"
119 parms = "login:command/password=%s" %(config.plugins.FritzCall.password.value)
120 url = "http://%s%s" %(host, uri)
122 getPage(url, method="POST", headers = {'Content-Type': "application/x-www-form-urlencoded",'Content-Length': str(len(parms))}, postdata=parms).addCallback(self._gotPage).addErrback(self.error)
124 def verifyLogin(self, html):
125 # print "[FritzCallPhonebook] verifyLogin - html: %s" %html
127 found = re.match('.*<p class="errorMessage">FEHLER: Das angegebene Kennwort', html, re.S)
129 self.loadFritzBoxPhonebook()
131 text = _("Fritz!Box Login failed! - Wrong Password!")
135 # print "[FritzCallPhonebook] reload"
136 self.phonebook.clear()
138 if not os_path.exists(config.plugins.FritzCall.phonebookLocation.value):
145 for line in open(config.plugins.FritzCall.phonebookLocation.value):
147 number, name = line.split("#")
148 if not self.phonebook.has_key(number):
149 self.phonebook[number] = name
151 print "[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" %line
153 if config.plugins.FritzCall.fritzphonebook.value:
154 if config.plugins.FritzCall.password.value != "":
157 self.loadFritzBoxPhonebook()
159 def search(self, number):
160 # print "[FritzCallPhonebook] Searching for %s" %number
162 if config.plugins.FritzCall.phonebook.value:
163 if self.phonebook.has_key(number):
164 name = self.phonebook[number].replace(", ", "\n")
167 def add(self, number, name):
168 # print "[FritzCallPhonebook] add"
169 if config.plugins.FritzCall.phonebook.value and config.plugins.FritzCall.addcallers.value:
171 f = open(config.plugins.FritzCall.phonebookLocation.value, 'a')
172 name = name.strip() + "\n"
173 string = "%s#%s" %(number, name)
174 self.phonebook[number] = name;
182 phonebook = FritzCallPhonebook()
184 class FritzCallSetup(ConfigListScreen, Screen):
186 <screen position="100,90" size="550,420" title="FritzCall Setup" >
187 <widget name="config" position="20,10" size="510,300" scrollbarMode="showOnDemand" />
188 <widget name="consideration" position="20,320" font="Regular;20" halign="center" size="510,50" />
191 def __init__(self, session, args = None):
193 Screen.__init__(self, session)
195 self["consideration"] = Label(_("You need to enable the monitoring on your Fritz!Box by dialing #96*5*!"))
198 self["setupActions"] = ActionMap(["SetupActions"],
201 "cancel": self.cancel,
205 ConfigListScreen.__init__(self, self.list)
210 ConfigListScreen.keyLeft(self)
214 ConfigListScreen.keyRight(self)
217 def createSetup(self):
219 self.list.append(getConfigListEntry(_("Call monitoring"), config.plugins.FritzCall.enable))
220 if config.plugins.FritzCall.enable.value:
221 self.list.append(getConfigListEntry(_("Fritz!Box FON IP address"), config.plugins.FritzCall.hostname))
223 self.list.append(getConfigListEntry(_("Show Calls for specific MSN"), config.plugins.FritzCall.filter))
224 if config.plugins.FritzCall.filter.value:
225 self.list.append(getConfigListEntry(_("MSN to show"), config.plugins.FritzCall.filtermsn))
227 self.list.append(getConfigListEntry(_("Show Outgoing Calls"), config.plugins.FritzCall.showOutgoing))
228 self.list.append(getConfigListEntry(_("Timeout for Call Notifications (seconds)"), config.plugins.FritzCall.timeout))
229 self.list.append(getConfigListEntry(_("Reverse Lookup Caller ID (DE only)"), config.plugins.FritzCall.lookup))
231 self.list.append(getConfigListEntry(_("Read PhoneBook from Fritz!Box"), config.plugins.FritzCall.fritzphonebook))
232 if config.plugins.FritzCall.fritzphonebook.value:
233 self.list.append(getConfigListEntry(_("Password Accessing Fritz!Box"), config.plugins.FritzCall.password))
235 self.list.append(getConfigListEntry(_("Use internal PhoneBook"), config.plugins.FritzCall.phonebook))
236 if config.plugins.FritzCall.phonebook.value:
237 self.list.append(getConfigListEntry(_("PhoneBook Location"), config.plugins.FritzCall.phonebookLocation))
238 self.list.append(getConfigListEntry(_("Automatically add new Caller to PhoneBook"), config.plugins.FritzCall.addcallers))
240 self.list.append(getConfigListEntry(_("Strip Leading 0"), config.plugins.FritzCall.internal))
241 self.list.append(getConfigListEntry(_("Prefix for Outgoing Calls"), config.plugins.FritzCall.prefix))
243 self["config"].list = self.list
244 self["config"].l.setList(self.list)
247 # print "[FritzCallSetup] save"
248 for x in self["config"].list:
250 if fritz_call is not None:
253 if config.plugins.FritzCall.phonebook.value:
254 if not os_path.exists(config.plugins.FritzCall.phonebookLocation.value):
255 if not phonebook.create():
256 Notifications.AddNotification(MessageBox, _("Can't create PhoneBook.txt"), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
258 print "[FritzCallSetup] called phonebook.reload()"
264 # print "[FritzCallSetup] cancel"
265 for x in self["config"].list:
269 class FritzProtocol(LineReceiver):
271 # print "[FritzProtocol] __init__"
274 def resetValues(self):
275 # print "[FritzProtocol] resetValues"
281 def notify(self, text, timeout=config.plugins.FritzCall.timeout.value):
282 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO, timeout=timeout)
284 def handleIncoming(self):
285 # print "[FritzProtocol] handle Incoming!"
287 text = _("Incoming Call ")
288 if self.caller is not None:
289 text += _("on %s from\n---------------------------------------------\n%s\n%s\n---------------------------------------------\nto: %s") % (self.date, self.number, self.caller, self.phone)
291 text += _("on %s from\n---------------------------------------------\n%s (UNKNOWN)\n---------------------------------------------\nto: %s") % (self.date, self.number, self.phone)
296 def handleOutgoing(self):
297 # print "[FritzProtocol] handle Outgoing!"
298 text = _("Outgoing Call ")
299 if(self.caller is not None):
300 text += _("on %s to\n---------------------------------------------\n%s\n%s\n---------------------------------------------\nfrom: %s") % (self.date, self.number, self.caller, self.phone)
302 text += _("on %s to\n---------------------------------------------\n%s (UNKNOWN)\n\n---------------------------------------------\nfrom: %s") % (self.date, self.number, self.phone)#
307 def handleEvent(self):
308 # print "[FritzProtocol] handleEvent!"
309 if self.event == "RING":
310 self.handleIncoming()
311 elif self.event == "CALL":
312 self.handleOutgoing()
314 def handleEventOnError(self, error):
315 # print "[FritzProtocol] handleEventOnError - Error :%s" %error
318 def _gotPage(self, data):
319 # print "[FritzProtocol] _gotPage"
323 import traceback, sys
324 traceback.print_exc(file=sys.stdout)
328 def gotPage(self, html):
329 # print "[FritzProtocol] gotPage"
330 found = re.match('.*<td.*?class="cel-data border.*?>(.*?)</td>', html, re.S)
332 td = found.group(1) # group(1) is the content of (.*?) in our pattern
333 td.decode("ISO-8859-1").encode("UTF-8")
334 text = re.sub("<.*?>", "", td) # remove tags and their content
335 text = text.split("\n")
337 #wee need to strip the values as there a lots of whitespaces
338 name = text[2].strip()
339 address = text[8].replace(" ", " ").replace(", ", "\n").strip();
340 # print "[FritzProtocol] Reverse lookup succeeded:\nName: %s\n\nAddress: %s" %(name, address)
342 self.caller = "%s\n%s" %(name, address)
344 #Autoadd to PhoneBook.txt if enabled
345 if config.plugins.FritzCall.addcallers.value and self.event == "RING":
346 phonebook.add(self.number, self.caller.replace("\n", ", "))
348 # print "[FritzProtocol] Reverse lookup without result!"
352 def reverseLookup(self):
353 # print "[FritzProtocol] reverse Lookup!"
354 url = "http://www.dasoertliche.de/?form_name=search_inv&ph=%s" %self.number
355 getPage(url,method="GET").addCallback(self._gotPage).addErrback(self.handleEventOnError)
357 def lineReceived(self, line):
358 # print "[FritzProtocol] lineReceived"
359 #15.07.06 00:38:54;CALL;1;4;<provider>;<callee>;
360 #15.07.06 00:38:58;DISCONNECT;1;0;
361 #15.07.06 00:39:22;RING;0;<caller>;<outgoing msn>;
362 #15.07.06 00:39:27;DISCONNECT;0;0;
365 (self.date, self.event) = a[0:2]
368 if self.event == "RING":
371 if not config.plugins.FritzCall.filter.value or config.plugins.FritzCall.filtermsn.value == phone:
372 phonename = phonebook.search(phone)
373 if phonename is not None:
374 self.phone = "%s (%s)" %(phone, phonename)
378 if config.plugins.FritzCall.internal.value and a[3][0]=="0" and len(a[3]) > 3:
379 self.number = a[3][1:]
383 self.caller = phonebook.search(self.number)
384 if self.caller is None:
385 if config.plugins.FritzCall.lookup.value:
393 elif config.plugins.FritzCall.showOutgoing.value and self.event == "CALL":
396 if not config.plugins.FritzCall.filter.value or config.plugins.FritzCall.filtermsn.value == self.phone:
398 if config.plugins.FritzCall.internal.value and a[5][0]=="0" and len(a[3]) > 3:
399 self.number = a[5][1:]
403 self.caller = phonebook.search(self.number)
405 if self.number[0] != '0':
406 self.number = config.plugins.FritzCall.prefix.value + self.number
408 if self.caller is None:
409 if config.plugins.FritzCall.lookup.value:
415 class FritzClientFactory(ReconnectingClientFactory):
420 self.hangup_ok = False
422 def startedConnecting(self, connector):
423 Notifications.AddNotification(MessageBox, _("Connecting to Fritz!Box..."), type=MessageBox.TYPE_INFO, timeout=2)
425 def buildProtocol(self, addr):
426 Notifications.AddNotification(MessageBox, _("Connected to Fritz!Box!"), type=MessageBox.TYPE_INFO, timeout=4)
428 return FritzProtocol()
430 def clientConnectionLost(self, connector, reason):
431 if not self.hangup_ok:
432 Notifications.AddNotification(MessageBox, _("Connection to Fritz!Box! lost\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
433 ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
435 def clientConnectionFailed(self, connector, reason):
436 Notifications.AddNotification(MessageBox, _("Connecting to Fritz!Box failed\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
437 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
447 if config.plugins.FritzCall.enable.value:
448 f = FritzClientFactory()
449 self.d = (f, reactor.connectTCP("%d.%d.%d.%d" % tuple(config.plugins.FritzCall.hostname.value), 1012, f))
455 if self.d is not None:
456 self.d[0].hangup_ok = True
457 self.d[0].stopTrying()
458 self.d[1].disconnect()
462 session.open(FritzCallSetup)
466 def autostart(reason, **kwargs):
469 # ouch, this is a hack
470 if kwargs.has_key("session"):
471 global my_global_session
472 my_global_session = kwargs["session"]
475 print "[Fritz!Call] - Autostart"
477 fritz_call = FritzCall()
479 fritz_call.shutdown()
482 def Plugins(**kwargs):
483 return [ PluginDescriptor(name="FritzCall", description="Display Fritzbox-Fon calls on screen", where = PluginDescriptor.WHERE_PLUGINMENU, fnc=main),
484 PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart) ]