1 # -*- coding: utf-8 -*-
2 from Screens.Screen import Screen
3 from Screens.MessageBox import MessageBox
4 from Screens import Standby
6 from Components.ActionMap import ActionMap
7 from Components.Label import Label
8 from Components.Button import Button
9 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigIP, ConfigEnableDisable, getConfigListEntry, ConfigText, ConfigInteger
10 from Components.ConfigList import ConfigListScreen
11 from Components.ScrollLabel import ScrollLabel
13 from Plugins.Plugin import PluginDescriptor
14 from Tools import Notifications
16 from twisted.internet import reactor
17 from twisted.internet.protocol import ReconnectingClientFactory
18 from twisted.protocols.basic import LineReceiver
19 from twisted.web.client import getPage
21 from xml.dom.minidom import parse
23 from os import path as os_path
24 from urllib import urlencode
28 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
30 _ = gettext.translation('FritzCall', resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/locale"), [config.osd.language.getText()]).gettext
35 my_global_session = None
37 config.plugins.FritzCall = ConfigSubsection()
38 config.plugins.FritzCall.enable = ConfigEnableDisable(default = False)
39 config.plugins.FritzCall.hostname = ConfigIP(default = [192, 168, 178, 1])
40 config.plugins.FritzCall.afterStandby = ConfigSelection(choices = [("none", _("show nothing")), ("inList", _("show as list")), ("each", _("show each call"))])
41 config.plugins.FritzCall.filter = ConfigEnableDisable(default = False)
42 config.plugins.FritzCall.filtermsn = ConfigText(default = "", fixed_size = False)
43 config.plugins.FritzCall.filtermsn.setUseableChars('0123456789,')
44 config.plugins.FritzCall.showOutgoing = ConfigEnableDisable(default = False)
45 config.plugins.FritzCall.timeout = ConfigInteger(default = 15, limits = (0,60))
46 config.plugins.FritzCall.lookup = ConfigEnableDisable(default = False)
47 config.plugins.FritzCall.internal = ConfigEnableDisable(default = False)
48 config.plugins.FritzCall.fritzphonebook = ConfigEnableDisable(default = False)
49 config.plugins.FritzCall.phonebook = ConfigEnableDisable(default = False)
50 config.plugins.FritzCall.addcallers = ConfigEnableDisable(default = False)
51 config.plugins.FritzCall.phonebookLocation = ConfigSelection(choices = [("/etc/enigma2/PhoneBook.txt", _("Flash")), ("/media/usb/PhoneBook.txt", _("USB Stick")), ("/media/cf/PhoneBook.txt", _("CF Drive")), ("/media/hdd/PhoneBook.txt", _("Harddisk"))])
52 config.plugins.FritzCall.password = ConfigText(default = "", fixed_size = False)
53 config.plugins.FritzCall.showType = ConfigEnableDisable(default = True)
54 config.plugins.FritzCall.showShortcut = ConfigEnableDisable(default = False)
55 config.plugins.FritzCall.showVanity = ConfigEnableDisable(default = False)
56 config.plugins.FritzCall.prefix = ConfigText(default = "", fixed_size = False)
57 config.plugins.FritzCall.prefix.setUseableChars('0123456789')
60 ("0049", _("Germany")),
61 ("0031", _("The Netherlands")),
62 ("0033", _("France")),
64 ("0041", _("Switzerland")),
65 ("0043", _("Austria"))
67 config.plugins.FritzCall.country = ConfigSelection(choices = countryCodes)
71 FBF_MISSED_CALLS = "2"
73 fbfCallsChoices = {FBF_ALL_CALLS: _("All calls"),
74 FBF_IN_CALLS: _("Incoming calls"),
75 FBF_MISSED_CALLS: _("Missed calls"),
76 FBF_OUT_CALLS: _("Outgoing calls")
78 config.plugins.FritzCall.fbfCalls = ConfigSelection(choices = fbfCallsChoices)
80 def html2utf8(in_html):
83 htmlentitynumbermask = re.compile('(&#(\d{1,5}?);)')
84 htmlentitynamemask = re.compile('(&(\D{1,5}?);)')
85 entities = htmlentitynamemask.finditer(in_html)
88 entitydict[x.group(1)] = x.group(2)
89 for key, name in entitydict.items():
91 entitydict[key] = htmlentitydefs.name2codepoint[name]
94 entities = htmlentitynumbermask.finditer(in_html)
96 entitydict[x.group(1)] = x.group(2)
97 for key, codepoint in entitydict.items():
99 in_html = in_html.replace(key, (unichr(int(codepoint)).encode('utf8', "replace")))
103 return in_html.replace("&", "&").replace("ß", "ß").replace("ä", "ä").replace("ö", "ö").replace("ü", "ü").replace("Ä", "Ä").replace("Ö", "Ö").replace("Ü", "Ü")
109 print "[FritzCallFBF] __init__"
110 self.callScreen= None
111 self.loggedIn = False
113 self.loginCallback = None
116 self.callType = config.plugins.FritzCall.fbfCalls.value
118 def notify(self, text):
119 print "[FritzCallFBF] notify"
121 print "[FritzCallFBF] notify: try to close callScreen"
122 self.callScreen.close()
123 self.callScreen = None
124 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
126 def errorLogin(self, error):
127 text = _("FRITZ!Box Login failed! - Error: %s") %error
130 def _gotPageLogin(self, html):
131 # print "[FritzCallPhonebook] _gotPageLogin"
132 # workaround: exceptions in gotPage-callback were ignored
134 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login verification"))
136 print "[FritzCallFBF] _gotPageLogin: verify login"
137 found = re.match('.*<p class="errorMessage">FEHLER: Das angegebene Kennwort', html, re.S)
139 text = _("FRITZ!Box Login failed! - Wrong Password!")
143 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login ok"))
148 import traceback, sys
149 traceback.print_exc(file=sys.stdout)
153 print "[FritzCallFBF] Login"
154 if config.plugins.FritzCall.password.value != "":
156 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login"))
157 host = "%d.%d.%d.%d" %tuple(config.plugins.FritzCall.hostname.value)
158 uri = "/cgi-bin/webcm"
159 parms = "login:command/password=%s" %(config.plugins.FritzCall.password.value)
160 url = "http://%s%s" %(host, uri)
161 getPage(url, method="POST", headers = {'Content-Type': "application/x-www-form-urlencoded",'Content-Length': str(len(parms))}, postdata=parms).addCallback(self._gotPageLogin).addErrback(self.errorLogin)
164 self.loginCallback = None
166 def errorLoad(self, error):
167 text = _("Could not load phonebook from FRITZ!Box - Error: %s") %error
170 def _gotPageLoad(self, html):
171 print "[FritzCallFBF] _gotPageLoad"
172 # workaround: exceptions in gotPage-callback were ignored
174 self.parseFritzBoxPhonebook(html)
176 import traceback, sys
177 traceback.print_exc(file=sys.stdout)
180 def loadFritzBoxPhonebook(self):
181 print "[FritzCallFBF] loadFritzBoxPhonebook"
182 if config.plugins.FritzCall.fritzphonebook.value:
183 print "[FritzCallFBF] loadFritzBoxPhonebook: logging in"
184 self.loginCallback = self._loadFritzBoxPhonebook
187 def _loadFritzBoxPhonebook(self):
188 host = "%d.%d.%d.%d" %tuple(config.plugins.FritzCall.hostname.value)
189 uri = "/cgi-bin/webcm"# % tuple(config.plugins.FritzCall.hostname.value)
190 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de','var:pagename':'fonbuch','var:menu':'fon'})
191 url = "http://%s%s?%s" %(host, uri, parms)
193 getPage(url).addCallback(self._gotPageLoad).addErrback(self.errorLoad)
195 def parseFritzBoxPhonebook(self, html):
196 print "[FritzCallFBF] parseFritzBoxPhonebook"
198 table = html2utf8(html.replace("\xa0"," ").decode("ISO-8859-1", "replace"))
199 if re.search('TrFonName', table):
200 #===============================================================================
201 # New Style: 7170 / 7270 (FW 54.04.58, 54.04.63-11941)
202 # We expect one line with TrFonName followed by several lines with
203 # TrFonNr(Type,Number,Shortcut,Vanity), which all belong to the name in TrFonName.
204 #===============================================================================
205 # entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]+"\);</SCRIPT>\s+[<SCRIPT type=text/javascript>TrFonNr\("[^"]+", "[^"]+", "[^"]+", "[^"]+"\);</SCRIPT>\s+]+)<SCRIPT type=text/javascript>document.write(TrFon1());</SCRIPT>', re.DOTALL)
206 # entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]+"\);.*?[.*?TrFonNr\("[^"]+", "[^"]+", "[^"]+", "[^"]+"\);.*?]+).*?document.write(TrFon1());', re.DOTALL)
207 entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]*"\);.*?)TrFon1\(\)', re.S)
208 entries = entrymask.finditer(html)
209 for entry in entries:
210 # print entry.group(1)
211 found = re.match('TrFonName\("[^"]*", "([^"]+)", "[^"]*"\);', entry.group(1))
213 name = found.group(1)
216 detailmask = re.compile('TrFonNr\("([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\);', re.S)
217 details = detailmask.finditer(entry.group(1))
218 for found in details:
221 type = found.group(1)
222 if config.plugins.FritzCall.showType.value:
224 thisname = thisname + " (" +_("mobile") + ")"
226 thisname = thisname + " (" +_("home") + ")"
228 thisname = thisname + " (" +_("work") + ")"
230 if config.plugins.FritzCall.showShortcut.value and found.group(3):
231 thisname = thisname + ", " + _("Shortcut") + ": " + found.group(3)
232 if config.plugins.FritzCall.showVanity.value and found.group(4):
233 thisname = thisname + ", " + _("Vanity") + ": " + found.group(4)
235 thisnumber = found.group(2).strip()
236 thisname = html2utf8(thisname.strip())
238 print "[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" %(thisname, thisnumber)
239 phonebook.phonebook[thisnumber] = thisname
241 print "[FritzCallFBF] ignoring empty number for %s" %thisname
244 elif re.search('TrFon', table):
245 #===============================================================================
246 # Old Style: 7050 (FW 14.04.33)
247 # We expect one line with TrFon(No,Name,Number,Shortcut,Vanity)
248 #===============================================================================
249 entrymask = re.compile('TrFon\("[^"]*", "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\)', re.S)
250 entries = entrymask.finditer(html)
251 for found in entries:
252 name = found.group(1).strip()
253 thisnumber = found.group(2).strip()
254 if config.plugins.FritzCall.showShortcut.value and found.group(3):
255 name = name + ", " + _("Shortcut") + ": " + found.group(3)
256 if config.plugins.FritzCall.showVanity.value and found.group(4):
257 name = name + ", " +_("Vanity") +": " + found.group(4)
259 name = html2utf8(name)
260 print "[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" %(name, thisnumber)
261 phonebook.phonebook[thisnumber] = name
263 print "[FritzCallFBF] ignoring empty number for %s" %name
266 self.notify(_("Could not parse FRITZ!Box Phonebook entry"))
268 def errorCalls(self, error):
269 text = _("Could not load calls from FRITZ!Box - Error: %s") %error
272 def _gotPageCalls(self, csv = ""):
273 def _resolveNumber(number):
275 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0]=="0": number = number[1:]
276 name = phonebook.search(number)
278 found = re.match('(.*?)\n.*', name)
280 name = found.group(1)
283 number = _("UNKNOWN")
284 # if len(number) > 20: number = number[:20]
288 print "[FritzCallFBF] _gotPageCalls: got csv, setting callList"
290 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("done"))
291 # check for error: wrong password or password not set... TODO
292 found = re.search('Melden Sie sich mit dem Kennwort der FRITZ!Box an', csv)
294 text = _("You need to set the password of the FRITZ!Box\nin the configuration dialog to display calls\n\nIt could be a communication issue, just try again.")
295 # self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
299 csv = csv.decode('iso-8859-1','replace').encode('utf-8','replace')
300 lines = csv.splitlines()
301 self.callList = lines
303 print "[FritzCallFBF] _gotPageCalls: got no csv, but have callList"
305 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("done, using last list"))
306 lines = self.callList
308 print "[FritzCallFBF] _gotPageCalls: got no csv, no callList, leaving"
315 # Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer
316 found = re.match("^(" + self.callType + ");([^;]*);([^;]*);([^;]*);([^;]*);([^;]*)", line)
318 direct = found.group(1)
319 date = found.group(2)
320 if direct != FBF_OUT_CALLS and found.group(3):
321 caller = found.group(3)
323 caller = _resolveNumber(found.group(4))
324 found1 = re.match('Internet: (.*)', found.group(6))
326 callee = _resolveNumber(found1.group(1))
328 callee = _resolveNumber(found.group(6))
329 while (len(caller) + len(callee)) > 40:
330 if len(caller) > len(callee):
334 found = re.match("(\d\d.\d\d.)\d\d( \d\d:\d\d)", date)
335 if found: date = found.group(1) + found.group(2)
336 if direct == FBF_OUT_CALLS:
337 text = text + date + "\t" + callee + " -> " + caller + "\n"
339 text = text + date + "\t" + caller + " -> " + callee + "\n"
342 # print "[FritzCallFBF] _gotPageCalls result:\n" + text
344 if self.Callback is not None:
345 # print "[FritzCallFBF] _gotPageCalls call callback with\n" + text
346 self.Callback(text = text, count = noCalls)
348 self.callScreen = None
350 def getCalls(self, callScreen, callback, type):
352 # call sequence must be:
354 # - getPage -> _gotPageLogin
355 # - loginCallback (_getCalls)
356 # - getPage -> _getCalls1
357 print "[FritzCallFBF] getCalls"
358 self.callScreen = callScreen
360 self.Callback = callback
361 if (time.time() - self.timestamp) > 180:
362 print "[FritzCallFBF] getCalls: outdated data, login and get new ones"
363 self.timestamp = time.time()
364 self.loginCallback = self._getCalls
366 elif not self.callList:
367 print "[FritzCallFBF] getCalls: time is ok, but no callList"
370 print "[FritzCallFBF] getCalls: time is ok, callList is ok"
375 # we need this to fill Anrufliste.csv
376 # http://repeater1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=foncalls
378 print "[FritzCallFBF] _getCalls"
380 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("preparing"))
381 host = "%d.%d.%d.%d" %tuple(config.plugins.FritzCall.hostname.value)
382 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de','var:pagename':'foncalls','var:menu':'fon'})
383 url = "http://%s/cgi-bin/webcm?%s" %(host, parms)
384 getPage(url).addCallback(self._getCalls1).addErrback(self.errorCalls)
386 def _getCalls1(self, html = ""):
388 # finally we should have successfully lgged in and filled the csv
390 print "[FritzCallFBF] _getCalls1"
392 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("finishing"))
393 host = "%d.%d.%d.%d" %tuple(config.plugins.FritzCall.hostname.value)
394 parms = urlencode({'getpage':'../html/de/FRITZ!Box_Anrufliste.csv'})
395 url = "http://%s/cgi-bin/webcm?%s" %(host, parms)
396 getPage(url).addCallback(self._gotPageCalls).addErrback(self.errorCalls)
398 fritzbox = FritzCallFBF()
400 class FritzDisplayCalls(Screen):
403 <screen name="FritzDisplayCalls" position="100,90" size="550,420" title="%s" >
404 <widget name="statusbar" position="0,0" size="550,22" font="Regular;21" />
405 <widget name="list" position="0,22" size="550,358" font="Regular;19" />
406 <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
407 <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
408 <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
409 <ePixmap position="425,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
410 <widget name="key_red" position="5,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
411 <widget name="key_green" position="145,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
412 <widget name="key_yellow" position="285,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
413 <widget name="key_blue" position="425,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
414 </screen>""" % _("Phone calls")
416 def __init__(self, session, text = ""):
417 self.skin = FritzDisplayCalls.skin
418 Screen.__init__(self, session)
420 # TRANSLATORS: keep it short, this is a button
421 self["key_red"] = Button(_("All"))
422 # TRANSLATORS: keep it short, this is a button
423 self["key_green"] = Button(_("Missed"))
424 # TRANSLATORS: keep it short, this is a button
425 self["key_yellow"] = Button(_("Incoming"))
426 # TRANSLATORS: keep it short, this is a button
427 self["key_blue"] = Button(_("Outgoing"))
429 self["setupActions"] = ActionMap(["OkCancelActions", "DirectionActions", "ColorActions"],
431 "red": self.displayAllCalls,
432 "green": self.displayMissedCalls,
433 "yellow": self.displayInCalls,
434 "blue": self.displayOutCalls,
435 "down": self.pageDown,
437 "right": self.pageDown,
443 self["statusbar"] = Label(_("Getting calls from FRITZ!Box..."))
444 self["list"] = ScrollLabel("")
445 print "[FritzDisplayCalls] init: '''%s'''" %config.plugins.FritzCall.fbfCalls.value
452 self["list"].pageDown()
455 self["list"].pageUp()
457 def displayAllCalls(self):
458 print "[FritzDisplayCalls] displayAllCalls"
459 config.plugins.FritzCall.fbfCalls.value = FBF_ALL_CALLS
460 config.plugins.FritzCall.fbfCalls.save()
463 def displayMissedCalls(self):
464 print "[FritzDisplayCalls] displayMissedCalls"
465 config.plugins.FritzCall.fbfCalls.value = FBF_MISSED_CALLS
466 config.plugins.FritzCall.fbfCalls.save()
469 def displayInCalls(self):
470 print "[FritzDisplayCalls] displayInCalls"
471 config.plugins.FritzCall.fbfCalls.value = FBF_IN_CALLS
472 config.plugins.FritzCall.fbfCalls.save()
475 def displayOutCalls(self):
476 print "[FritzDisplayCalls] displayOutCalls"
477 config.plugins.FritzCall.fbfCalls.value = FBF_OUT_CALLS
478 config.plugins.FritzCall.fbfCalls.save()
481 def displayCalls(self):
482 print "[FritzDisplayCalls] displayCalls"
483 self.header = fbfCallsChoices[config.plugins.FritzCall.fbfCalls.value]
484 fritzbox.getCalls(self, self.gotCalls, config.plugins.FritzCall.fbfCalls.value)
486 def gotCalls(self, text, count):
487 # print "[FritzDisplayCalls] gotCalls:\n" + text
488 self["statusbar"].setText(self.header + " (" + str(count) + ")")
489 self["list"].setText(text)
491 def updateStatus(self, text):
492 self["statusbar"].setText(text)
495 class FritzCallPhonebook:
502 f = open(config.plugins.FritzCall.phonebookLocation.value, 'w')
503 f.write("01234567890#Name, Street, Location (Keep the Spaces!!!)\n");
510 print "[FritzCallPhonebook] reload"
511 self.phonebook.clear()
513 if not config.plugins.FritzCall.enable.value:
518 if config.plugins.FritzCall.phonebook.value:
519 if not os_path.exists(config.plugins.FritzCall.phonebookLocation.value):
526 for line in open(config.plugins.FritzCall.phonebookLocation.value):
528 number, name = line.split("#")
529 if not self.phonebook.has_key(number):
530 self.phonebook[number] = name
532 print "[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" %line
534 if config.plugins.FritzCall.fritzphonebook.value:
535 fritzbox.loadFritzBoxPhonebook()
537 def search(self, number):
538 # print "[FritzCallPhonebook] Searching for %s" %number
540 if config.plugins.FritzCall.phonebook.value or config.plugins.FritzCall.fritzphonebook.value:
541 if self.phonebook.has_key(number):
542 name = self.phonebook[number].replace(", ", "\n").strip()
545 def add(self, number, name):
546 print "[FritzCallPhonebook] add"
547 #===============================================================================
548 # It could happen, that two reverseLookups are running in parallel,
549 # so check first, whether we have already added the number to the phonebook.
550 #===============================================================================
551 if phonebook.search(number) is None and number <> 0 and config.plugins.FritzCall.phonebook.value and config.plugins.FritzCall.addcallers.value:
553 f = open(config.plugins.FritzCall.phonebookLocation.value, 'a')
554 name = name.strip() + "\n"
555 string = "%s#%s" %(number, name)
556 self.phonebook[number] = name;
559 print "[FritzCallPhonebook] added %s with %sto Phonebook.txt" %(number, name)
565 phonebook = FritzCallPhonebook()
567 class FritzCallSetup(ConfigListScreen, Screen):
569 <screen position="100,90" size="550,420" title="%s" >
570 <widget name="config" position="20,10" size="510,300" scrollbarMode="showOnDemand" />
571 <widget name="consideration" position="20,320" font="Regular;20" halign="center" size="510,50" />
572 <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
573 <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
574 <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
575 <widget name="key_red" position="5,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
576 <widget name="key_green" position="145,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
577 <widget name="key_yellow" position="285,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
578 </screen>""" % _("FritzCall Setup")
580 def __init__(self, session, args = None):
582 Screen.__init__(self, session)
583 self.session = session
585 self["consideration"] = Label(_("You need to enable the monitoring on your FRITZ!Box by dialing #96*5*!"))
589 # TRANSLATORS: keep it short, this is a button
590 self["key_red"] = Button(_("Cancel"))
591 # TRANSLATORS: keep it short, this is a button
592 self["key_green"] = Button(_("OK"))
593 # TRANSLATORS: keep it short, this is a button
594 self["key_yellow"] = Button(_("Phone calls"))
596 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
598 "cancel": self.cancel,
599 "red": self.cancel, # not strictly needed, better for clarity
601 "green": self.save, # not strictly needed, better for clarity
602 "yellow": self.displayCalls,
606 ConfigListScreen.__init__(self, self.list, session = session)
611 ConfigListScreen.keyLeft(self)
615 ConfigListScreen.keyRight(self)
618 def createSetup(self):
620 self.list.append(getConfigListEntry(_("Call monitoring"), config.plugins.FritzCall.enable))
621 if config.plugins.FritzCall.enable.value:
622 self.list.append(getConfigListEntry(_("FRITZ!Box FON IP address"), config.plugins.FritzCall.hostname))
624 self.list.append(getConfigListEntry(_("Show after Standby"), config.plugins.FritzCall.afterStandby))
626 self.list.append(getConfigListEntry(_("Show Calls for specific MSN"), config.plugins.FritzCall.filter))
627 if config.plugins.FritzCall.filter.value:
628 self.list.append(getConfigListEntry(_("MSN to show (separated by ,)"), config.plugins.FritzCall.filtermsn))
630 self.list.append(getConfigListEntry(_("Show Outgoing Calls"), config.plugins.FritzCall.showOutgoing))
631 if config.plugins.FritzCall.showOutgoing.value:
632 self.list.append(getConfigListEntry(_("Areacode to add to Outgoing Calls (if necessary)"), config.plugins.FritzCall.prefix))
633 self.list.append(getConfigListEntry(_("Timeout for Call Notifications (seconds)"), config.plugins.FritzCall.timeout))
634 self.list.append(getConfigListEntry(_("Reverse Lookup Caller ID (select country below)"), config.plugins.FritzCall.lookup))
635 if config.plugins.FritzCall.lookup.value:
636 self.list.append(getConfigListEntry(_("Country"), config.plugins.FritzCall.country))
638 self.list.append(getConfigListEntry(_("Password Accessing FRITZ!Box"), config.plugins.FritzCall.password))
639 self.list.append(getConfigListEntry(_("Read PhoneBook from FRITZ!Box"), config.plugins.FritzCall.fritzphonebook))
640 if config.plugins.FritzCall.fritzphonebook.value:
641 self.list.append(getConfigListEntry(_("Append type of number"), config.plugins.FritzCall.showType))
642 self.list.append(getConfigListEntry(_("Append shortcut number"), config.plugins.FritzCall.showShortcut))
643 self.list.append(getConfigListEntry(_("Append vanity name"), config.plugins.FritzCall.showVanity))
645 self.list.append(getConfigListEntry(_("Use internal PhoneBook"), config.plugins.FritzCall.phonebook))
646 if config.plugins.FritzCall.phonebook.value:
647 self.list.append(getConfigListEntry(_("PhoneBook Location"), config.plugins.FritzCall.phonebookLocation))
648 if config.plugins.FritzCall.lookup.value:
649 self.list.append(getConfigListEntry(_("Automatically add new Caller to PhoneBook"), config.plugins.FritzCall.addcallers))
651 self.list.append(getConfigListEntry(_("Strip Leading 0"), config.plugins.FritzCall.internal))
652 # self.list.append(getConfigListEntry(_("Default display mode for FRITZ!Box calls"), config.plugins.FritzCall.fbfCalls))
654 self["config"].list = self.list
655 self["config"].l.setList(self.list)
658 # print "[FritzCallSetup] save"
659 for x in self["config"].list:
661 if fritz_call is not None:
664 if config.plugins.FritzCall.phonebook.value:
665 if not os_path.exists(config.plugins.FritzCall.phonebookLocation.value):
666 if not phonebook.create():
667 Notifications.AddNotification(MessageBox, _("Can't create PhoneBook.txt"), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
669 print "[FritzCallSetup] called phonebook.reload()"
675 # print "[FritzCallSetup] cancel"
676 for x in self["config"].list:
680 def displayCalls(self):
681 self.session.open(FritzDisplayCalls)
690 def add(self, event, date, number, caller, phone):
691 print "[FritzCallList] add"
692 if len(self.callList) > 10:
693 if self.callList[0] != "Start":
694 self.callList[0] = "Start"
697 self.callList.append((event, number, date, caller, phone))
700 print "[FritzCallList] display"
702 global my_global_session
704 # Standby.inStandby.onClose.remove(self.display) object does not exist anymore...
705 # build screen from call list
707 if self.callList[0] == "Start":
708 text = text + _("Last 10 calls:\n")
711 for call in self.callList:
712 (event, number, date, caller, phone) = call
717 found = re.match(".*(\d\d.\d\d.)\d\d( \d\d:\d\d)", date)
718 if found: date = found.group(1) + found.group(2)
719 found = re.match(".*\((.*)\)", phone)
720 if found: phone = found.group(1)
721 # if len(phone) > 20: phone = phone[:20]
723 if caller == _("UNKNOWN") and number != "":
726 found = re.match("(.*)\n.*", caller)
727 if found: caller = found.group(1)
728 # if len(caller) > 20: caller = caller[:20]
729 while (len(caller) + len(phone)) > 40:
730 if len(caller) > len(phone):
735 text = text + "%s %s %s %s\n" %(date, caller, direction, phone)
737 print "[FritzCallList] display: '%s %s %s %s'" %(date, caller, direction, phone)
739 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO)
740 # my_global_session.open(FritzDisplayCalls, text) # TODO please HELP: from where can I get a session?
744 callList = FritzCallList()
746 def notifyCall(event, date, number, caller, phone):
747 if Standby.inStandby is None or config.plugins.FritzCall.afterStandby.value == "each":
749 text = _("Incoming Call on %(date)s from\n---------------------------------------------\n%(number)s\n%(caller)s\n---------------------------------------------\nto: %(phone)s") % { 'date':date, 'number':number, 'caller':caller, 'phone':phone }
751 text = _("Outgoing Call on %(date)s to\n---------------------------------------------\n%(number)s\n%(caller)s\n---------------------------------------------\nfrom: %(phone)s") % { 'date':date, 'number':number, 'caller':caller, 'phone':phone }
752 print "[FritzCall] notifyCall:\n%s" %text
753 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
754 elif config.plugins.FritzCall.afterStandby.value == "inList":
756 # if not yet done, register function to show call list
760 Standby.inStandby.onHide.append(callList.display)
761 # add text/timeout to call list
762 callList.add(event, date, number, caller, phone)
763 print "[FritzCall] notifyCall: added to callList"
764 else: # this is the "None" case
765 print "[FritzCall] notifyCall: standby and no show"
768 #===============================================================================
769 # We need a separate class for each invocation of reverseLookup to retain
770 # the necessary data for the notification
771 #===============================================================================
775 class FritzReverseLookupAndNotifier:
776 def __init__(self, event, number, caller, phone, date):
777 print "[FritzReverseLookupAndNotifier] reverse Lookup for %s!" %number
783 self.currentWebsite = None
784 self.nextWebsiteNo = 0
787 dom = parse(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/reverselookup.xml"))
788 for top in dom.getElementsByTagName("reverselookup"):
789 for country in top.getElementsByTagName("country"):
790 code = country.getAttribute("code").replace("+","00")
791 countries[code] = country.getElementsByTagName("website")
793 self.countrycode = config.plugins.FritzCall.country.value
796 self.caller = _("UNKNOWN")
797 self.notifyAndReset()
800 if self.number[:2] == "00":
801 if countries.has_key(self.number[:3]): # e.g. USA
802 self.countrycode = self.number[:3]
803 elif countries.has_key(self.number[:4]):
804 self.countrycode = self.number[:4]
805 elif countries.has_key(self.number[:5]):
806 self.countrycode = self.number[:5]
808 print "[FritzReverseLookupAndNotifier] Country cannot be reverse handled"
809 self.caller = _("UNKNOWN")
810 self.notifyAndReset()
813 if countries.has_key(self.countrycode):
814 print "[FritzReverseLookupAndNotifier] Found website for reverse lookup"
815 self.websites = countries[self.countrycode]
816 self.nextWebsiteNo = 1
817 self.handleWebsite(self.websites[0])
819 print "[FritzReverseLookupAndNotifier] Country cannot be reverse handled"
820 self.caller = _("UNKNOWN")
821 self.notifyAndReset()
824 def handleWebsite(self, website):
825 print "[FritzReverseLookupAndNotifier] handleWebsite: " + website.getAttribute("name")
826 if self.number[:2] == "00":
827 number = website.getAttribute("prefix") + self.number.replace(self.countrycode,"")
831 url = website.getAttribute("url")
832 if re.search('$AREACODE',url) or re.search('$PFXAREACODE',url):
833 print "[FritzReverseLookupAndNotifier] handleWebsite: (PFX)ARECODE cannot be handled"
834 self.caller = _("UNKNOWN")
835 self.notifyAndReset()
838 # Apparently, there is no attribute called (pfx)areacode anymore
839 # So, this below will not work.
841 if re.search('\\$AREACODE',url) and website.hasAttribute("areacode"):
842 areaCodeLen = int(website.getAttribute("areacode"))
843 url = url.replace("$AREACODE","%(areacode)s").replace("$NUMBER","%(number)s")
844 url = url %{ 'areacode':number[:areaCodeLen], 'number':number[areaCodeLen:] }
845 elif re.search('\\$PFXAREACODE',url) and website.hasAttribute("pfxareacode"):
846 areaCodeLen = int(website.getAttribute("pfxareacode"))
847 url = url.replace("$PFXAREACODE","%(pfxareacode)s").replace("$NUMBER","%(number)s")
848 url = url %{ 'pfxareacode':number[:areaCodeLen], 'number':number[areaCodeLen:] }
849 elif re.search('\\$NUMBER',url):
850 url = url.replace("$NUMBER","%s") %number
852 print "[FritzReverseLookupAndNotifier] handleWebsite: cannot handle websites with no $NUMBER in url"
853 self.caller = _("UNKNOWN")
854 self.notifyAndReset()
856 print "[FritzReverseLookupAndNotifier] Url to query: " + url
857 url = url.encode("UTF-8", "replace")
858 self.currentWebsite = website
859 getPage(url, method="GET").addCallback(self._gotPage).addErrback(self._gotError)
861 def _gotPage(self, page):
862 print "[FritzReverseLookupAndNotifier] _gotPage"
863 found = re.match('.*content=".*?charset=([^"]+)"',page,re.S)
865 print "[FritzReverseLookupAndNotifier] Charset: " + found.group(1)
866 page = page.replace("\xa0"," ").decode(found.group(1), "replace")
868 page = page.replace("\xa0"," ").decode("ISO-8859-1", "replace")
870 for entry in self.currentWebsite.getElementsByTagName("entry"):
871 # print "[FritzReverseLookupAndNotifier] _gotPage: try entry"
873 for what in ["name", "street", "city", "zipcode"]:
874 pat = "(.*)" + self.getPattern(entry, what)
875 # print "[FritzReverseLookupAndNotifier] _gotPage: look for '''%s''' with '''%s'''" %( what, pat )
876 found = re.match(pat, page, re.S|re.M)
878 # print "[FritzReverseLookupAndNotifier] _gotPage: found for '''%s''': '''%s'''" %( what, found.group(2) )
879 item = found.group(2).replace(" "," ").replace("</b>","").replace(",","")
880 item = html2utf8(item)
881 details.append(item.strip())
882 # print "[FritzReverseLookupAndNotifier] _gotPage: got '''%s''': '''%s'''" %( what, item.strip() )
886 if len(details) != 4:
890 address = details[1] + ", " + details[3] + " " + details[2]
891 print "[FritzReverseLookupAndNotifier] _gotPage: Reverse lookup succeeded:\nName: %s\nAddress: %s" %(name, address)
892 self.caller = "%s, %s" %(name, address)
893 if self.number != 0 and config.plugins.FritzCall.addcallers.value and self.event == "RING":
894 phonebook.add(self.number, self.caller)
896 self.caller = self.caller.replace(", ", "\n").encode("UTF-8", "replace")
897 self.notifyAndReset()
901 self._gotError("[FritzReverseLookupAndNotifier] _gotPage: Nothing found at %s" %self.currentWebsite.getAttribute("name"))
903 def _gotError(self, error = ""):
904 print "[FritzReverseLookupAndNotifier] _gotError - Error: %s" %error
905 if self.nextWebsiteNo >= len(self.websites):
906 print "[FritzReverseLookupAndNotifier] _gotError: I give up"
907 self.caller = _("UNKNOWN")
908 self.notifyAndReset()
911 print "[FritzReverseLookupAndNotifier] _gotError: try next website"
912 self.nextWebsiteNo = self.nextWebsiteNo+1
913 self.handleWebsite(self.websites[self.nextWebsiteNo-1])
915 def getPattern(self, website, which):
916 pat1 = website.getElementsByTagName(which)
918 print "Something strange: more than one %s for website %s" %(which, website.getAttribute("name"))
919 return pat1[0].childNodes[0].data
921 def notifyAndReset(self, timeout=config.plugins.FritzCall.timeout.value):
922 notifyCall(self.event, self.date, self.number, self.caller, self.phone)
923 # kill that object...
926 class FritzProtocol(LineReceiver):
928 print "[FritzProtocol] __init__"
931 def resetValues(self):
932 print "[FritzProtocol] resetValues"
938 def notifyAndReset(self, timeout=config.plugins.FritzCall.timeout.value):
939 notifyCall(self.event, self.date, self.number, self.caller, self.phone)
942 def lineReceived(self, line):
943 print "[FritzProtocol] lineReceived: %s" %line
944 #15.07.06 00:38:54;CALL;1;4;<from/extern>;<to/our msn>;
945 #15.07.06 00:38:58;DISCONNECT;1;0;
946 #15.07.06 00:39:22;RING;0;<from/extern>;<to/our msn>;
947 #15.07.06 00:39:27;DISCONNECT;0;0;
950 (self.date, self.event) = a[0:2]
952 if self.event == "RING" or (self.event == "CALL" and config.plugins.FritzCall.showOutgoing.value):
955 if self.event == "RING":
960 print "[FritzProtocol] lineReceived phone: '''%s''' number: '''%s'''" % (phone, number)
962 filtermsns = config.plugins.FritzCall.filtermsn.value.split(",")
963 for i in range(len(filtermsns)):
964 filtermsns[i] = filtermsns[i].strip()
965 if not (config.plugins.FritzCall.filter.value and phone not in filtermsns):
966 print "[FritzProtocol] lineReceived no filter hit"
967 phonename = phonebook.search(phone) # do we have a name for the number of our side?
968 if phonename is not None:
969 self.phone = "%s (%s)" %(phone, phonename)
973 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0]=="0":
974 self.number = number[1:]
978 if self.event == "CALL" and self.number[0] != '0': # should only happen for outgoing
979 self.number = config.plugins.FritzCall.prefix.value + self.number
981 if self.number is not "":
982 print "[FritzProtocol] lineReceived phonebook.search: %s" %self.number
983 self.caller = phonebook.search(self.number)
984 print "[FritzProtocol] lineReceived phonebook.search reault: %s" %self.caller
985 if (self.caller is None) and config.plugins.FritzCall.lookup.value:
986 FritzReverseLookupAndNotifier(self.event, self.number, self.caller, self.phone, self.date)
987 return # reverselookup is supposed to handle the message itself
989 if self.caller is None:
990 self.caller = _("UNKNOWN")
992 self.notifyAndReset()
994 class FritzClientFactory(ReconnectingClientFactory):
999 self.hangup_ok = False
1001 def startedConnecting(self, connector):
1002 Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box..."), type=MessageBox.TYPE_INFO, timeout=2)
1004 def buildProtocol(self, addr):
1005 Notifications.AddNotification(MessageBox, _("Connected to FRITZ!Box!"), type=MessageBox.TYPE_INFO, timeout=4)
1007 return FritzProtocol()
1009 def clientConnectionLost(self, connector, reason):
1010 if not self.hangup_ok:
1011 Notifications.AddNotification(MessageBox, _("Connection to FRITZ!Box! lost\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1012 ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
1014 def clientConnectionFailed(self, connector, reason):
1015 Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box failed\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1016 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
1026 if config.plugins.FritzCall.enable.value:
1027 f = FritzClientFactory()
1028 self.d = (f, reactor.connectTCP("%d.%d.%d.%d" % tuple(config.plugins.FritzCall.hostname.value), 1012, f))
1034 if self.d is not None:
1035 self.d[0].hangup_ok = True
1036 self.d[0].stopTrying()
1037 self.d[1].disconnect()
1040 def displayCalls(session, servicelist):
1041 session.open(FritzDisplayCalls)
1044 session.open(FritzCallSetup)
1048 def autostart(reason, **kwargs):
1051 # ouch, this is a hack
1052 if kwargs.has_key("session"):
1053 global my_global_session
1054 my_global_session = kwargs["session"]
1057 print "[FRITZ!Call] - Autostart"
1059 fritz_call = FritzCall()
1061 fritz_call.shutdown()
1064 def Plugins(**kwargs):
1065 what = _("Display FRITZ!box-Fon calls on screen")
1066 what_calls = _("Phone calls")
1067 return [ PluginDescriptor(name="FritzCall", description=what, where = PluginDescriptor.WHERE_PLUGINMENU, icon = "plugin.png", fnc=main),
1068 PluginDescriptor(name=what_calls, description=what_calls, where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayCalls),
1069 PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart) ]