1 # -*- coding: utf-8 -*-
2 #===============================================================================
6 #==============================
7 from Screens.Screen import Screen
8 from Screens.MessageBox import MessageBox
9 from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
10 from Screens.InputBox import InputBox
11 from Screens import Standby
12 from Screens.HelpMenu import HelpableScreen
14 from enigma import eListboxPythonMultiContent, gFont, RT_HALIGN_LEFT
16 from Components.MenuList import MenuList
17 from Components.ActionMap import ActionMap
18 from Components.Label import Label
19 from Components.Button import Button
20 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigEnableDisable, getConfigListEntry, ConfigText, ConfigInteger, ConfigPassword
21 from Components.ConfigList import ConfigListScreen
23 from Plugins.Plugin import PluginDescriptor
24 from Tools import Notifications
25 from Tools.NumericalTextInput import NumericalTextInput
27 from twisted.internet import reactor
28 from twisted.internet.protocol import ReconnectingClientFactory
29 from twisted.protocols.basic import LineReceiver
30 from twisted.web.client import getPage
32 from xml.dom.minidom import parse
34 from urllib import urlencode
38 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
40 _ = gettext.translation('FritzCall', resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/locale"), [config.osd.language.getText()]).gettext
45 my_global_session = None
47 config.plugins.FritzCall = ConfigSubsection()
48 config.plugins.FritzCall.enable = ConfigEnableDisable(default = False)
49 # config.plugins.FritzCall.hostname = ConfigIP(default = [192, 168, 178, 1])
50 config.plugins.FritzCall.hostname = ConfigText(default = "fritz.box", fixed_size = False)
51 config.plugins.FritzCall.afterStandby = ConfigSelection(choices = [("none", _("show nothing")), ("inList", _("show as list")), ("each", _("show each call"))])
52 config.plugins.FritzCall.filter = ConfigEnableDisable(default = False)
53 config.plugins.FritzCall.filtermsn = ConfigText(default = "", fixed_size = False)
54 config.plugins.FritzCall.filtermsn.setUseableChars('0123456789,')
55 config.plugins.FritzCall.showOutgoing = ConfigEnableDisable(default = False)
56 config.plugins.FritzCall.timeout = ConfigInteger(default = 15, limits = (0,60))
57 config.plugins.FritzCall.lookup = ConfigEnableDisable(default = False)
58 config.plugins.FritzCall.internal = ConfigEnableDisable(default = False)
59 config.plugins.FritzCall.fritzphonebook = ConfigEnableDisable(default = False)
60 config.plugins.FritzCall.phonebook = ConfigEnableDisable(default = False)
61 config.plugins.FritzCall.addcallers = ConfigEnableDisable(default = False)
62 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"))])
63 config.plugins.FritzCall.password = ConfigPassword(default = "", fixed_size = False)
64 config.plugins.FritzCall.showType = ConfigEnableDisable(default = True)
65 config.plugins.FritzCall.showShortcut = ConfigEnableDisable(default = False)
66 config.plugins.FritzCall.showVanity = ConfigEnableDisable(default = False)
67 config.plugins.FritzCall.prefix = ConfigText(default = "", fixed_size = False)
68 config.plugins.FritzCall.prefix.setUseableChars('0123456789')
71 ("0049", _("Germany")),
72 ("0031", _("The Netherlands")),
73 ("0033", _("France")),
75 ("0041", _("Switzerland")),
76 ("0043", _("Austria"))
78 config.plugins.FritzCall.country = ConfigSelection(choices = countryCodes)
82 FBF_MISSED_CALLS = "2"
84 fbfCallsChoices = {FBF_ALL_CALLS: _("All calls"),
85 FBF_IN_CALLS: _("Incoming calls"),
86 FBF_MISSED_CALLS: _("Missed calls"),
87 FBF_OUT_CALLS: _("Outgoing calls")
89 config.plugins.FritzCall.fbfCalls = ConfigSelection(choices = fbfCallsChoices)
91 config.plugins.FritzCall.name = ConfigText(default = "", fixed_size = False)
92 config.plugins.FritzCall.number= ConfigText(default = "", fixed_size = False)
93 config.plugins.FritzCall.number.setUseableChars('0123456789')
96 def html2utf8(in_html):
100 # TODO: first convert some WML codes; does not work?!?!
101 # in_html = in_html.replace("ß;", "ß").replace("ä", "ä").replace("ö", "ö").replace("ü", "ü").replace("Ä", "Ä").replace("Ö", "Ö").replace("Ü", "Ü")
103 htmlentitynamemask = re.compile('(&(\D{1,5}?);)')
105 entities = htmlentitynamemask.finditer(in_html)
107 entitydict[x.group(1)] = x.group(2)
108 for key, name in entitydict.items():
110 entitydict[key] = htmlentitydefs.name2codepoint[name]
112 print "[FritzCallhtml2utf8] KeyError " + key + "/" + name
115 htmlentitynumbermask = re.compile('(&#(\d{1,5}?);)')
116 entities = htmlentitynumbermask.finditer(in_html)
118 entitydict[x.group(1)] = x.group(2)
119 for key, codepoint in entitydict.items():
121 in_html = in_html.replace(key, (unichr(int(codepoint)).encode('utf8', "replace")))
123 print "[FritzCallhtml2utf8] ValueError " + key + "/" + str(codepoint)
127 return in_html.replace("&", "&").replace("ß", "ß").replace("ä", "ä").replace("ö", "ö").replace("ü", "ü").replace("Ä", "Ä").replace("Ö", "Ö").replace("Ü", "Ü")
128 except UnicodeDecodeError:
135 print "[FritzCallFBF] __init__"
136 self.callScreen= None
137 self.loggedIn = False
139 self.loginCallback = None
142 self.callType = config.plugins.FritzCall.fbfCalls.value
144 def notify(self, text):
145 print "[FritzCallFBF] notify"
147 print "[FritzCallFBF] notify: try to close callScreen"
148 self.callScreen.close()
149 self.callScreen = None
150 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
152 def errorLogin(self, error):
153 text = _("FRITZ!Box Login failed! - Error: %s") %error
156 def _gotPageLogin(self, html):
157 # print "[FritzCallPhonebook] _gotPageLogin"
158 # workaround: exceptions in gotPage-callback were ignored
160 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login verification"))
162 print "[FritzCallFBF] _gotPageLogin: verify login"
163 found = re.match('.*<p class="errorMessage">FEHLER: Das angegebene Kennwort', html, re.S)
165 text = _("FRITZ!Box Login failed! - Wrong Password!")
169 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login ok"))
173 import traceback, sys
174 traceback.print_exc(file=sys.stdout)
178 print "[FritzCallFBF] Login"
179 if config.plugins.FritzCall.password.value != "":
181 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login"))
182 parms = "login:command/password=%s" %(config.plugins.FritzCall.password.value)
183 url = "http://%s/cgi-bin/webcm" %(config.plugins.FritzCall.hostname.value)
184 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)
187 self.loginCallback = None
189 def errorLoad(self, error):
190 text = _("Could not load phonebook from FRITZ!Box - Error: %s") %error
193 def _gotPageLoad(self, html):
194 print "[FritzCallFBF] _gotPageLoad"
195 # workaround: exceptions in gotPage-callback were ignored
197 self.parseFritzBoxPhonebook(html)
199 import traceback, sys
200 traceback.print_exc(file=sys.stdout)
203 def loadFritzBoxPhonebook(self):
204 print "[FritzCallFBF] loadFritzBoxPhonebook"
205 if config.plugins.FritzCall.fritzphonebook.value:
206 print "[FritzCallFBF] loadFritzBoxPhonebook: logging in"
207 self.loginCallback = self._loadFritzBoxPhonebook
210 def _loadFritzBoxPhonebook(self):
211 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de','var:pagename':'fonbuch','var:menu':'fon'})
212 url = "http://%s/cgi-bin/webcm?%s" %(config.plugins.FritzCall.hostname.value, parms)
214 getPage(url).addCallback(self._gotPageLoad).addErrback(self.errorLoad)
216 def parseFritzBoxPhonebook(self, html):
217 print "[FritzCallFBF] parseFritzBoxPhonebook"
219 table = html2utf8(html.replace("\xa0"," ").decode("ISO-8859-1", "replace"))
220 if re.search('TrFonName', table):
221 #===============================================================================
222 # New Style: 7170 / 7270 (FW 54.04.58, 54.04.63-11941)
223 # We expect one line with TrFonName followed by several lines with
224 # TrFonNr(Type,Number,Shortcut,Vanity), which all belong to the name in TrFonName.
225 #===============================================================================
226 # entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]+"\);</SCRIPT>\s+[<SCRIPT type=text/javascript>TrFonNr\("[^"]+", "[^"]+", "[^"]+", "[^"]+"\);</SCRIPT>\s+]+)<SCRIPT type=text/javascript>document.write(TrFon1());</SCRIPT>', re.DOTALL)
227 # entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]+"\);.*?[.*?TrFonNr\("[^"]+", "[^"]+", "[^"]+", "[^"]+"\);.*?]+).*?document.write(TrFon1());', re.DOTALL)
228 entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]*"\);.*?)TrFon1\(\)', re.S)
229 entries = entrymask.finditer(html)
230 for entry in entries:
231 # print entry.group(1)
232 found = re.match('TrFonName\("[^"]*", "([^"]+)", "[^"]*"\);', entry.group(1))
234 name = found.group(1)
237 detailmask = re.compile('TrFonNr\("([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\);', re.S)
238 details = detailmask.finditer(entry.group(1))
239 for found in details:
242 type = found.group(1)
243 if config.plugins.FritzCall.showType.value:
245 thisname = thisname + " (" +_("mobile") + ")"
247 thisname = thisname + " (" +_("home") + ")"
249 thisname = thisname + " (" +_("work") + ")"
251 if config.plugins.FritzCall.showShortcut.value and found.group(3):
252 thisname = thisname + ", " + _("Shortcut") + ": " + found.group(3)
253 if config.plugins.FritzCall.showVanity.value and found.group(4):
254 thisname = thisname + ", " + _("Vanity") + ": " + found.group(4)
256 thisnumber = found.group(2).strip()
257 thisname = html2utf8(thisname.strip())
259 print "[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" %(thisname, thisnumber)
260 phonebook.phonebook[thisnumber] = thisname
262 print "[FritzCallFBF] ignoring empty number for %s" %thisname
265 elif re.search('TrFon', table):
266 #===============================================================================
267 # Old Style: 7050 (FW 14.04.33)
268 # We expect one line with TrFon(No,Name,Number,Shortcut,Vanity)
269 #===============================================================================
270 entrymask = re.compile('TrFon\("[^"]*", "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\)', re.S)
271 entries = entrymask.finditer(html)
272 for found in entries:
273 name = found.group(1).strip()
274 thisnumber = found.group(2).strip()
275 if config.plugins.FritzCall.showShortcut.value and found.group(3):
276 name = name + ", " + _("Shortcut") + ": " + found.group(3)
277 if config.plugins.FritzCall.showVanity.value and found.group(4):
278 name = name + ", " +_("Vanity") +": " + found.group(4)
280 name = html2utf8(name)
281 print "[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" %(name, thisnumber)
282 phonebook.phonebook[thisnumber] = name
284 print "[FritzCallFBF] ignoring empty number for %s" %name
287 self.notify(_("Could not parse FRITZ!Box Phonebook entry"))
289 def errorCalls(self, error):
290 text = _("Could not load calls from FRITZ!Box - Error: %s") %error
293 def _gotPageCalls(self, csv = ""):
294 def _resolveNumber(number):
296 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0]=="0": number = number[1:]
298 if config.plugins.FritzCall.country.value == '0049':
299 if re.match('^0100\d\d', number):
301 elif re.match('^010\d\d', number):
303 if config.plugins.FritzCall.prefix.value and number and number[0] != '0': # should only happen for outgoing
304 number = config.plugins.FritzCall.prefix.value + number
305 name = phonebook.search(number)
307 found = re.match('(.*?)\n.*', name)
309 name = found.group(1)
312 number = _("UNKNOWN")
313 # if len(number) > 20: number = number[:20]
317 print "[FritzCallFBF] _gotPageCalls: got csv, setting callList"
319 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("done"))
320 # check for error: wrong password or password not set... TODO
321 found = re.search('Melden Sie sich mit dem Kennwort der FRITZ!Box an', csv)
323 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.")
324 # self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
328 csv = csv.decode('iso-8859-1','replace').encode('utf-8','replace')
329 lines = csv.splitlines()
330 self.callList = lines
332 print "[FritzCallFBF] _gotPageCalls: got no csv, but have callList"
334 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("done, using last list"))
335 lines = self.callList
337 print "[FritzCallFBF] _gotPageCalls: got no csv, no callList, leaving"
343 # Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer
344 found = re.match("^(" + self.callType + ");([^;]*);([^;]*);([^;]*);([^;]*);([^;]*)", line)
346 direct = found.group(1)
347 date = found.group(2)
348 if direct != FBF_OUT_CALLS and found.group(3):
349 remote = found.group(3)
351 remote = _resolveNumber(found.group(4))
352 found1 = re.match('Internet: (.*)', found.group(6))
354 here = _resolveNumber(found1.group(1))
356 here = _resolveNumber(found.group(6))
358 # strip CbC prefix for Germany
359 number = found.group(4)
360 if config.plugins.FritzCall.country.value == '0049':
361 if re.match('^0100\d\d', number):
363 elif re.match('^010\d\d', number):
365 if config.plugins.FritzCall.prefix.value and number and number[0] != '0': # should only happen for outgoing
366 number = config.plugins.FritzCall.prefix.value + number
367 callList.append((number, date, here, direct, remote))
369 # print "[FritzCallFBF] _gotPageCalls result:\n" + text
371 if self.Callback is not None:
372 # print "[FritzCallFBF] _gotPageCalls call callback with\n" + text
373 self.Callback(callList)
375 self.callScreen = None
377 def getCalls(self, callScreen, callback, type):
379 # call sequence must be:
381 # - getPage -> _gotPageLogin
382 # - loginCallback (_getCalls)
383 # - getPage -> _getCalls1
384 print "[FritzCallFBF] getCalls"
385 self.callScreen = callScreen
387 self.Callback = callback
388 if (time.time() - self.timestamp) > 180:
389 print "[FritzCallFBF] getCalls: outdated data, login and get new ones"
390 self.timestamp = time.time()
391 self.loginCallback = self._getCalls
393 elif not self.callList:
394 print "[FritzCallFBF] getCalls: time is ok, but no callList"
397 print "[FritzCallFBF] getCalls: time is ok, callList is ok"
402 # we need this to fill Anrufliste.csv
403 # http://repeater1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=foncalls
405 print "[FritzCallFBF] _getCalls"
407 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("preparing"))
408 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de','var:pagename':'foncalls','var:menu':'fon'})
409 url = "http://%s/cgi-bin/webcm?%s" %(config.plugins.FritzCall.hostname.value, parms)
410 getPage(url).addCallback(self._getCalls1).addErrback(self.errorCalls)
412 def _getCalls1(self, html = ""):
414 # finally we should have successfully lgged in and filled the csv
416 print "[FritzCallFBF] _getCalls1"
418 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("finishing"))
419 parms = urlencode({'getpage':'../html/de/FRITZ!Box_Anrufliste.csv'})
420 url = "http://%s/cgi-bin/webcm?%s" %(config.plugins.FritzCall.hostname.value, parms)
421 getPage(url).addCallback(self._gotPageCalls).addErrback(self.errorCalls)
423 def dial(self, number):
424 ''' initiate a call to number '''
426 # TODO: implement dial out
427 # does not work... if anybody wants to make it work, feel free
428 # I not convinced of FBF's style to establish a connection: first get the connection, then ring the local phone?!?!
431 # http://fritz.box/cgi-bin/webcm
432 # getpage=../html/de/menus/menu2.html
434 # var:pagename=foncalls
439 # var:showDialing=08001235005
440 # telcfg:settings/UseJournal=1
441 # telcfg:command/Dial=08001235005
443 url = "http://%s/cgi-bin/webcm" %config.plugins.FritzCall.hostname.value
445 'getpage':'../html/de/menus/menu2.html',
446 'errorpage':'../html/de/menus/menu2.html',
448 'var:pagename':'foncalls',
449 'var:errorpagename':'foncalls',
452 'var:settings/time':'0,0',
455 'var:showDialing':number,
463 'telcfg:settings/UseJournal':'1',
464 'telcfg:command/Dial':number
466 print "[FritzCallFBF] dial url: '" + url + "' parms: '" + parms + "'"
467 getPage(url, method="POST", headers = {'Content-Type': "application/x-www-form-urlencoded",'Content-Length': str(len(parms))}, postdata=parms)
471 fritzbox = FritzCallFBF()
473 class FritzDisplayCalls(Screen, HelpableScreen):
475 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
477 <screen name="FritzDisplayCalls" position="100,90" size="570,420" title="%s" >
478 <widget name="statusbar" position="0,0" size="570,22" font="Regular;21" />
479 <widget name="entries" position="0,22" size="570,358" scrollbarMode="showOnDemand" />
480 <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
481 <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
482 <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
483 <ePixmap position="425,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
484 <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" />
485 <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" />
486 <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" />
487 <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" />
488 </screen>""" % _("Phone calls")
490 def __init__(self, session, text = ""):
491 self.skin = FritzDisplayCalls.skin
492 Screen.__init__(self, session)
493 HelpableScreen.__init__(self)
495 # TRANSLATORS: keep it short, this is a button
496 self["key_red"] = Button(_("All"))
497 # TRANSLATORS: keep it short, this is a button
498 self["key_green"] = Button(_("Missed"))
499 # TRANSLATORS: keep it short, this is a button
500 self["key_yellow"] = Button(_("Incoming"))
501 # TRANSLATORS: keep it short, this is a button
502 self["key_blue"] = Button(_("Outgoing"))
504 self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
506 "red": self.displayAllCalls,
507 "green": self.displayMissedCalls,
508 "yellow": self.displayInCalls,
509 "blue": self.displayOutCalls,
511 "ok": self.showEntry,}, -2)
513 # TRANSLATORS: this is a help text, keep it short
514 self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
515 # TRANSLATORS: this is a help text, keep it short
516 self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
517 # TRANSLATORS: this is a help text, keep it short
518 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Display all calls"))]))
519 # TRANSLATORS: this is a help text, keep it short
520 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Display missed calls"))]))
521 # TRANSLATORS: this is a help text, keep it short
522 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Display incoming calls"))]))
523 # TRANSLATORS: this is a help text, keep it short
524 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Display outgoing calls"))]))
526 self["statusbar"] = Label(_("Getting calls from FRITZ!Box..."))
527 self["entries"] = MenuList([], True, content = eListboxPythonMultiContent)
528 self["entries"].l.setFont(0, gFont("Console", 16))
529 self["entries"].l.setItemHeight(20)
531 print "[FritzDisplayCalls] init: '''%s'''" %config.plugins.FritzCall.fbfCalls.value
537 def displayAllCalls(self):
538 print "[FritzDisplayCalls] displayAllCalls"
539 config.plugins.FritzCall.fbfCalls.value = FBF_ALL_CALLS
540 config.plugins.FritzCall.fbfCalls.save()
543 def displayMissedCalls(self):
544 print "[FritzDisplayCalls] displayMissedCalls"
545 config.plugins.FritzCall.fbfCalls.value = FBF_MISSED_CALLS
546 config.plugins.FritzCall.fbfCalls.save()
549 def displayInCalls(self):
550 print "[FritzDisplayCalls] displayInCalls"
551 config.plugins.FritzCall.fbfCalls.value = FBF_IN_CALLS
552 config.plugins.FritzCall.fbfCalls.save()
555 def displayOutCalls(self):
556 print "[FritzDisplayCalls] displayOutCalls"
557 config.plugins.FritzCall.fbfCalls.value = FBF_OUT_CALLS
558 config.plugins.FritzCall.fbfCalls.save()
561 def displayCalls(self):
562 print "[FritzDisplayCalls] displayCalls"
563 self.header = fbfCallsChoices[config.plugins.FritzCall.fbfCalls.value]
564 fritzbox.getCalls(self, self.gotCalls, config.plugins.FritzCall.fbfCalls.value)
566 def gotCalls(self, callList):
567 print "[FritzDisplayCalls] gotCalls"
568 self.updateStatus(self.header + " (" + str(len(callList)) + ")")
570 for (number, date, remote, direct, here) in callList:
571 while (len(remote) + len(here)) > 40:
572 if len(remote) > len(here):
576 found = re.match("(\d\d.\d\d.)\d\d( \d\d:\d\d)", date)
577 if found: date = found.group(1) + found.group(2)
578 if direct == FBF_OUT_CALLS:
579 message = date + " " + remote + " -> " + here
581 message = date + " " + here + " -> " + remote
582 sortlist.append([number, (eListboxPythonMultiContent.TYPE_TEXT, 0, 0, 560, 20, 0, RT_HALIGN_LEFT, message)])
583 self["entries"].setList(sortlist)
586 print "[FritzDisplayCalls] showEntry"
587 cur = self["entries"].getCurrent()
590 print "[FritzDisplayCalls] showEntry %s" % (cur[0])
591 # TODO: offer to call number
592 fullname = phonebook.search(cur[0])
594 self.session.open(MessageBox,
595 cur[0] + "\n\n" + fullname.replace(", ","\n"),
596 type = MessageBox.TYPE_INFO)
598 self.actualNumber = cur[0]
599 self.session.openWithCallback(
602 _("Do you want to add a phonebook entry for\n\n%s?") %cur[0]
605 self.session.open(MessageBox,
607 type = MessageBox.TYPE_INFO)
609 def addConfirmed(self, ret):
611 phonebook.displayPhonebook(self.session).add(self.actualNumber)
613 def updateStatus(self, text):
614 self["statusbar"].setText(text)
617 class FritzCallPhonebook:
623 print "[FritzCallPhonebook] reload"
626 if not config.plugins.FritzCall.enable.value:
629 if config.plugins.FritzCall.phonebook.value and os.path.exists(config.plugins.FritzCall.phonebookLocation.value):
630 phonebookTxtCorrupt = False
631 for line in open(config.plugins.FritzCall.phonebookLocation.value):
632 if re.match("^\d+#.*$", line):
634 number, name = line.split("#")
635 if not self.phonebook.has_key(number):
636 self.phonebook[number] = name
638 print "[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" %line
639 phonebookTxtCorrupt = True
641 print "[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" %line
642 phonebookTxtCorrupt = True
644 if phonebookTxtCorrupt:
645 # dump phonebook to PhoneBook.txt
646 print "[FritzCallPhonebook] dump Phonebook.txt"
647 os.rename(config.plugins.FritzCall.phonebookLocation.value,
648 config.plugins.FritzCall.phonebookLocation.value + ".bck")
649 fNew = open(config.plugins.FritzCall.phonebookLocation.value, 'w')
650 for (number, name) in self.phonebook.iteritems():
651 fNew.write(number + "#" + name)
654 if config.plugins.FritzCall.fritzphonebook.value:
655 fritzbox.loadFritzBoxPhonebook()
657 def search(self, number):
658 # print "[FritzCallPhonebook] Searching for %s" %number
660 if config.plugins.FritzCall.phonebook.value or config.plugins.FritzCall.fritzphonebook.value:
661 if self.phonebook.has_key(number):
662 name = self.phonebook[number].replace(", ", "\n").strip()
665 def add(self, number, name):
666 print "[FritzCallPhonebook] add"
667 #===============================================================================
668 # It could happen, that two reverseLookups are running in parallel,
669 # so check first, whether we have already added the number to the phonebook.
670 #===============================================================================
671 self.phonebook[number] = name;
672 if number <> 0 and config.plugins.FritzCall.phonebook.value and config.plugins.FritzCall.addcallers.value:
674 f = open(config.plugins.FritzCall.phonebookLocation.value, 'a')
675 name = name.strip() + "\n"
676 string = "%s#%s" %(number, name)
679 print "[FritzCallPhonebook] added %s with %s to Phonebook.txt" %(number, name)
685 def remove(self, number):
686 print "[FritzCallPhonebook] remove"
687 if number in self.phonebook:
688 print "[FritzCallPhonebook] remove entry in phonebook"
689 del self.phonebook[number]
690 if config.plugins.FritzCall.phonebook.value:
692 print "[FritzCallPhonebook] remove entry in Phonebook.txt"
693 fOld = open(config.plugins.FritzCall.phonebookLocation.value, 'r')
694 fNew = open(config.plugins.FritzCall.phonebookLocation.value + str(os.getpid()), 'w')
695 line = fOld.readline()
697 if not re.match("^"+number+"#.*$", line):
699 line = fOld.readline()
702 os.remove(config.plugins.FritzCall.phonebookLocation.value)
703 os.rename(config.plugins.FritzCall.phonebookLocation.value + str(os.getpid()),
704 config.plugins.FritzCall.phonebookLocation.value)
705 print "[FritzCallPhonebook] removed %s from Phonebook.txt" %number
712 class displayPhonebook(Screen, HelpableScreen, NumericalTextInput):
713 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
715 <screen name="FritzDisplayPhonebook" position="100,90" size="570,420" title="%s" >
716 <widget name="entries" position="5,5" size="560,370" scrollbarMode="showOnDemand" />
717 <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
718 <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
719 <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
720 <ePixmap position="425,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
721 <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" />
722 <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" />
723 <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" />
724 <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" />
725 </screen>""" % _("Phonebook")
727 def __init__(self, session):
728 Screen.__init__(self, session)
729 NumericalTextInput.__init__(self)
730 HelpableScreen.__init__(self)
732 # TRANSLATORS: keep it short, this is a button
733 self["key_red"] = Button(_("Delete"))
734 # TRANSLATORS: keep it short, this is a button
735 self["key_green"] = Button(_("New"))
736 # TRANSLATORS: keep it short, this is a button
737 self["key_yellow"] = Button(_("Edit"))
738 # TRANSLATORS: keep it short, this is a button
739 self["key_blue"] = Button(_("Search"))
741 self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
748 "ok": self.showEntry,}, -2)
750 # TRANSLATORS: this is a help text, keep it short
751 self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
752 # TRANSLATORS: this is a help text, keep it short
753 self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
754 # TRANSLATORS: this is a help text, keep it short
755 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Delete entry"))]))
756 # TRANSLATORS: this is a help text, keep it short
757 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Add entry to phonebook"))]))
758 # TRANSLATORS: this is a help text, keep it short
759 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Edit selected entry"))]))
760 # TRANSLATORS: this is a help text, keep it short
761 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Search (case insensitive)"))]))
763 self["entries"] = MenuList([], True, content = eListboxPythonMultiContent)
764 self["entries"].l.setFont(0, gFont("Console", 16))
765 self["entries"].l.setItemHeight(20)
766 print "[FritzCallPhonebook] displayPhonebook init"
769 def display(self, filter=""):
770 print "[FritzCallPhonebook] displayPhonebook/display"
772 sortlistHelp = sorted((name.lower(), name, number) for (number, name) in phonebook.phonebook.iteritems())
773 for (low, name, number) in sortlistHelp:
774 if number == "01234567890":
777 low = low.decode("utf-8")
779 filter = filter.lower()
780 if low.find(filter) == -1:
782 name = name.strip().decode("utf-8")
783 number = number.strip().decode("utf-8")
784 found = re.match("([^,]*),.*", name) # strip address information from the name part
786 shortname = found.group(1)
789 if len(shortname) > 35:
790 shortname = shortname[:35]
791 message = u"%-35s %-18s" %(shortname, number)
792 message = message.encode("utf-8")
793 # print "[FritzCallPhonebook] displayPhonebook/display: add " + message
794 self.sortlist.append([(number.encode("utf-8","replace"),
795 name.encode("utf-8","replace")),
796 (eListboxPythonMultiContent.TYPE_TEXT, 0, 0, 560, 20, 0, RT_HALIGN_LEFT, message)])
797 except UnicodeDecodeError: # this should definitely not happen
798 self.session.open(MessageBox, _("Corrupt phonebook entry\nfor number %d\nDeleting.") %number, type = MessageBox.TYPE_ERROR)
799 phonebook.remove(number)
802 self["entries"].setList(self.sortlist)
805 cur = self["entries"].getCurrent()
807 print "[FritzCallPhonebook] displayPhonebook/showEntry (%s,%s)" % (cur[0][0],cur[0][1])
808 fullname = phonebook.search(cur[0][0])
810 self.session.open(MessageBox,
811 cur[0][0] + "\n\n" + fullname.replace(", ","\n"),
812 type = MessageBox.TYPE_INFO)
814 self.session.open(MessageBox,
816 type = MessageBox.TYPE_INFO)
819 cur = self["entries"].getCurrent()
821 print "[FritzCallPhonebook] displayPhonebook/delete " + cur[0][0]
822 self.session.openWithCallback(
823 self.deleteConfirmed,
825 _("Do you really want to delete entry for\n\n%(number)s\n\n%(name)s?")
826 % { 'number':str(cur[0][0]), 'name':str(cur[0][1]).replace(", ","\n") }
829 self.session.open(MessageBox,_("No entry selected"), MessageBox.TYPE_INFO)
831 def deleteConfirmed(self, ret):
832 print "[FritzCallPhonebook] displayPhonebook/deleteConfirmed"
834 # if ret: delete number from sortlist, delete number from phonebook.phonebook and write it to disk
836 cur = self["entries"].getCurrent()
839 # delete number from sortlist, delete number from phonebook.phonebook and write it to disk
840 print "[FritzCallPhonebook] displayPhonebook/deleteConfirmed: remove " +cur[0][0]
841 phonebook.remove(cur[0][0])
844 # self.session.open(MessageBox, _("Not deleted."), MessageBox.TYPE_INFO)
846 self.session.open(MessageBox,_("No entry selected"), MessageBox.TYPE_INFO)
848 def add(self, number = ""):
849 class addScreen(Screen, ConfigListScreen):
850 '''ConfiglistScreen with two ConfigTexts for Name and Number'''
851 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
853 <screen position="100,150" size="570,130" title="%s" >
854 <widget name="config" position="5,5" size="560,75" scrollbarMode="showOnDemand" />
855 <ePixmap position="145,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
856 <ePixmap position="285,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
857 <widget name="key_red" position="145,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
858 <widget name="key_green" position="285,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
859 </screen>""" % _("Add entry to phonebook")
861 def __init__(self, session, parent, number = ""):
863 # setup screen with two ConfigText and OK and ABORT button
865 Screen.__init__(self, session)
866 self.session = session
868 # TRANSLATORS: keep it short, this is a button
869 self["key_red"] = Button(_("Cancel"))
870 # TRANSLATORS: keep it short, this is a button
871 self["key_green"] = Button(_("OK"))
872 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
874 "cancel": self.cancel,
881 ConfigListScreen.__init__(self, self.list, session = session)
882 config.plugins.FritzCall.name.value = ""
883 config.plugins.FritzCall.number.value = number
884 self.list.append(getConfigListEntry(_("Name"), config.plugins.FritzCall.name))
885 self.list.append(getConfigListEntry(_("Number"), config.plugins.FritzCall.number))
886 self["config"].list = self.list
887 self["config"].l.setList(self.list)
891 # get texts from Screen
892 # add (number,name) to sortlist and phonebook.phonebook and disk
893 self.number = config.plugins.FritzCall.number.value
894 self.name = config.plugins.FritzCall.name.value
895 if not self.number or not self.name:
896 self.session.open(MessageBox, _("Entry incomplete."), type = MessageBox.TYPE_ERROR)
898 # add (number,name) to sortlist and phonebook.phonebook and disk
899 oldname = phonebook.search(self.number)
901 self.session.openWithCallback(
902 self.overwriteConfirmed,
904 _("Do you really want to overwrite entry for\n%(number)s\n\n%(name)s\n\nwith\n\n%(newname)s?")
906 'number':self.number,
908 'newname':self.name.replace(", ","\n")
913 phonebook.add(self.number, self.name)
915 self.parent.display()
917 def overwriteConfirmed(self, ret):
919 phonebook.add(self.number, self.name)
920 self.parent.display()
926 print "[FritzCallPhonebook] displayPhonebook/add"
927 self.session.open(addScreen, self, number)
928 # self.session.open(MessageBox, "Not yet implemented.", type = MessageBox.TYPE_INFO)
932 # Edit selected Timer
933 class editScreen(Screen, ConfigListScreen):
934 '''ConfiglistScreen with two ConfigTexts for Name and Number'''
935 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
937 <screen position="100,150" size="570,130" title="%s" >
938 <widget name="config" position="5,5" size="560,75" scrollbarMode="showOnDemand" />
939 <ePixmap position="145,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
940 <ePixmap position="285,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
941 <widget name="key_red" position="145,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
942 <widget name="key_green" position="285,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
943 </screen>""" % _("Edit phonebook entry").decode("utf-8")
945 def __init__(self, session, parent, name, number):
947 # setup screen with two ConfigText and OK and ABORT button
949 Screen.__init__(self, session)
950 self.session = session
952 # TRANSLATORS: keep it short, this is a button
953 self["key_red"] = Button(_("Cancel"))
954 # TRANSLATORS: keep it short, this is a button
955 self["key_green"] = Button(_("OK"))
956 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
958 "cancel": self.cancel,
965 ConfigListScreen.__init__(self, self.list, session = session)
966 # config.plugins.FritzCall.name.value = config.plugins.FritzCall.name.value.replace(", ","\n")
969 config.plugins.FritzCall.name.value = name
970 config.plugins.FritzCall.number.value = number
971 self.list.append(getConfigListEntry(_("Name"), config.plugins.FritzCall.name))
972 self.list.append(getConfigListEntry(_("Number"), config.plugins.FritzCall.number))
973 self["config"].list = self.list
974 self["config"].l.setList(self.list)
978 print "[FritzCallPhonebook] displayPhonebook/edit: add (%s,%s)" %(config.plugins.FritzCall.number.value,config.plugins.FritzCall.name.value)
979 self.newname = config.plugins.FritzCall.name.value.replace("\n",", ")
980 self.newnumber = config.plugins.FritzCall.number.value
981 if not self.number or not self.name:
982 self.session.open(MessageBox, _("Entry incomplete."), type = MessageBox.TYPE_ERROR)
984 if self.number != self.newnumber:
985 if phonebook.search(self.newnumber):
986 self.session.openWithCallback(
987 self.overwriteConfirmed,
989 _("Do you really want to overwrite entry for\n%(number)s\n\n%(name)s\n\nwith\n\n%(newname)s?")
991 'number':self.newnumber,
992 'name':phonebook.search(self.newnumber).replace(", ","\n"),
993 'newname': self.newname
999 phonebook.remove(self.number)
1000 phonebook.add(self.newnumber, self.newname)
1002 self.parent.display()
1004 def overwriteConfirmed(self, ret):
1006 phonebook.add(self.newnumber, self.newname)
1007 self.parent.display()
1014 print "[FritzCallPhonebook] displayPhonebook/edit"
1015 # self.session.open(MessageBox, "Not yet implemented.", type = MessageBox.TYPE_INFO)
1017 cur = self["entries"].getCurrent()
1019 self.session.open(MessageBox,_("No entry selected"), MessageBox.TYPE_INFO)
1021 (number, name) = cur[0]
1022 self.session.open(editScreen, self, str(name), str(number))
1025 print "[FritzCallPhonebook] displayPhonebook/search"
1026 self.help_window = self.session.instantiateDialog(NumericalTextInputHelpDialog, self)
1027 self.help_window.show()
1028 self.session.openWithCallback(self.doSearch, InputBox, _("Enter Search Terms"), _("Search phonebook"))
1030 def doSearch(self, searchTerms):
1031 if not searchTerms: searchTerms = ""
1032 print "[FritzCallPhonebook] displayPhonebook/doSearch: " + searchTerms
1033 if self.help_window:
1034 self.session.deleteDialog(self.help_window)
1035 self.help_window = None
1036 self.display(searchTerms)
1041 phonebook = FritzCallPhonebook()
1044 class FritzCallSetup(Screen, ConfigListScreen, HelpableScreen):
1045 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
1047 <screen position="100,90" size="570,420" title="%s" >
1048 <widget name="config" position="5,10" size="560,300" scrollbarMode="showOnDemand" />
1049 <widget name="consideration" position="20,320" font="Regular;20" halign="center" size="510,50" />
1050 <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
1051 <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
1052 <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
1053 <ePixmap position="425,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
1054 <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" />
1055 <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" />
1056 <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" />
1057 <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" />
1058 </screen>""" % _("FritzCall Setup")
1060 def __init__(self, session, args = None):
1062 Screen.__init__(self, session)
1063 HelpableScreen.__init__(self)
1064 self.session = session
1066 self["consideration"] = Label(_("You need to enable the monitoring on your FRITZ!Box by dialing #96*5*!"))
1069 # Initialize Buttons
1070 # TRANSLATORS: keep it short, this is a button
1071 self["key_red"] = Button(_("Cancel"))
1072 # TRANSLATORS: keep it short, this is a button
1073 self["key_green"] = Button(_("OK"))
1074 # TRANSLATORS: keep it short, this is a button
1075 self["key_yellow"] = Button(_("Phone calls"))
1076 # TRANSLATORS: keep it short, this is a button
1077 self["key_blue"] = Button(_("Phonebook"))
1079 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
1083 "yellow": self.displayCalls,
1084 "blue": self.displayPhonebook,
1085 "cancel": self.cancel,
1090 # TRANSLATORS: this is a help text, keep it short
1091 self.helpList.append((self["setupActions"], "SetupActions", [("ok", _("save and quit"))]))
1092 # TRANSLATORS: this is a help text, keep it short
1093 self.helpList.append((self["setupActions"], "SetupActions", [("save", _("save and quit"))]))
1094 # TRANSLATORS: this is a help text, keep it short
1095 self.helpList.append((self["setupActions"], "SetupActions", [("cancel", _("quit"))]))
1096 # TRANSLATORS: this is a help text, keep it short
1097 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("quit"))]))
1098 # TRANSLATORS: this is a help text, keep it short
1099 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("save and quit"))]))
1100 # TRANSLATORS: this is a help text, keep it short
1101 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("display calls"))]))
1102 # TRANSLATORS: this is a help text, keep it short
1103 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("display phonebook"))]))
1105 ConfigListScreen.__init__(self, self.list, session = session)
1110 ConfigListScreen.keyLeft(self)
1114 ConfigListScreen.keyRight(self)
1117 def createSetup(self):
1119 self.list.append(getConfigListEntry(_("Call monitoring"), config.plugins.FritzCall.enable))
1120 if config.plugins.FritzCall.enable.value:
1121 self.list.append(getConfigListEntry(_("FRITZ!Box FON address (Name or IP)"), config.plugins.FritzCall.hostname))
1123 self.list.append(getConfigListEntry(_("Show after Standby"), config.plugins.FritzCall.afterStandby))
1125 self.list.append(getConfigListEntry(_("Show Calls for specific MSN"), config.plugins.FritzCall.filter))
1126 if config.plugins.FritzCall.filter.value:
1127 self.list.append(getConfigListEntry(_("MSN to show (separated by ,)"), config.plugins.FritzCall.filtermsn))
1129 self.list.append(getConfigListEntry(_("Show Outgoing Calls"), config.plugins.FritzCall.showOutgoing))
1130 if config.plugins.FritzCall.showOutgoing.value:
1131 self.list.append(getConfigListEntry(_("Areacode to add to Outgoing Calls (if necessary)"), config.plugins.FritzCall.prefix))
1132 self.list.append(getConfigListEntry(_("Timeout for Call Notifications (seconds)"), config.plugins.FritzCall.timeout))
1133 self.list.append(getConfigListEntry(_("Reverse Lookup Caller ID (select country below)"), config.plugins.FritzCall.lookup))
1134 if config.plugins.FritzCall.lookup.value:
1135 self.list.append(getConfigListEntry(_("Country"), config.plugins.FritzCall.country))
1137 self.list.append(getConfigListEntry(_("Password Accessing FRITZ!Box"), config.plugins.FritzCall.password))
1138 self.list.append(getConfigListEntry(_("Read PhoneBook from FRITZ!Box"), config.plugins.FritzCall.fritzphonebook))
1139 if config.plugins.FritzCall.fritzphonebook.value:
1140 self.list.append(getConfigListEntry(_("Append type of number"), config.plugins.FritzCall.showType))
1141 self.list.append(getConfigListEntry(_("Append shortcut number"), config.plugins.FritzCall.showShortcut))
1142 self.list.append(getConfigListEntry(_("Append vanity name"), config.plugins.FritzCall.showVanity))
1144 self.list.append(getConfigListEntry(_("Use internal PhoneBook"), config.plugins.FritzCall.phonebook))
1145 if config.plugins.FritzCall.phonebook.value:
1146 self.list.append(getConfigListEntry(_("PhoneBook Location"), config.plugins.FritzCall.phonebookLocation))
1147 if config.plugins.FritzCall.lookup.value:
1148 self.list.append(getConfigListEntry(_("Automatically add new Caller to PhoneBook"), config.plugins.FritzCall.addcallers))
1150 self.list.append(getConfigListEntry(_("Strip Leading 0"), config.plugins.FritzCall.internal))
1151 # self.list.append(getConfigListEntry(_("Default display mode for FRITZ!Box calls"), config.plugins.FritzCall.fbfCalls))
1153 self["config"].list = self.list
1154 self["config"].l.setList(self.list)
1157 # print "[FritzCallSetup] save"
1158 for x in self["config"].list:
1160 if fritz_call is not None:
1161 fritz_call.connect()
1162 print "[FritzCallSetup] called phonebook.reload()"
1168 # print "[FritzCallSetup] cancel"
1169 for x in self["config"].list:
1173 def displayCalls(self):
1174 self.session.open(FritzDisplayCalls)
1176 def displayPhonebook(self):
1177 self.session.open(phonebook.displayPhonebook)
1182 class FritzCallList:
1186 def add(self, event, date, number, caller, phone):
1187 print "[FritzCallList] add"
1188 if len(self.callList) > 10:
1189 if self.callList[0] != "Start":
1190 self.callList[0] = "Start"
1191 del self.callList[1]
1193 self.callList.append((event, number, date, caller, phone))
1196 print "[FritzCallList] display"
1198 global my_global_session
1200 # Standby.inStandby.onClose.remove(self.display) object does not exist anymore...
1201 # build screen from call list
1203 if self.callList[0] == "Start":
1204 text = text + _("Last 10 calls:\n")
1205 del self.callList[0]
1207 for call in self.callList:
1208 (event, number, date, caller, phone) = call
1214 # shorten the date info
1215 found = re.match(".*(\d\d.\d\d.)\d\d( \d\d:\d\d)", date)
1216 if found: date = found.group(1) + found.group(2)
1218 # our phone could be of the form "0123456789 (home)", then we only take "home"
1219 found = re.match(".*\((.*)\)", phone)
1220 if found: phone = found.group(1)
1222 # if we have an unknown number, show the number
1223 if caller == _("UNKNOWN") and number != "":
1226 # strip off the address part of the remote number, if there is any
1227 found = re.match("(.*)\n.*", caller)
1228 if found: caller = found.group(1)
1230 while (len(caller) + len(phone)) > 40:
1231 if len(caller) > len(phone):
1232 caller = caller[:-1]
1236 text = text + "%s %s %s %s\n" %(date, caller, direction, phone)
1238 print "[FritzCallList] display: '%s %s %s %s'" %(date, caller, direction, phone)
1240 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO)
1241 # my_global_session.open(FritzDisplayCalls, text) # TODO please HELP: from where can I get a session?
1245 callList = FritzCallList()
1247 def notifyCall(event, date, number, caller, phone):
1248 if Standby.inStandby is None or config.plugins.FritzCall.afterStandby.value == "each":
1249 # TODO: mute audio and/or stop tv/video
1251 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 }
1253 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 }
1254 print "[FritzCall] notifyCall:\n%s" %text
1255 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1256 elif config.plugins.FritzCall.afterStandby.value == "inList":
1258 # if not yet done, register function to show call list
1260 if not standbyMode :
1262 Standby.inStandby.onHide.append(callList.display)
1263 # add text/timeout to call list
1264 callList.add(event, date, number, caller, phone)
1265 print "[FritzCall] notifyCall: added to callList"
1266 else: # this is the "None" case
1267 print "[FritzCall] notifyCall: standby and no show"
1270 #===============================================================================
1271 # We need a separate class for each invocation of reverseLookup to retain
1272 # the necessary data for the notification
1273 #===============================================================================
1277 class FritzReverseLookupAndNotifier:
1278 def __init__(self, event, number, caller, phone, date):
1279 print "[FritzReverseLookupAndNotifier] reverse Lookup for %s!" %number
1281 self.number = number
1282 self.caller = caller
1285 self.currentWebsite = None
1286 self.nextWebsiteNo = 0
1289 dom = parse(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/reverselookup.xml"))
1290 for top in dom.getElementsByTagName("reverselookup"):
1291 for country in top.getElementsByTagName("country"):
1292 code = country.getAttribute("code").replace("+","00")
1293 countries[code] = country.getElementsByTagName("website")
1295 self.countrycode = config.plugins.FritzCall.country.value
1297 if number[0] != "0":
1298 self.caller = _("UNKNOWN")
1299 self.notifyAndReset()
1302 if self.number[:2] == "00":
1303 if countries.has_key(self.number[:3]): # e.g. USA
1304 self.countrycode = self.number[:3]
1305 elif countries.has_key(self.number[:4]):
1306 self.countrycode = self.number[:4]
1307 elif countries.has_key(self.number[:5]):
1308 self.countrycode = self.number[:5]
1310 print "[FritzReverseLookupAndNotifier] Country cannot be reverse handled"
1311 self.caller = _("UNKNOWN")
1312 self.notifyAndReset()
1315 if countries.has_key(self.countrycode):
1316 print "[FritzReverseLookupAndNotifier] Found website for reverse lookup"
1317 self.websites = countries[self.countrycode]
1318 self.nextWebsiteNo = 1
1319 self.handleWebsite(self.websites[0])
1321 print "[FritzReverseLookupAndNotifier] Country cannot be reverse handled"
1322 self.caller = _("UNKNOWN")
1323 self.notifyAndReset()
1326 def handleWebsite(self, website):
1327 print "[FritzReverseLookupAndNotifier] handleWebsite: " + website.getAttribute("name")
1328 if self.number[:2] == "00":
1329 number = website.getAttribute("prefix") + self.number.replace(self.countrycode,"")
1331 number = self.number
1333 url = website.getAttribute("url")
1334 if re.search('$AREACODE',url) or re.search('$PFXAREACODE',url):
1335 print "[FritzReverseLookupAndNotifier] handleWebsite: (PFX)ARECODE cannot be handled"
1336 self.caller = _("UNKNOWN")
1337 self.notifyAndReset()
1340 # Apparently, there is no attribute called (pfx)areacode anymore
1341 # So, this below will not work.
1343 if re.search('\\$AREACODE',url) and website.hasAttribute("areacode"):
1344 areaCodeLen = int(website.getAttribute("areacode"))
1345 url = url.replace("$AREACODE","%(areacode)s").replace("$NUMBER","%(number)s")
1346 url = url %{ 'areacode':number[:areaCodeLen], 'number':number[areaCodeLen:] }
1347 elif re.search('\\$PFXAREACODE',url) and website.hasAttribute("pfxareacode"):
1348 areaCodeLen = int(website.getAttribute("pfxareacode"))
1349 url = url.replace("$PFXAREACODE","%(pfxareacode)s").replace("$NUMBER","%(number)s")
1350 url = url %{ 'pfxareacode':number[:areaCodeLen], 'number':number[areaCodeLen:] }
1351 elif re.search('\\$NUMBER',url):
1352 url = url.replace("$NUMBER","%s") %number
1354 print "[FritzReverseLookupAndNotifier] handleWebsite: cannot handle websites with no $NUMBER in url"
1355 self.caller = _("UNKNOWN")
1356 self.notifyAndReset()
1358 print "[FritzReverseLookupAndNotifier] Url to query: " + url
1359 url = url.encode("UTF-8", "replace")
1360 self.currentWebsite = website
1361 getPage(url, method="GET").addCallback(self._gotPage).addErrback(self._gotError)
1363 def _gotPage(self, page):
1364 print "[FritzReverseLookupAndNotifier] _gotPage"
1365 found = re.match('.*content=".*?charset=([^"]+)"',page,re.S)
1367 print "[FritzReverseLookupAndNotifier] Charset: " + found.group(1)
1368 page = page.replace("\xa0"," ").decode(found.group(1), "replace")
1370 page = page.replace("\xa0"," ").decode("ISO-8859-1", "replace")
1372 for entry in self.currentWebsite.getElementsByTagName("entry"):
1373 # print "[FritzReverseLookupAndNotifier] _gotPage: try entry"
1375 for what in ["name", "street", "city", "zipcode"]:
1376 pat = "(.*)" + self.getPattern(entry, what)
1377 # print "[FritzReverseLookupAndNotifier] _gotPage: look for '''%s''' with '''%s'''" %( what, pat )
1378 found = re.match(pat, page, re.S|re.M)
1380 # print "[FritzReverseLookupAndNotifier] _gotPage: found for '''%s''': '''%s'''" %( what, found.group(2) )
1381 item = found.group(2).replace(" "," ").replace("</b>","").replace(",","")
1382 item = html2utf8(item)
1383 newitem = item.replace(" ", " ")
1384 while newitem != item:
1386 newitem = item.replace(" ", " ")
1387 details.append(item.strip())
1388 # print "[FritzReverseLookupAndNotifier] _gotPage: got '''%s''': '''%s'''" %( what, item.strip() )
1392 if len(details) != 4:
1396 address = details[1] + ", " + details[3] + " " + details[2]
1397 print "[FritzReverseLookupAndNotifier] _gotPage: Reverse lookup succeeded:\nName: %s\nAddress: %s" %(name, address)
1398 self.caller = "%s, %s" %(name, address)
1399 if self.number != 0 and config.plugins.FritzCall.addcallers.value and self.event == "RING":
1400 phonebook.add(self.number, self.caller)
1402 self.caller = self.caller.replace(", ", "\n").encode("UTF-8", "replace")
1403 self.notifyAndReset()
1407 self._gotError("[FritzReverseLookupAndNotifier] _gotPage: Nothing found at %s" %self.currentWebsite.getAttribute("name"))
1409 def _gotError(self, error = ""):
1410 print "[FritzReverseLookupAndNotifier] _gotError - Error: %s" %error
1411 if self.nextWebsiteNo >= len(self.websites):
1412 print "[FritzReverseLookupAndNotifier] _gotError: I give up"
1413 self.caller = _("UNKNOWN")
1414 self.notifyAndReset()
1417 print "[FritzReverseLookupAndNotifier] _gotError: try next website"
1418 self.nextWebsiteNo = self.nextWebsiteNo+1
1419 self.handleWebsite(self.websites[self.nextWebsiteNo-1])
1421 def getPattern(self, website, which):
1422 pat1 = website.getElementsByTagName(which)
1424 print "Something strange: more than one %s for website %s" %(which, website.getAttribute("name"))
1425 return pat1[0].childNodes[0].data
1427 def notifyAndReset(self, timeout=config.plugins.FritzCall.timeout.value):
1428 notifyCall(self.event, self.date, self.number, self.caller, self.phone)
1429 # kill that object...
1432 class FritzProtocol(LineReceiver):
1434 print "[FritzProtocol] __init__"
1437 def resetValues(self):
1438 print "[FritzProtocol] resetValues"
1444 def notifyAndReset(self, timeout=config.plugins.FritzCall.timeout.value):
1445 notifyCall(self.event, self.date, self.number, self.caller, self.phone)
1448 def lineReceived(self, line):
1449 print "[FritzProtocol] lineReceived: %s" %line
1450 #15.07.06 00:38:54;CALL;1;4;<from/extern>;<to/our msn>;
1451 #15.07.06 00:38:58;DISCONNECT;1;0;
1452 #15.07.06 00:39:22;RING;0;<from/extern>;<to/our msn>;
1453 #15.07.06 00:39:27;DISCONNECT;0;0;
1455 (self.date, self.event) = a[0:2]
1457 if self.event == "RING" or (self.event == "CALL" and config.plugins.FritzCall.showOutgoing.value):
1460 if self.event == "RING":
1465 print "[FritzProtocol] lineReceived phone: '''%s''' number: '''%s'''" % (phone, number)
1467 filtermsns = config.plugins.FritzCall.filtermsn.value.split(",")
1468 for i in range(len(filtermsns)):
1469 filtermsns[i] = filtermsns[i].strip()
1470 if not (config.plugins.FritzCall.filter.value and phone not in filtermsns):
1471 print "[FritzProtocol] lineReceived no filter hit"
1472 phonename = phonebook.search(phone) # do we have a name for the number of our side?
1473 if phonename is not None:
1474 self.phone = "%s (%s)" %(phone, phonename)
1478 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0]=="0":
1479 print "[FritzProtocol] lineReceived: strip leading 0"
1480 self.number = number[1:]
1482 self.number = number
1483 if self.event == "CALL" and self.number[0] != '0': # should only happen for outgoing
1484 print "[FritzProtocol] lineReceived: add local prefix"
1485 self.number = config.plugins.FritzCall.prefix.value + self.number
1487 # check, whether we are in Germany and number has call-by-call prefix. If so strip it
1488 if self.event == "CALL" and config.plugins.FritzCall.country.value == '0049':
1489 if re.match('^0100\d\d', self.number):
1490 print "[FritzProtocol] lineReceived: strip CbC 0100.. prefix"
1491 self.number = self.number[6:]
1492 elif re.match('^010\d\d', self.number):
1493 print "[FritzProtocol] lineReceived: strip CbC 010.. prefix"
1494 self.number = self.number[5:]
1496 if self.number is not "":
1497 print "[FritzProtocol] lineReceived phonebook.search: %s" %self.number
1498 self.caller = phonebook.search(self.number)
1499 print "[FritzProtocol] lineReceived phonebook.search reault: %s" %self.caller
1500 if (self.caller is None) and config.plugins.FritzCall.lookup.value:
1501 FritzReverseLookupAndNotifier(self.event, self.number, self.caller, self.phone, self.date)
1502 return # reverselookup is supposed to handle the message itself
1504 if self.caller is None:
1505 self.caller = _("UNKNOWN")
1507 self.notifyAndReset()
1509 class FritzClientFactory(ReconnectingClientFactory):
1514 self.hangup_ok = False
1516 def startedConnecting(self, connector):
1517 Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box..."), type=MessageBox.TYPE_INFO, timeout=2)
1519 def buildProtocol(self, addr):
1520 Notifications.AddNotification(MessageBox, _("Connected to FRITZ!Box!"), type=MessageBox.TYPE_INFO, timeout=4)
1522 return FritzProtocol()
1524 def clientConnectionLost(self, connector, reason):
1525 if not self.hangup_ok:
1526 Notifications.AddNotification(MessageBox, _("Connection to FRITZ!Box! lost\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1527 ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
1529 def clientConnectionFailed(self, connector, reason):
1530 Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box failed\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1531 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
1541 if config.plugins.FritzCall.enable.value:
1542 f = FritzClientFactory()
1543 self.d = (f, reactor.connectTCP(config.plugins.FritzCall.hostname.value, 1012, f))
1549 if self.d is not None:
1550 self.d[0].hangup_ok = True
1551 self.d[0].stopTrying()
1552 self.d[1].disconnect()
1555 def displayCalls(session, servicelist):
1556 session.open(FritzDisplayCalls)
1558 def displayPhonebook(session, servicelist):
1559 session.open(phonebook.displayPhonebook)
1562 session.open(FritzCallSetup)
1566 def autostart(reason, **kwargs):
1569 # ouch, this is a hack
1570 if kwargs.has_key("session"):
1571 global my_global_session
1572 my_global_session = kwargs["session"]
1575 print "[FRITZ!Call] - Autostart"
1577 fritz_call = FritzCall()
1579 fritz_call.shutdown()
1582 def Plugins(**kwargs):
1583 what = _("Display FRITZ!box-Fon calls on screen")
1584 what_calls = _("Phone calls")
1585 what_phonebook = _("Phonebook")
1586 return [ PluginDescriptor(name="FritzCall", description=what, where = PluginDescriptor.WHERE_PLUGINMENU, icon = "plugin.png", fnc=main),
1587 PluginDescriptor(name=what_calls, description=what_calls, where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayCalls),
1588 PluginDescriptor(name=what_phonebook, description=what_phonebook, where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayPhonebook),
1589 PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart) ]